diff options
author | David Kalnischkies <david@kalnischkies.de> | 2016-05-28 11:03:35 +0200 |
---|---|---|
committer | Julian Andres Klode <jak@debian.org> | 2016-11-14 15:10:03 +0100 |
commit | 76277d6f6c6cbcec83a52d3b1399061326c7574e (patch) | |
tree | f0a9b0bef3cca836a9083fc2dd75aa27c1443e2d | |
parent | 4ed2a17ab4334f019c00512aa54a162f0bf083c4 (diff) |
accept only the expected UTC timezones in date parsing
HTTP/1.1 hardcodes GMT (RFC 7231 §7.1.1.1) and what is good enough for the
internet must be good enough for us™ as we reuse the implementation
internally to parse (most) dates we encounter in various places like the
Release files with their Date and Valid-Until header fields.
Implementing a fully timezone aware parser just feels too hard for no
effective benefit as it would take 5+ years (= until LTS's are out of
fashion) until a repository could use non-UTC dates and expect it to
work. Not counting non-apt implementations which might or might not
only want to encounter UTC here as well.
As a bonus, this eliminates the use of an instance of setlocale in
libapt.
Closes: 819697
(cherry picked from commit 9febc2b238e1e322dce1f94ecbed46d595893b52)
-rw-r--r-- | apt-pkg/contrib/strutl.cc | 74 | ||||
-rw-r--r-- | apt-pkg/contrib/strutl.h | 15 | ||||
-rwxr-xr-x | test/integration/test-apt-update-ims | 4 | ||||
-rw-r--r-- | test/libapt/strutil_test.cc | 38 |
4 files changed, 107 insertions, 24 deletions
diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc index 9b7162e3e..92fdee32a 100644 --- a/apt-pkg/contrib/strutl.cc +++ b/apt-pkg/contrib/strutl.cc @@ -21,16 +21,19 @@ #include <apt-pkg/fileutl.h> #include <apt-pkg/error.h> +#include <algorithm> +#include <iomanip> +#include <locale> +#include <sstream> +#include <string> +#include <vector> + #include <stddef.h> #include <stdlib.h> #include <time.h> -#include <string> -#include <vector> #include <ctype.h> #include <string.h> -#include <sstream> #include <stdio.h> -#include <algorithm> #include <unistd.h> #include <regex.h> #include <errno.h> @@ -917,29 +920,56 @@ static time_t timegm(struct tm *t) } #endif /*}}}*/ -// FullDateToTime - Converts a HTTP1.1 full date strings into a time_t /*{{{*/ +// RFC1123StrToTime - Converts a HTTP1.1 full date strings into a time_t /*{{{*/ // --------------------------------------------------------------------- -/* tries to parses a full date as specified in RFC2616 Section 3.3.1 - with one exception: All timezones (%Z) are accepted but the protocol - says that it MUST be GMT, but this one is equal to UTC which we will - encounter from time to time (e.g. in Release files) so we accept all - here and just assume it is GMT (or UTC) later on */ +/* tries to parses a full date as specified in RFC7231 §7.1.1.1 + with one exception: HTTP/1.1 valid dates need to have GMT as timezone. + As we encounter dates from UTC or with a numeric timezone in other places, + we allow them here to to be able to reuse the method. Either way, a date + must be in UTC or parsing will fail. Previous implementations of this + method used to ignore the timezone and assume always UTC. */ bool RFC1123StrToTime(const char* const str,time_t &time) { - struct tm Tm; - setlocale (LC_ALL,"C"); - bool const invalid = - // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 - (strptime(str, "%a, %d %b %Y %H:%M:%S %Z", &Tm) == NULL && - // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 - strptime(str, "%A, %d-%b-%y %H:%M:%S %Z", &Tm) == NULL && - // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format - strptime(str, "%a %b %d %H:%M:%S %Y", &Tm) == NULL); - setlocale (LC_ALL,""); - if (invalid == true) + struct tm t; + auto const &posix = std::locale("C.UTF-8"); + auto const parse_time = [&](char const * const s, bool const has_timezone) { + std::istringstream ss(str); + ss.imbue(posix); + ss >> std::get_time(&t, s); + if (has_timezone && ss.fail() == false) + { + std::string timezone; + ss >> timezone; + if (timezone.empty()) + return false; + if (timezone != "GMT" && timezone != "UTC" && timezone != "Z") // RFC 822 + { + // numeric timezones as a should of RFC 1123 and generally preferred + try { + size_t pos; + auto const zone = std::stoi(timezone, &pos); + if (zone != 0 || pos != timezone.length()) + return false; + } catch (...) { + return false; + } + } + } + t.tm_isdst = 0; + return ss.fail() == false; + }; + + bool const good = + // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + parse_time("%a, %d %b %Y %H:%M:%S", true) || + // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + parse_time("%A, %d-%b-%y %H:%M:%S", true) || + // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + parse_time("%c", false); // "%a %b %d %H:%M:%S %Y" + if (good == false) return false; - time = timegm(&Tm); + time = timegm(&t); return true; } /*}}}*/ diff --git a/apt-pkg/contrib/strutl.h b/apt-pkg/contrib/strutl.h index ef1172678..a32aaf06d 100644 --- a/apt-pkg/contrib/strutl.h +++ b/apt-pkg/contrib/strutl.h @@ -67,6 +67,21 @@ std::string Base64Encode(const std::string &Str); std::string OutputInDepth(const unsigned long Depth, const char* Separator=" "); std::string URItoFileName(const std::string &URI); std::string TimeRFC1123(time_t Date); +/** parses time as needed by HTTP/1.1 and Debian files. + * + * HTTP/1.1 prefers dates in RFC1123 format (but the other two obsolete date formats + * are supported to) and e.g. Release files use the same format in Date & Valid-Until + * fields. + * + * Note: datetime strings need to be in UTC timezones (GMT, UTC, Z, +/-0000) to be + * parsed. Other timezones will be rejected as invalid. Previous implementations + * accepted other timezones, but treated them as UTC. + * + * @param str is the datetime string to parse + * @param[out] time will be the seconds since epoch of the given datetime if + * parsing is successful, undefined otherwise. + * @return \b true if parsing was successful, otherwise \b false. + */ bool RFC1123StrToTime(const char* const str,time_t &time) APT_MUSTCHECK; bool FTPMDTMStrToTime(const char* const str,time_t &time) APT_MUSTCHECK; APT_DEPRECATED_MSG("Use RFC1123StrToTime or FTPMDTMStrToTime as needed instead") bool StrToTime(const std::string &Val,time_t &Result); diff --git a/test/integration/test-apt-update-ims b/test/integration/test-apt-update-ims index 5a44911a6..88f479465 100755 --- a/test/integration/test-apt-update-ims +++ b/test/integration/test-apt-update-ims @@ -97,9 +97,9 @@ runtest 'warning' # make the release file old find aptarchive -name '*Release' -exec sed -i \ - -e "s#^Date: .*\$#Date: $(date -d '-2 weeks' '+%a, %d %b %Y %H:%M:%S %Z')#" \ + -e "s#^Date: .*\$#Date: $(date -ud '-2 weeks' '+%a, %d %b %Y %H:%M:%S %Z')#" \ -e '/^Valid-Until: / d' -e "/^Date: / a\ -Valid-Until: $(date -d '-1 weeks' '+%a, %d %b %Y %H:%M:%S %Z')" '{}' \; +Valid-Until: $(date -ud '-1 weeks' '+%a, %d %b %Y %H:%M:%S %Z')" '{}' \; signreleasefiles logcurrentarchivedirectory diff --git a/test/libapt/strutil_test.cc b/test/libapt/strutil_test.cc index 8947aea59..b7ba816ee 100644 --- a/test/libapt/strutil_test.cc +++ b/test/libapt/strutil_test.cc @@ -246,3 +246,41 @@ TEST(StrUtilTest,QuoteString) EXPECT_EQ("%45ltvill%65%2d%45rbach", QuoteString("Eltville-Erbach", "E-Ae")); EXPECT_EQ("Eltville-Erbach", DeQuoteString(QuoteString("Eltville-Erbach", ""))); } + +TEST(StrUtilTest,RFC1123StrToTime) +{ + { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37 GMT", t)); + EXPECT_EQ(784111777, t); + } { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37 UTC", t)); + EXPECT_EQ(784111777, t); + } { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37 -0000", t)); + EXPECT_EQ(784111777, t); + } { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37 +0000", t)); + EXPECT_EQ(784111777, t); + } { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 GMT", t)); + EXPECT_EQ(784111777, t); + } { + time_t t; + EXPECT_TRUE(RFC1123StrToTime("Sun Nov 6 08:49:37 1994", t)); + EXPECT_EQ(784111777, t); + } + time_t t; + EXPECT_FALSE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37", t)); + EXPECT_FALSE(RFC1123StrToTime("Sun, 06 Nov 1994 GMT", t)); + EXPECT_FALSE(RFC1123StrToTime("Sonntag, 06 Nov 1994 08:49:37 GMT", t)); + EXPECT_FALSE(RFC1123StrToTime("domingo Nov 6 08:49:37 1994", t)); + EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 GMT+1", t)); + EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 EDT", t)); + EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 -0100", t)); + EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 -0.1", t)); +} |