Handling Input

Let's get a little further into our example and look at how to process input. Consider this HTML form used to create or edit a forum in our system:

<html> {{handler SimpleForums.dll/EditForum}}   <head>     <title>Edit Forum</title>   </head>   <body>     <h1>Edit Forum Information</h1>     {{if ValidForumId}}     <form action="editforum.srf?forumid={{ForumId}}"      method="post">       <table border="0" cellpadding="0">         <tr>           <td>             Forum Name:           </td>           <td>             <input type="text" name="forumName"              maxlength="63" value="{{ForumName}}" />           </td>         </tr>         <tr>           <td>             Forum Description:           </td>           <td>             <textarea cols="50" rows="10" wrap="soft"              >               {{ForumDescription}}             </textarea>           </td>         </tr>       </table>       <input type="submit" />       <a href="forumlist.srf">Return to Forum List</a>     </form>     {{else}}     <p><b>You have given an invalid forum ID.       Shame on you!</b></p>     {{endif}}   </body> </html> 

Here we're using both the standard ways to do input in HTML: The browser query string contains the forum ID that we're editing, and the post variables contain the new text and descriptions. ATL Server provides access to both of these via the m_HttpRequest object. This object is of the class CHttpRequest and provides a variety of ways to get access to server, query string, and form variables:

