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" id="forumName"
maxlength="63" value="{{ForumName}}" />
</td>
</tr>
<tr>
<td>
Forum Description:
</td>
<td>
<textarea cols="50" rows="10" wrap="soft"
id="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></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.
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"
id="forumName" maxlength="63"
value="{{ForumName}}" />
</td>
</tr>
<tr>
<td>
Forum Description:
</td>
<td>
<textarea cols="50" rows="10"
wrap="soft" name="forumDescription"
id="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).
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.
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.
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.
|