]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Support @-prefixed Unix epoch timestamps as date strings 2606/head
authorZhaofeng Li <hello@zhaofeng.li>
Wed, 14 May 2025 22:01:44 +0000 (16:01 -0600)
committerZhaofeng Li <hello@zhaofeng.li>
Thu, 15 May 2025 15:59:33 +0000 (09:59 -0600)
Signed-off-by: Zhaofeng Li <hello@zhaofeng.li>
libarchive/archive_parse_date.c
libarchive/test/test_archive_parse_date.c

index 5e2af2aa0c447286bd09add3068bde34d860f011..f907bf20ac60e7f448c04c54a58272c1927aed1a 100644 (file)
@@ -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. */
index fffec9ea64ae4c1190100c16ece25318ae8a04e8..0a70971b1da6b0cc5ac9b7dc1fd8fdd430c3c099 100644 (file)
@@ -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. */
 }