mirror of https://github.com/Icinga/icinga2.git
Utility::FormatDateTime(): handle invalid format strings on Windows
On Windows, the strftime() function family invokes an invalid parameter handler when the format string is invalid (see the "Remarks" section in their documentation). std::put_time() shows the same behavior as it uses _wcsftime_l() internally. The default invalid parameter handler may terminate the process, which can be a problem given that the format string can be specified by the user from the Icinga DSL. Thus, temporarily set a thread-local no-op handler to disable the default one allowing the program to continue. This then simply results in the function returning an error which then results in an exception as we ask the stream to throw one. See also: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strftime-wcsftime-strftime-l-wcsftime-l?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/parameter-validation?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170
This commit is contained in:
parent
0285028689
commit
d5b3ffaa6d
|
@ -4,6 +4,7 @@
|
|||
#include "base/utility.hpp"
|
||||
#include "base/convert.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/defer.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include "base/socket.hpp"
|
||||
|
@ -1092,6 +1093,32 @@ String Utility::FormatDateTime(const char *format, double ts)
|
|||
}
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* On Windows, the strftime() function family invokes an invalid parameter handler when the format string is
|
||||
* invalid (see the "Remarks" section in their documentation). std::put_time() shows the same behavior as it
|
||||
* uses _wcsftime_l() internally. The default invalid parameter handler may terminate the process, which can
|
||||
* be a problem given that the format string can be specified by the user from the Icinga DSL.
|
||||
*
|
||||
* Thus, temporarily set a thread-local no-op handler to disable the default one allowing the program to
|
||||
* continue. This then simply results in the function returning an error which then results in an exception as
|
||||
* we ask the stream to throw one.
|
||||
*
|
||||
* See also:
|
||||
* https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strftime-wcsftime-strftime-l-wcsftime-l?view=msvc-170
|
||||
* https://learn.microsoft.com/en-us/cpp/c-runtime-library/parameter-validation?view=msvc-170
|
||||
* https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170
|
||||
*/
|
||||
|
||||
auto oldHandler = _set_thread_local_invalid_parameter_handler(
|
||||
[](const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, uintptr_t) {
|
||||
// Intentionally do nothing to continue executing.
|
||||
});
|
||||
|
||||
Defer resetHandler([oldHandler]() {
|
||||
_set_thread_local_invalid_parameter_handler(oldHandler);
|
||||
});
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
char buf[128];
|
||||
size_t n = strftime(buf, sizeof(buf), format, &tmthen);
|
||||
// On error, n == 0 and an empty string is returned.
|
||||
|
|
|
@ -191,6 +191,19 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) {
|
|||
// not really possible due to limitations in strftime() error handling, see comment in the implementation.
|
||||
BOOST_CHECK_EQUAL("", Utility::FormatDateTime(repeat("%Y", 1000).c_str(), ts));
|
||||
|
||||
// Invalid format strings.
|
||||
for (const char* format : {"%", "x % y", "x %! y"}) {
|
||||
std::string result = Utility::FormatDateTime(format, ts);
|
||||
|
||||
// Implementations of strftime() seem to either keep invalid format specifiers and return them in the output, or
|
||||
// treat them as an error which our implementation currently maps to the empty string due to strftime() not
|
||||
// properly reporting errors. If this limitation of our implementation is lifted, other behavior like throwing
|
||||
// an exception would also be valid.
|
||||
BOOST_CHECK_MESSAGE(result.empty() || result == format,
|
||||
"FormatDateTime(" << std::quoted(format) << ", " << ts << ") = " << std::quoted(result) <<
|
||||
" should be one of [\"\", " << std::quoted(format) << "]");
|
||||
}
|
||||
|
||||
// Out of range timestamps.
|
||||
BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), -double_limit::infinity())), negative_overflow);
|
||||
BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), +double_limit::infinity())), positive_overflow);
|
||||
|
|
Loading…
Reference in New Issue