]> git.saurik.com Git - apt.git/commitdiff
accept only the expected UTC timezones in date parsing
authorDavid Kalnischkies <david@kalnischkies.de>
Sat, 28 May 2016 09:03:35 +0000 (11:03 +0200)
committerDavid Kalnischkies <david@kalnischkies.de>
Sat, 28 May 2016 09:42:20 +0000 (11:42 +0200)
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
apt-pkg/contrib/strutl.cc
apt-pkg/contrib/strutl.h
test/integration/test-apt-update-ims
test/libapt/strutil_test.cc

index 96ea99147e5a1a97fe94d2c4bf1b71903a9a6148..7fdc5922899b02ca31aee9b5fe50424ea37f71b0 100644 (file)
 #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;
 }
                                                                        /*}}}*/
index ef117267814d7ef4bcbc54e500a4b3bcc8e5174d..a32aaf06d06a95fe8fac3cba613faed65ad6a6ee 100644 (file)
@@ -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);
index e1ffd5240eb7c2eb460eb63927c075e15dfc984b..f118f7c11b269b38cad662220ced7ca2c891c21a 100755 (executable)
@@ -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'
index 8947aea596aeececda261aea6714e46cf7eae2fb..b7ba816eeeabd7b75944640daf7e4bf6a45be680 100644 (file)
@@ -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));
+}