views:

178

answers:

2

We have a C++/MFC application which allows users to customize date formatting via configuration files. Not wanting to reinvent the wheel, I pass the format string to CTime::Format("< format string >") to do the actual formatting. Under the covers, Format calls a variant of the standard C function strftime().

Naturally, the user can accidentally enter an invalid format string. (For example, "%s" instead of "%S".) When this happens, the C Run-Time calls the Invalid Argument Handler which, by default, exits the app. (No exceptions to catch-- just app exit.)

My question is how to gracefully handle this untrusted input. In theory, I could write my own parser/validator for the format string, but this sounded like a waste of time. Instead, the best I could come up with was to set my own (global) invalid argument handler which, instead of exiting, throws an Invalid Argument exception:

void MyInvalidParameterHandler(
    const wchar_t* expression,
    const wchar_t* function, 
    const wchar_t* file, 
    unsigned int line, 
    uintptr_t pReserved)
{
    ::AfxThrowInvalidArgException();
}

This does seem to work, and allows my to explicitly catch (and gracefully handle) the invalid argument exceptions in the cases where I "expect" them to occur. I am concerned, however, that I am overriding a global, run-time setting in a large application in order to solve a relatively "local" problem-- I would hate for this fix to cause additional problems elsewhere.

Is this approach sensible? Or is there a cleaner approach solving this problem?

+1  A: 

If you are only interested in catching this error at certain times, you could temporarily replace the invalid parameter handler and then set it back once you have called Format.

_invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(MyInvalidParameterHandler);

// Your try/Format/catch code here

_set_invalid_parameter_handler(oldHandler);

Of course, I suppose it is possible that if you have multiple threads in your program, another thread could end up calling your invalid parameter handler while it's set. You would have to determine how likely that is.

Other than writing your own validation function, I'm not sure how else you could do this.

Dustin
Thank you for the suggestion. Unfortunately, my app is indeed multi-threaded, so I don't think I can safely toggle the handler back and forth. (The invalid parameter handler is global, rather than thread-specific.)
Eric Pi
In that case, I believe that you will need to create your own validation function. It shouldn't be that hard. If you look in strftime.c in the CRT source code you will see a function called _expandtime. It contains a case statement for all of the supported format specifiers. You could use it as a basis for your function.
Dustin
One possibility is to add your own invalid parameter handler that does nothing. The docs state that if control returns to the calling function it will return an error code.
Dustin
I have validated that this does indeed work. In Debug builds you will still get break points due to assertions but in Release builds strftime will return 0 and errno will contain EINVAL.
Dustin
Thanks, Dustin. I think a global handler that either does nothing (requiring callers to check errno) or throws an exception (requiring callers to catch) are both acceptable solutions.
Eric Pi
A: 

How about this verification:

 if (strftime(buffer, N, fmt_string, dummytime)) {
    // format string is ok, continue ...
 }

I assume that CTime::Format and strftime use the same format specifiers.

Nick D
I don't think that will work as strftime will call the invalid parameter handler as described above if the format string is invalid. It suffers from the same issue as calling Format with a bad format string.
Dustin
Thanks for the suggestion. However, under the covers, CTime::Format does indeed call (a variant of) strftime. It's actually strftime() itself which calls the invalid parameter handler. Therefore, the 'if' test in your example would (by default) exit the app, rather than evaluate false when given an invalid fmt_string.
Eric Pi
@Dustin, @Eric: `strftime` returns `0` on failure. I tried it with wrong specifiers and it worked.
Nick D
@Nick D: Which compiler / OS are you using? I just tried it with Visual Studio 2005 and it does indeed die/exit. I guess the real issue is that the behavior is undefined by the C standard.
Eric Pi
@Nick D: I tried with VS2008 and it crashes in both Debug and Release.
Dustin
@Dustin, @Eric: I tested it on VC++ 6.0 (gulp!) But this is a *weird* behavior for a format function, nevertheless.
Nick D