summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Kalnischkies <david@kalnischkies.de>2016-05-28 11:03:35 +0200
committerDavid Kalnischkies <david@kalnischkies.de>2016-05-28 11:42:20 +0200
commit9febc2b238e1e322dce1f94ecbed46d595893b52 (patch)
tree38137e0585535cb11c4d21d619c612b25609ed7a
parentb58e2c7c56b1416a343e81f9f80cb1f02c128e25 (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
-rw-r--r--apt-pkg/contrib/strutl.cc74
-rw-r--r--apt-pkg/contrib/strutl.h15
-rwxr-xr-xtest/integration/test-apt-update-ims4
-rw-r--r--test/libapt/strutil_test.cc38
4 files changed, 107 insertions, 24 deletions
diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc
index 96ea99147..7fdc59228 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>
@@ -921,29 +924,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 e1ffd5240..f118f7c11 100755
--- a/test/integration/test-apt-update-ims
+++ b/test/integration/test-apt-update-ims
@@ -95,9 +95,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
msgmsg 'expired InRelease'
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));
+}