From 518e6d4ae2446e60b93753e05981f42e8b20e025 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Wed, 15 Dec 2021 00:31:41 +0900 Subject: [PATCH 1/2] feat: check date and time are valid or not --- toml/parser.hpp | 64 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/toml/parser.hpp b/toml/parser.hpp index a060114..518f87e 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -736,11 +736,28 @@ parse_local_date(location& loc) const auto month = static_cast(from_string(m.unwrap().str(), 0)); const auto day = static_cast(from_string(d.unwrap().str(), 0)); - // this could be improved a bit more, but is it ... really ... needed? - if(31 < day) + // We briefly check whether the input date is valid or not. But here, we + // only check if the RFC3339 compliance. + // Actually there are several special date that does not exist, + // because of historical reasons, such as 1582/10/5-1582/10/14 (only in + // several countries). But here, we do not care about such a complicated + // rule. It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in a different layer. { - throw syntax_error(format_underline("toml::parse_date: invalid date", - {{source_location(loc), "here"}}), source_location(loc)); + const bool is_leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = (month == 2) ? (is_leap ? 29 : 28) : + ((month == 4 || month == 6 || month == 9 || month == 11) ? 30 : 31); + + if((month < 1 || 12 < month) || (day < 1 || max_day < day)) + { + throw syntax_error(format_underline("toml::parse_date: " + "invalid date: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } } return ok(std::make_pair(local_date(year, static_cast(month - 1), day), token.unwrap())); @@ -787,10 +804,22 @@ parse_local_time(location& loc) {{source_location(inner_loc), "here"}}), source_location(inner_loc)); } - local_time time( - from_string(h.unwrap().str(), 0), - from_string(m.unwrap().str(), 0), - from_string(s.unwrap().str(), 0), 0, 0); + + const int hour = from_string(h.unwrap().str(), 0); + const int minute = from_string(m.unwrap().str(), 0); + const int second = from_string(s.unwrap().str(), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute) || + (second < 0 || 60 < second)) // it may be leap second + { + throw syntax_error(format_underline("toml::parse_time: " + "invalid time: it does not conform RFC3339.", {{ + source_location(loc), "hour should be 00-23, minute should be" + " 00-59, second should be 00-60 (depending on the leap" + " second rules.)"}}), source_location(inner_loc)); + } + + local_time time(hour, minute, second, 0, 0); const auto before_secfrac = inner_loc.iter(); if(const auto secfrac = lex_time_secfrac::invoke(inner_loc)) @@ -904,15 +933,26 @@ parse_offset_datetime(location& loc) if(const auto ofs = lex_time_numoffset::invoke(inner_loc)) { const auto str = ofs.unwrap().str(); + + const auto hour = from_string(str.substr(1,2), 0); + const auto minute = from_string(str.substr(4,2), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute)) + { + throw syntax_error(format_underline("toml::parse_offset_datetime: " + "invalid offset: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } + if(str.front() == '+') { - offset = time_offset(from_string(str.substr(1,2), 0), - from_string(str.substr(4,2), 0)); + offset = time_offset(hour, minute); } else { - offset = time_offset(-from_string(str.substr(1,2), 0), - -from_string(str.substr(4,2), 0)); + offset = time_offset(-hour, -minute); } } else if(*inner_loc.iter() != 'Z' && *inner_loc.iter() != 'z') From 23f6c931e50f6e902cc55f926840c1df4ccbb6b0 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Wed, 15 Dec 2021 00:51:07 +0900 Subject: [PATCH 2/2] test: add valid/invalid datetime cases --- tests/test_parse_datetime.cpp | 126 ++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) diff --git a/tests/test_parse_datetime.cpp b/tests/test_parse_datetime.cpp index 832aa06..e7247b0 100644 --- a/tests/test_parse_datetime.cpp +++ b/tests/test_parse_datetime.cpp @@ -17,6 +17,11 @@ BOOST_AUTO_TEST_CASE(test_time) TOML11_TEST_PARSE_EQUAL(parse_local_time, "07:32:00.99", toml::local_time(7, 32, 0, 990, 0)); TOML11_TEST_PARSE_EQUAL(parse_local_time, "07:32:00.999", toml::local_time(7, 32, 0, 999, 0)); TOML11_TEST_PARSE_EQUAL(parse_local_time, "07:32:00.999999", toml::local_time(7, 32, 0, 999, 999)); + + + TOML11_TEST_PARSE_EQUAL(parse_local_time, "00:00:00.000000", toml::local_time( 0, 0, 0, 0, 0)); + TOML11_TEST_PARSE_EQUAL(parse_local_time, "23:59:59.999999", toml::local_time(23, 59, 59, 999, 999)); + TOML11_TEST_PARSE_EQUAL(parse_local_time, "23:59:60.999999", toml::local_time(23, 59, 60, 999, 999)); // leap second } BOOST_AUTO_TEST_CASE(test_time_value) @@ -25,17 +30,125 @@ BOOST_AUTO_TEST_CASE(test_time_value) TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "07:32:00.99", toml::value(toml::local_time(7, 32, 0, 990, 0))); TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "07:32:00.999", toml::value(toml::local_time(7, 32, 0, 999, 0))); TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "07:32:00.999999", toml::value(toml::local_time(7, 32, 0, 999, 999))); + + TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "00:00:00.000000", toml::value(toml::local_time( 0, 0, 0, 0, 0))); + TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "23:59:59.999999", toml::value(toml::local_time(23, 59, 59, 999, 999))); + + std::istringstream stream1(std::string("invalid-datetime = 24:00:00")); + std::istringstream stream2(std::string("invalid-datetime = 00:60:00")); + std::istringstream stream3(std::string("invalid-datetime = 00:00:61")); + BOOST_CHECK_THROW(toml::parse(stream1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream2), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream3), toml::syntax_error); } BOOST_AUTO_TEST_CASE(test_date) { - TOML11_TEST_PARSE_EQUAL(parse_local_date, "1979-05-27", - toml::local_date(1979, toml::month_t::May, 27)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "1979-05-27", toml::local_date(1979, toml::month_t::May, 27)); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-01-01", toml::local_date(2000, toml::month_t::Jan, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-01-31", toml::local_date(2000, toml::month_t::Jan, 31)); + std::istringstream stream1_1(std::string("invalid-datetime = 2000-01-00")); + std::istringstream stream1_2(std::string("invalid-datetime = 2000-01-32")); + BOOST_CHECK_THROW(toml::parse(stream1_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream1_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-02-01", toml::local_date(2000, toml::month_t::Feb, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-02-29", toml::local_date(2000, toml::month_t::Feb, 29)); + std::istringstream stream2_1(std::string("invalid-datetime = 2000-02-00")); + std::istringstream stream2_2(std::string("invalid-datetime = 2000-02-30")); + BOOST_CHECK_THROW(toml::parse(stream2_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream2_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2001-02-28", toml::local_date(2001, toml::month_t::Feb, 28)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2004-02-29", toml::local_date(2004, toml::month_t::Feb, 29)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2100-02-28", toml::local_date(2100, toml::month_t::Feb, 28)); + std::istringstream stream2_3(std::string("invalid-datetime = 2001-02-29")); + std::istringstream stream2_4(std::string("invalid-datetime = 2004-02-30")); + std::istringstream stream2_5(std::string("invalid-datetime = 2100-02-29")); + BOOST_CHECK_THROW(toml::parse(stream2_3), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream2_4), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream2_5), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-03-01", toml::local_date(2000, toml::month_t::Mar, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-03-31", toml::local_date(2000, toml::month_t::Mar, 31)); + std::istringstream stream3_1(std::string("invalid-datetime = 2000-03-00")); + std::istringstream stream3_2(std::string("invalid-datetime = 2000-03-32")); + BOOST_CHECK_THROW(toml::parse(stream3_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream3_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-04-01", toml::local_date(2000, toml::month_t::Apr, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-04-30", toml::local_date(2000, toml::month_t::Apr, 30)); + std::istringstream stream4_1(std::string("invalid-datetime = 2000-04-00")); + std::istringstream stream4_2(std::string("invalid-datetime = 2000-04-31")); + BOOST_CHECK_THROW(toml::parse(stream4_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream4_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-05-01", toml::local_date(2000, toml::month_t::May, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-05-31", toml::local_date(2000, toml::month_t::May, 31)); + std::istringstream stream5_1(std::string("invalid-datetime = 2000-05-00")); + std::istringstream stream5_2(std::string("invalid-datetime = 2000-05-32")); + BOOST_CHECK_THROW(toml::parse(stream5_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream5_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-06-01", toml::local_date(2000, toml::month_t::Jun, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-06-30", toml::local_date(2000, toml::month_t::Jun, 30)); + std::istringstream stream6_1(std::string("invalid-datetime = 2000-06-00")); + std::istringstream stream6_2(std::string("invalid-datetime = 2000-06-31")); + BOOST_CHECK_THROW(toml::parse(stream6_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream6_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-07-01", toml::local_date(2000, toml::month_t::Jul, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-07-31", toml::local_date(2000, toml::month_t::Jul, 31)); + std::istringstream stream7_1(std::string("invalid-datetime = 2000-07-00")); + std::istringstream stream7_2(std::string("invalid-datetime = 2000-07-32")); + BOOST_CHECK_THROW(toml::parse(stream7_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream7_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-08-01", toml::local_date(2000, toml::month_t::Aug, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-08-31", toml::local_date(2000, toml::month_t::Aug, 31)); + std::istringstream stream8_1(std::string("invalid-datetime = 2000-08-00")); + std::istringstream stream8_2(std::string("invalid-datetime = 2000-08-32")); + BOOST_CHECK_THROW(toml::parse(stream8_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream8_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-09-01", toml::local_date(2000, toml::month_t::Sep, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-09-30", toml::local_date(2000, toml::month_t::Sep, 30)); + std::istringstream stream9_1(std::string("invalid-datetime = 2000-09-00")); + std::istringstream stream9_2(std::string("invalid-datetime = 2000-09-31")); + BOOST_CHECK_THROW(toml::parse(stream9_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream9_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-10-01", toml::local_date(2000, toml::month_t::Oct, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-10-31", toml::local_date(2000, toml::month_t::Oct, 31)); + std::istringstream stream10_1(std::string("invalid-datetime = 2000-10-00")); + std::istringstream stream10_2(std::string("invalid-datetime = 2000-10-32")); + BOOST_CHECK_THROW(toml::parse(stream10_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream10_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-11-01", toml::local_date(2000, toml::month_t::Nov, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-11-30", toml::local_date(2000, toml::month_t::Nov, 30)); + std::istringstream stream11_1(std::string("invalid-datetime = 2000-11-00")); + std::istringstream stream11_2(std::string("invalid-datetime = 2000-11-31")); + BOOST_CHECK_THROW(toml::parse(stream11_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream11_2), toml::syntax_error); + + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-12-01", toml::local_date(2000, toml::month_t::Dec, 1)); + TOML11_TEST_PARSE_EQUAL(parse_local_date, "2000-12-31", toml::local_date(2000, toml::month_t::Dec, 31)); + std::istringstream stream12_1(std::string("invalid-datetime = 2000-12-00")); + std::istringstream stream12_2(std::string("invalid-datetime = 2000-12-32")); + BOOST_CHECK_THROW(toml::parse(stream12_1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream12_2), toml::syntax_error); + + std::istringstream stream13_1(std::string("invalid-datetime = 2000-13-01")); + BOOST_CHECK_THROW(toml::parse(stream13_1), toml::syntax_error); + std::istringstream stream0_1(std::string("invalid-datetime = 2000-00-01")); + BOOST_CHECK_THROW(toml::parse(stream0_1), toml::syntax_error); } + BOOST_AUTO_TEST_CASE(test_date_value) { - TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "1979-05-27", - value(toml::local_date(1979, toml::month_t::May, 27))); + TOML11_TEST_PARSE_EQUAL_VALUE(parse_value, "1979-05-27", value(toml::local_date(1979, toml::month_t::May, 27))); } BOOST_AUTO_TEST_CASE(test_datetime) @@ -107,6 +220,11 @@ BOOST_AUTO_TEST_CASE(test_offset_datetime) TOML11_TEST_PARSE_EQUAL(parse_offset_datetime, "1979-05-27T07:32:00.999999+09:00", toml::offset_datetime(toml::local_date(1979, toml::month_t::May, 27), toml::local_time(7, 32, 0, 999, 999), toml::time_offset(9, 0))); + + std::istringstream stream1(std::string("invalid-datetime = 2000-01-01T00:00:00+24:00")); + std::istringstream stream2(std::string("invalid-datetime = 2000-01-01T00:00:00+00:60")); + BOOST_CHECK_THROW(toml::parse(stream1), toml::syntax_error); + BOOST_CHECK_THROW(toml::parse(stream2), toml::syntax_error); } BOOST_AUTO_TEST_CASE(test_offset_datetime_value)