class CHttpRequest : public IHttpRequestLookup {                  public:                                                             // Access to Query String parameters as a collection              const CHttpRequestParams& GetQueryParams() const;                 // Access to Query String parameters via an iterator              POSITION GetFirstQueryParam(LPCSTR *ppszName,                         LPCSTR *ppszValue);                                           POSITION GetNextQueryParam(POSITION pos,                              LPCSTR *ppszName, LPCSTR *ppszValue);                         // Get the entire raw query string                                LPCSTR GetQueryString();                                          // Access to form variables as a collection                       const CHttpRequestParams& GetFormVars() const;                    // Access to form variables via an iterator                       POSITION GetFirstFormVar(LPCSTR *ppszName,                            LPCSTR *ppszValue);                                           POSITION GetNextFormVar(POSITION pos,                                 LPCSTR *ppszName, LPCSTR *ppszValue);                         // Access to uploaded files                                       POSITION GetFirstFile(LPCSTR *ppszName,                               IHttpFile **ppFile);                                          POSITION GetNextFile(POSITION pos,                                    LPCSTR *ppszName, IHttpFile **ppFile);                        // Get all cookies as a string                                    BOOL GetCookies(LPSTR szBuf,LPDWORD pdwSize);                     BOOL GetCookies(CStringA& strBuff);                               // Get a single cookie by name                                    const CCookie& Cookies(LPCSTR szName);                            // Access cookies via an iterator                                 POSITION GetFirstCookie(LPCSTR *ppszName,                             const CCookie **ppCookie);                                    POSITION GetNextCookie(POSITION pos,                                  LPCSTR *ppszName, const CCookie **ppCookie);                  // Get the session cookie                                         const CCookie& GetSessionCookie();                                // Get the HTTP method used for this request                      LPCSTR GetMethodString();                                         HTTP_METHOD GetMethod();                                          // Access to various server variables and HTTP Headers            LPCSTR GetContentType();                                          BOOL GetAuthUserName(LPSTR szBuff, DWORD *pdwSize);               BOOL GetAuthUserName(CStringA &str);                              BOOL GetPhysicalPath(LPSTR szBuff, DWORD *pdwSize);               BOOL GetPhysicalPath(CStringA &str);                              BOOL GetAuthUserPassword(LPSTR szBuff, DWORD *pdwSize);           BOOL GetAuthUserPassword(CStringA &str);                          BOOL GetUrl(LPSTR szBuff, DWORD *pdwSize);                        BOOL GetUrl(CStringA &str);                                       BOOL GetUserHostName(LPSTR szBuff, DWORD *pdwSize);               BOOL GetUserHostName(CStringA &str);                              BOOL GetUserHostAddress(LPSTR szBuff, DWORD *pdwSize);            BOOL GetUserHostAddress(CStringA &str);                           LPCSTR GetScriptPathTranslated();                                 LPCSTR GetPathTranslated();                                       LPCSTR GetPathInfo();                                             BOOL GetAuthenticated();                                          BOOL GetAuthenticationType(LPSTR szBuff, DWORD *pdwSize);         BOOL GetAuthenticationType(CStringA &str);                        BOOL GetUserName(LPSTR szBuff, DWORD *pdwSize);                   BOOL GetUserName(CStringA &str);                                  BOOL GetUserAgent(LPSTR szBuff, DWORD *pdwSize);                  BOOL GetUserAgent(CStringA &str);                                 BOOL GetUserLanguages(LPSTR szBuff, DWORD *pdwSize);              BOOL GetUserLanguages(CStringA &str);                             BOOL GetAcceptTypes(LPSTR szBuff,DWORD *pdwSize);                 BOOL GetAcceptTypes(CStringA &str);                               BOOL GetAcceptEncodings(LPSTR szBuff, DWORD *pdwSize);            BOOL GetAcceptEncodings(CStringA& str);                           BOOL GetUrlReferer(LPSTR szBuff, DWORD *pdwSize);                 BOOL GetUrlReferer(CStringA &str);                                BOOL GetScriptName(LPSTR szBuff, DWORD *pdwSize);                 BOOL GetScriptName(CStringA &str);                                // Raw access to server variables                                 BOOL GetServerVariable(LPCSTR szVariable, CStringA &str) const; }; // class CHttpRequest                                          

For methods that return strings (that is, almost all of them), there are two overloads. The first one is the traditional "pass in a buffer and a DWORD containing the buffer length" style used so often in the Win32 API. The second overload lets you pass in a CStringA reference and stores the resulting string in the CString. The latter overload is much more convenient; the former gives you complete control over memory allocation if you need it for performance.

The query string and form variable access methods give you a variety of ways to get at the contents of these two collections of variables. For query strings, the easiest way to work if you know what query strings you're expecting is to use the GetQueryParams() method. This returns a reference to a CHttpRequestParams object. This object basically maps name/value pairs and is used to access the contents of the query strings. Usage is quite simple:

const CHttpRequestParams& queryParams =     m_HttpRequest.GetQueryParams( ); CStringA cstrForumId = queryParams.Lookup( "forumid" ); 

If the query parameter you're looking for isn't present, you get back an empty string.

The CHttpRequestParams object also supports an iterator interface to walk the list of name/value pairs in the collection. Unfortunately, this is an MFC-style iterator rather than a standard C++ iterator. Here's an example that walks the list of form variables submitted in a post:

HTTP_CODE EditForumHandler::OnFormFields( ) {   if( m_HttpRequest.GetMethod( ) ==     CHttpRequest::HTTP_METHOD_POST ) {     const CHttpRequestParams &formFields =       m_HttpRequest.GetFormVars( );     POSITION pos = formFields.GetStartPosition( );     m_HttpResponse << "Form fields:<br>" << "<ul>";     const CHttpRequestParams::CPair *pField;     for( pField = formFields.GetNext( pos );       pField != 0;       pField = formFields.GetNext( pos ) ) {       m_HttpResponse << "<li>" << pField->m_key <<         ": " << pField->m_value << "</li>";     }     m_HttpResponse << "</ul>";   }   return HTTP_SUCCESS; } 

To use the iterator interface, you call the GetStartPosition( ) method on the collection to get back a POSITION object. This acts as a pointer into the collection and is initialized to one before the first element in the collection. The GetNext( ) method increments the POSITION to point to the next item in the collection and returns a pointer to the object at the new POSITION. When you get to the end, GetNext( ) returns 0.

Because the CHttpRequestParams class stores name/value pairs, it makes sense that the GetNext() call returns a CPair object; this is a nested type defined within the map class. It has two fields: m_key and m_value, which should be self-explanatory.

It's up to you to choose which way to access your inputs. The Lookup method is much more convenient when you know in advance what form fields or query string parameters you're expecting. The iterator versions are useful if you can have a wide variety of inputs and don't know in advance what you're going to get (for example, some blog systems enable you to pass a variety of different parameters to bring up a single post, all posts in a month, or posts from a start/end date).

One thing to consider is what to do about parameters you don't expect and don't support. The easiest thing to do is simply ignore them. However, if somebody is sending you unexpected junk, it might be somebody trying to hack your system, so you might want to at least loop through the query string and form variables to check if there's anything in there you don't expect. Your response to these values is up to you: This could range from ignoring them to logging the invalid parameters or failing the request outright.

Data Exchange and Validation

So we have easy access to our query string and form variables, but that access is less than convenient. We need to check for empty strings when calling the Lookup() method to verify that the variable exists at all. We need to do data type conversions: In our example, the forum ID is an integer, but in the query string it's stored as a string. And when we've got the value, we need to do validation on it: Faulty input validation is the single biggest security flaw in web sites today.[5]

[5] See www.owasp.org/documentation/topten.html (http://tinysells.com/57).

ATL Server includes some common validation and data-conversion functions to make life easier for the web developer. This is implemented via the CValidateObject< > template and the CValidateContext class.

The CValidateObject< > template is designed to be used as a base class; the CHttpRequestParams class derives from CValidateObject< >. It provides numerous overloads of two methods: Exchange and Validate:

template <class TLookupClass, class TValidator = CAtlValidator> class CValidateObject {                                         public:                                                            template <class T>                                               DWORD Exchange(                                                     LPCSTR szParam,                                                 T* pValue,                                                      CValidateContext *pContext = NULL) const;                   template<>                                                      DWORD Exchange(                                                     LPCSTR szParam,                                                 CString* pstrValue,                                             CValidateContext *pContext) const;                          template<>                                                      DWORD Exchange(                                                     LPCSTR szParam,                                                 LPCSTR* ppszValue,                                              CValidateContext *pContext) const;                          template<>                                                      DWORD Exchange(                                                     LPCSTR szParam,                                                 GUID* pValue,                                                   CValidateContext *pContext) const;                          template<>                                                      DWORD Exchange(                                                     LPCSTR szParam,                                                 bool* pbValue,                                                  CValidateContext *pContext) const;                          template <class T, class TCompType>                             DWORD Validate(                                                     LPCSTR Param,                                                   T *pValue,                                                      TCompType nMinValue,                                            TCompType nMaxValue,                                            CValidateContext *pContext = NULL) const;                   template<>                                                      DWORD Validate(                                                     LPCSTR Param,                                                   LPCSTR* ppszValue,                                              int nMinChars,                                                  int nMaxChars,                                                  CValidateContext *pContext) const;                          template<>                                                      DWORD Validate(                                                     LPCSTR Param,                                                   CString* pstrValue,                                             int nMinChars,                                                  int nMaxChars,                                                  CValidateContext *pContext) const;                          template<>                                                      DWORD Validate(                                                     LPCSTR Param,                                                   double* pdblValue,                                              double dblMinValue,                                             double dblMaxValue,                                             CValidateContext *pContext) const;                      };                                                              

The Exchange( ) method takes in the name of a variable. If that variable exists in the collection you're using, it converts the string to the correct type (based on the type T you use) and stores the result in the requested pointer. The return value tells you whether the parameter was present:

HTTP_CODE ValidateAndExchange( ) {   ...   int forumId;   m_HttpRequest.GetQueryParams().Exchange( "forumid",     &forumId, NULL );   ... } 

Thanks to the wonder of template type inference, by passing in the address of a variable of type int, the Exchange method knows that I want the string converted to type int. The Exchange( ) method properly works with these types: ULONGLONG, LONGLONG, double, int, unsigned int, long, unsigned long, short, and unsigned short. In addition, there are specializations for CString and LPCSTR, GUID, and bool.

This is a convenient way to check whether a parameter exists, copy it, and do data conversion all in one fell swoop. But that's usually not enough. You generally need to do more checking than "Is it an int?" The Validate( ) method and the various overloads give you some more checking. Specifically, when working with a numeric value, Validate lets you check that a parameter is within a particular numeric range. When validating strings, the Validate method can check for minimum and maximum string lengths (very helpful to avoid buffer overflows). For example, here's some code from the ValidateAndExchange method that checks the results of our form post:

HTTP_CODE ValidateAndExchange( ) {   ...   if( m_HttpRequest.GetMethod( ) ==     CHttpRequest::HTTP_METHOD_POST ) {     const CHttpRequestParams& formFields =       m_HttpRequest.GetFormVars( );     formFields.Validate( "forumName", &m_forumName,       1, 50, &m_validationContext );     formFields.Validate( "forumDescription",       &m_forumDescription, 1, 255, &m_validationContext );   }   ... } 

Notice that I'm not actually checking the return values from the Validate method. That's one way to get the results of the Validate call, but having to do this repeatedly for every field gets tedious (and hard to maintain) quickly:

if( VALIDATION_SUCCEEDED( formFields.Validate(    "forumName", &m_forumName, 1, 50, &m_validationContext ) ) { ... } 

Instead, we take advantage of another class: CValidateContext. The last parameter for the Exchange() and Validate() methods is an optional pointer to a CValidateContext object. This object acts as a collectionspecifically, a collection of validation errors. If the Exchange() or Validate() call fails, an entry in the CValidateContext object is made. Using the validation context, you can do all your validation checks and not have to worry about the results until the end.

The easiest thing to do is check whether there were any validation failures, via the ParamsOK() method on the CValidateContext object. You can also walk the list of errors, like this:

HTTP_CODE EditForumHandler::OnValidationErrors( ) {   if( m_validationContext.ParamsOK( ) ) {     m_HttpResponse << "No validation errors occurred";   }   else {     int numValidationFailures =       m_validationContext.GetResultCount( );     m_HttpResponse << "<ol>";     for( int i = 0; i < numValidationFailures; ++i ) {       CStringA faultName;       DWORD faultCode;       m_validationContext.GetResultAt( i, faultName,         faultCode );       m_HttpResponse << "<li>" << faultName << ": " <<         faultCode << "</li>";     }     m_HttpResponse << "</ol>";   }   return HTTP_SUCCESS; } 

Here we're just printing the fault codes as integers. These are the possible fault codes:

  • VALIDATION_S_OK. The named value was found and could be converted successfully.

  • VALIDATION_S_EMPTY. The name was present, but the value was empty.

  • VALIDATION_E_PARAMNOTFOUND. The named value was not found.

  • VALIDATION_E_INVALIDPARAM. The name was present, but the value could not be converted to the requested data type.

  • VALIDATION_E_LENGTHMIN. The name was present and could be converted to the requested data type, but the value was too small.

  • VALIDATION_E_LENGTHMAX. The name was present and could be converted to the requested data type, but the value was too large.

  • VALIDATION_E_FAIL. An unspecified error occurred.

It would have been nice if these were just custom hrESULT values, but, unfortunately, they're not. Luckily, there's also a VALIDATION_SUCCEEDED macro that tells you whether a particular error code is a success.

When validation for a particular variable fails, the Validate (or Exchange) method adds a name/value pair to the validation context. The name is the name of the variable that failed. The value is the fault code. These can be retrieved using the GetresultAt method, as shown earlier. You are also free to add your own error records to the validation context via the AddResult method. For example, we use the Exchange method to find out whether there's a forumid, but we still need to see if it's valid:

void EditForumHandler::ValidateLegalForumId( ){   if( m_forumId != -1 ) {     if( SUCCEEDED( m_forumList.ReadOneForum(       m_forumId, &m_forumRecordset ) ) ) {       bool containsData;       if( SUCCEEDED( m_forumList.ContainsForumData(         m_forumRecordset, &containsData ) ) ) {         if( !containsData ) {             m_validationContext.AddResult(               "forumid", VALIDATION_E_FAIL );             m_forumId = -1;         }       }     }   } } 

In this case, I'm using a generic VALIDATION_E_FAIL code, but there's no reason you can't make up your own DWORD error-validation codes.

If you have multiple records with the same name, only the last one in is recorded. So, if you check the same value multiple times, as we do with forumid, be aware that later validation failures could overwrite earlier records in the context.

The CValidateContext class gives you several options when adding records to the collection:

class CValidateContext {                               public:                                                  enum { ATL_EMPTY_PARAMS_ARE_FAILURES = 0x00000001 };   CValidateContext(DWORD dwFlags=0);                     bool AddResult(LPCSTR szName, DWORD type,                bool bOnlyFailures = true);                          ...                                                  };                                                     

When constructing the CValidateContext object, by default, empty parameters (ones that were in the request but have no data) are not considered an error by the CValidateContext. If you specify the ATL_EMPTY_PARAMS_ARE_FAILURES flag when constructing the context, empty parameters are treated as errors. In addition, you can pass a third, optional parameter to the AddResult method. If true (the default), the context ignores records that have the fault code VALIDATION_S_OK or VALIDATION_S_EMPTY (although the latter is ignored only if empty parameters are not errors). This optional parameter is useful when you call AddResult yourself; Validate and Exchange never pass false for this parameter.

When validation fails, you generally want to display something to the user. Nothing is built into ATL Server, but it's easy enough to display errors on your own. Here's the .srf file for my "edit forum" page:

<html> {{handler SimpleForums.dll/EditForum}}   <head>     <title>Edit Forum</title>   </head>   <body>     <h1>Edit Forum Information</h1>     {{if ValidForumId}}     <form action="editforum.srf?forumid={{ForumId}}"       method="post">       <table border="0" cellpadding="0">         <tr>           <td>             Forum Name:           </td>           <td>             <input type="text" name="forumName"                maxlength="63"               value="{{ForumName}}" />           </td>         </tr>         <tr>         <td>           Forum Description:         </td>         <td>           <textarea cols="50" rows="10"             wrap="soft" name="forumDescription"             >             {{ForumDescription}}           </textarea>         </td>         </tr>       </table>       <input type="submit" />       <a href="forumlist.srf">Return to Forum List</a>     </form>     {{else}}     <p><b>You have given an invalid forum ID. Shame on you!</b>     {{endif}}     {{FormFields}}     {{ValidationErrors}}   </body> </html> 

The ValidationErrors substitution is handled by the OnValidationErrors method, which walks the validation context and outputs both the fields that have errors and the error code:

HTTP_CODE EditForumHandler::OnValidationErrors( ) {   if( m_validationContext.ParamsOK( ) ) {     m_HttpResponse << "No validation errors occurred";   }   else {     m_HttpResponse << "Validation Errors:";     int numValidationFailures =       m_validationContext.GetResultCount( );     m_HttpResponse << "<ol>";     for( int i = 0; i < numValidationFailures; ++i ) {       CStringA faultName;       DWORD faultCode;       m_validationContext.GetResultAt( i, faultName,         faultCode );       m_HttpResponse << "<li>" << faultName <<         ": " << FaultCodeToString(faultCode) << "</li>";     }     m_HttpResponse << "</ol>";   }   return HTTP_SUCCESS; } CStringA EditForumHandler::FaultCodeToString(DWORD faultCode) {   switch(faultCode) {     case VALIDATION_S_OK:       return "Validation succeeded";     case VALIDATION_S_EMPTY:       return "Name present but contents were empty";     case VALIDATION_E_PARAMNOTFOUND:       return "The named value was not found";     case VALIDATION_E_LENGTHMIN:       return "Value was present and converted, but too small";     case VALIDATION_E_LENGTHMAX:       return "Value was present and converted, but too large";     case VALIDATION_E_INVALIDLENGTH:       return "(Unused error code)";     case VALIDATION_E_INVALIDPARAM:       return "The value was present but could not be "         "converted to the given data type";     case VALIDATION_E_FAIL:       return "Validation failed";     default:       return "Unknown validation failure code";   } } 

This code simply walks through the validation context and displays the names of the failures (usually the field names) and the failure code, converted to a string. Figure 14.2 shows the results of validation failures. The fields in question weren't long enough to pass validation (because they need to be at least 1 character).

Figure 14.2. Results of validation failure

A small bug in the validation functions makes the ATL_EMPTY_PARAMS_ARE_FAILURES flag essential. The problem comes in when you have a post variable with an empty string. For example, Figure 14.3 shows our forum edit form; I cleared the forum name before clicking Submit.

Figure 14.3. Edit Forum page with no forum name

When I click the Submit Query button, the forumName text field gets sent back in the HTTP post, but with no value. In the ValidateAndExchange method, we make use of ATL Server's validation functions to check our input:

HTTP_CODE EditForumHandler::ValidatePost( ) {   ...   if( m_HttpRequest.GetMethod( ) ==     CHttpRequest::HTTP_METHOD_POST ) {     const CHttpRequestParams& formFields =       m_HttpRequest.GetFormVars( );     formFields.Validate( "forumName", &m_forumName,       1, 50, &m_validationContext );   }   return HTTP_SUCCESS; } 

The intention here is to require that the forumName variable exists and that it be from 1 to 50 characters in length. If we check the ParamsOK variable, it correctly returns false: The forumName variable is not within 1 and 50 characters in length. However, if we walk the list of errors in the validation context, there will be no record for the forumName field. What's going on here?

Let's take a look at the code for CValidateObject::Validate for strings:

template<>                                                        DWORD Validate(                                                     LPCSTR Param,                                                     LPCSTR* ppszValue,                                                int nMinChars,                                                    int nMaxChars,                                                    CValidateContext *pContext) const {                               LPCSTR pszValue = NULL;                                           DWORD dwRet = Exchange(Param, &pszValue, pContext);               if (dwRet == VALIDATION_S_OK ) {                                    if (ppszValue)                                                      *ppszValue = pszValue;                                          dwRet = TValidator::Validate(pszValue, nMinChars, nMaxChars);     if (pContext && dwRet != VALIDATION_S_OK)                           pContext->AddResult(Param, dwRet);                            }                                                                 else if (dwRet == VALIDATION_S_EMPTY && nMinChars > 0) {            dwRet = VALIDATION_E_LENGTHMIN;                                   if (pContext) {                                                     pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);           }                                                               }                                                                 return dwRet;                                                   }                                                                 

The two lines in bold are where the record is added to the validation context. Note that the first one calls the AddResult method. This is where we check for validation failures. Notice the second one: This code executes if the validation result is VALIDATION_S_EMPTY, and there's a minimum character length on the string. In this case, it calls the SetResultAt method on the validation context instead, using the name of the parameter.

Here's where the bug comes in. Let's look at the SetResultAt implementation:

class CValidateContext {                                  public:                                                     bool SetResultAt(__in LPCSTR szName, __in DWORD type) {     _ATLTRY {                                                   if (!VALIDATION_SUCCEEDED(type) ||                          (type == VALIDATION_S_EMPTY &&                              (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES))) {         m_bFailures = true;                                     }                                                         return TRUE == m_results.SetAt(szName,type);            }                                                         _ATLCATCHALL() { }                                        return false;                                           }                                                         // Returns true if there are no validation failures       // in the collection, returns false otherwise.            __checkReturn bool ParamsOK() {                           return !m_bFailures;                                      }                                                       protected:                                                  CSimpleMap<CStringA, DWORD> m_results;                    bool m_bFailures;                                       }; // CValidateContext                                    

The SetResultAt call sets the m_bFailures flag, which is used by the ParamsOK method, and then calls m_results.SetAt. And here's the source of the problem: CSimpleMap::SetAt sets the value only if the name you're using is already in the map. If the key isn't in the map, SetAt silently fails.

So what happens here is that, because an empty parameter isn't an error by default, it doesn't get added to the context in the AddResult call. Then, when the minimum-length validation fails, the call to SetResultAt TRies to add using the SetAt call. But that fails because the parameter isn't already in the m_results map. As a result, the m_bFailures flag is set, but there's no actual record of the specific failure.

You can work around this bug in two ways. The first is to set the ATL_EMPTY_PARAMS_ARE_FAILURES flag when you create your validation-context object. This is best if you absolutely must have a value in the parameter in question. The other option is best used if the parameter is actually optional. In this case, be sure to set the minimum length in the Validate call to 0 instead of 1, as I did earlier.

Regular Expressions

Dealing with numeric values is made quite easy by the Validate() method, but for strings, you often need to do a lot more than just check for the maximum length. It's good security practice to enforce that your input contains only a known set of good characters, for example. Or what if you need to receive dates in a particular format? None of the Validate overrides helps you there.

The typical tool used in these kinds of string validation is the regular expression. UNIX programmers have been using them for years; one could argue that the popularity of the Perl programming language is mainly because of the ease of regular expression matching. Luckily, ATL Server provides a regular expression engine that we can use from the comfort of good old C++.

Unfortunately, a discussion of regular expression syntax and how to use regular expressions is beyond the scope of this book; see the documentation for details.[6]

[6] The standard text on regular expressions is Mastering Regular Expressions (O'Reilly Publishing, 2002), by Jeffrey E. F. Friedl.

Regular expressions are done in ATL Server via the CAtlRegExp class:

template <class CharTraits /* =CAtlRECharTraits */> class CAtlRegExp {                                  public:                                               CAtlRegExp();                                       typedef typename CharTraits::RECHARTYPE RECHAR;     REParseError Parse(const RECHAR *szRE,                BOOL bCaseSensitive=TRUE);                        BOOL Match(const RECHAR *szIn,                        CAtlREMatchContext<CharTraits> *pContext,           const RECHAR **ppszEnd=NULL);                   };                                                  

The usage is fairly simple. For example, suppose we wanted to ensure that the forum name contains only alphabetical characters, spaces, and commas. The following does the trick:

void EditForumHandler::ValidateLegalForumName( ) {   CAtlRegExp< CAtlRECharTraitsW > re;   CAtlREMatchContext< CAtlRECharTraitsW > match;   ATLVERIFY( re.Parse( L"^[a-zA-Z,]*$" ) ==     REPARSE_ERROR_OK );   if( !re.Match( m_forumName.GetBuffer( ), &match ) ) {     m_validationContext.AddResult( "forumName",       VALIDATION_E_FAIL );   } } 

First, you create the CAtlRegExp object. The template parameter is a traits class that defines various properties of the character set that the regular expression engine will be searching. ATL defines three of these traits classes: CAtlRECharTraitsA (for ANSI characters), CAtlRECharTraitsMB (for multibyte strings) and CAtlRECharTraitsW (for wide character strings). These traits classes are used much like the traits classes are in the CString class as discussed in Chapter 2, "Strings and Text."

After you've created the regex object, you need to feed in a regular expression by calling the Parse method. This method returns a value of type REParseError. REPARSE_ERROR_OK means that everything was fine; any other return code indicates a syntax error in the regular express. The documentation for CAtlRegExp::Parse gives the complete list of possible error codes.

Next, you create an object of type CAtlREMatchContext, which takes the same character traits template parameter as the regexp object did. Then, you call the Match method on the regular expression object, passing in the string to search and the match context object. Match returns true if the regular expression matched the string, and false if it did not. In some cases, this is all we need to know. In others, we might want to know more about what specifically matched. This information is stored in the match context object. The documentation and sample code give many examples on how to use the match context and more information about what you can do with regular expressions.

ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net