From 6b97a57e8e7949b9b4872a132804261f5c4248e9 Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Wed, 14 May 2025 16:01:44 -0600 Subject: [PATCH] Support @-prefixed Unix epoch timestamps as date strings Signed-off-by: Zhaofeng Li --- libarchive/archive_parse_date.c | 55 +++++++++++++++++++++-- libarchive/test/test_archive_parse_date.c | 12 +++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/libarchive/archive_parse_date.c b/libarchive/archive_parse_date.c index 5e2af2aa0..f907bf20a 100644 --- a/libarchive/archive_parse_date.c +++ b/libarchive/archive_parse_date.c @@ -819,6 +819,23 @@ RelativeMonth(time_t Start, time_t Timezone, time_t RelMonth) Timezone, DSTmaybe)); } +/* + * Parses and consumes an unsigned number. + * Returns 1 if any number is parsed. Otherwise, *value is unchanged. + */ +static char +consume_unsigned_number(const char **in, time_t *value) +{ + char c; + if (isdigit((unsigned char)(c = **in))) { + for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); ) + *value = 10 * *value + c - '0'; + (*in)--; + return 1; + } + return 0; +} + /* * Tokenizer. */ @@ -895,10 +912,7 @@ nexttoken(const char **in, time_t *value) * Because '-' and '+' have other special meanings, I * don't deal with signed numbers here. */ - if (isdigit((unsigned char)(c = **in))) { - for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); ) - *value = 10 * *value + c - '0'; - (*in)--; + if (consume_unsigned_number(in, value)) { return (tUNUMBER); } @@ -929,6 +943,32 @@ difftm (struct tm *a, struct tm *b) + (a->tm_sec - b->tm_sec)); } +/* + * Parses a Unix epoch timestamp (seconds). + * This supports a subset of what GNU tar accepts from black box testing, + * but covers common use cases. + */ +static time_t +parse_unix_epoch(const char *p) +{ + time_t epoch; + + /* may begin with + */ + if (*p == '+') { + p++; + } + + /* followed by some number */ + if (!consume_unsigned_number(&p, &epoch)) + return (time_t)-1; + + /* ...and nothing else */ + if (*p != '\0') + return (time_t)-1; + + return epoch; +} + /* * * The public function. @@ -948,6 +988,13 @@ archive_parse_date(time_t now, const char *p) time_t tod; long tzone; + /* + * @-prefixed Unix epoch timestamps (seconds) + * Skip the complex tokenizer - We do not want to accept strings like "@tenth" + */ + if (*p == '@') + return parse_unix_epoch(p + 1); + /* Clear out the parsed token array. */ memset(tokens, 0, sizeof(tokens)); /* Initialize the parser state. */ diff --git a/libarchive/test/test_archive_parse_date.c b/libarchive/test/test_archive_parse_date.c index fffec9ea6..0a70971b1 100644 --- a/libarchive/test/test_archive_parse_date.c +++ b/libarchive/test/test_archive_parse_date.c @@ -81,5 +81,17 @@ DEFINE_TEST(test_archive_parse_date) /* "last tuesday" is one week before "tuesday" */ assertEqualInt(get_date(now, "last tuesday UTC"), now - 6 * 24 * 60 * 60); + + /* Unix epoch timestamps */ + assertEqualInt(get_date(now, "@0"), 0); + assertEqualInt(get_date(now, "@100"), 100); + assertEqualInt(get_date(now, "@+100"), 100); + + assertEqualInt(get_date(now, "@"), -1); + assertEqualInt(get_date(now, "@-"), -1); + assertEqualInt(get_date(now, "@+"), -1); + assertEqualInt(get_date(now, "@tenth"), -1); + assertEqualInt(get_date(now, "@100 tomorrow"), -1); + /* TODO: Lots more tests here. */ } -- 2.47.2