Browse Source

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
tags/debian/1.3_exp2
David Kalnischkies 5 years ago
parent
commit
9febc2b238
4 changed files with 107 additions and 24 deletions
  1. +52
    -22
      apt-pkg/contrib/strutl.cc
  2. +15
    -0
      apt-pkg/contrib/strutl.h
  3. +2
    -2
      test/integration/test-apt-update-ims
  4. +38
    -0
      test/libapt/strutil_test.cc

+ 52
- 22
apt-pkg/contrib/strutl.cc View File

@@ -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;
}
/*}}}*/


+ 15
- 0
apt-pkg/contrib/strutl.h View 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);


+ 2
- 2
test/integration/test-apt-update-ims View File

@@ -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'


+ 38
- 0
test/libapt/strutil_test.cc View 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));
}

Loading…
Cancel
Save