Repository: mdsteele/rust-ar (language: rust)

Issue:
mod timestamp -1 not supported
When attempting to parse `libcpmt.lib`/`0a783ea78e08268f9ead780da0368409` from the MSVC runtime, rust-ar fails to parse the first entry with the error: `Custom { kind: InvalidData, error: "Invalid timestamp field in entry header (\"-1          \")" }`

Indeed, the modification timestamps for the entries in this file are set to "-1":

<img width="567" alt="image" src="https://github.com/mdsteele/rust-ar/assets/156560/a41cf8b1-e0a5-4c93-9d11-71c693d6f7dc">


Discussion / hints:

Fix patch:
diff --git a/src/archive.rs b/src/archive.rs
index b24ab0b..ac7d715 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -723,6 +723,16 @@ mod tests {
         archive.next_entry().unwrap().unwrap();
     }
 
+    #[test]
+    fn read_archive_with_mtime_minus_one() {
+        let input = "\
+        !<arch>\n\
+        foo.txt         -1          501   20    100644  7         `\n\
+        foobar\n\n";
+        let mut archive = Archive::new(input.as_bytes());
+        archive.next_entry().unwrap().unwrap();
+    }
+
     #[test]
     #[should_panic(expected = "Invalid owner ID field in entry header \
                                (\\\"foo   \\\")")]
diff --git a/src/header.rs b/src/header.rs
index d16a819..9462650 100644
--- a/src/header.rs
+++ b/src/header.rs
@@ -173,7 +173,11 @@ impl Header {
             *variant = Variant::GNU;
             identifier.pop();
         }
-        let mtime = parse_number("timestamp", &buffer[16..28], 10)?;
+        let mtime = parse_number_permitting_minus_one(
+            "timestamp",
+            &buffer[16..28],
+            10,
+        )?;
         let uid = if *variant == Variant::GNU {
             parse_number_permitting_empty("owner ID", &buffer[28..34], 10)?
         } else {
@@ -299,6 +303,31 @@ fn parse_number(field_name: &str, bytes: &[u8], radix: u32) -> Result<u64> {
     Err(Error::new(ErrorKind::InvalidData, msg))
 }
 
+/*
+ * Equivalent to parse_number() except for the case of "-1"
+ * as MS tools may emit for mtime.
+ */
+fn parse_number_permitting_minus_one(
+    field_name: &str,
+    bytes: &[u8],
+    radix: u32,
+) -> Result<u64> {
+    if let Ok(string) = str::from_utf8(bytes) {
+        let trimmed = string.trim_end();
+        if trimmed == "-1" {
+            return Ok(0);
+        } else if let Ok(value) = u64::from_str_radix(trimmed, radix) {
+            return Ok(value);
+        }
+    }
+    let msg = format!(
+        "Invalid {} field in entry header ({:?})",
+        field_name,
+        String::from_utf8_lossy(bytes)
+    );
+    Err(Error::new(ErrorKind::InvalidData, msg))
+}
+
 /*
  * Equivalent to parse_number() except for the case of bytes being
  * all spaces (eg all 0x20) as MS tools emit for UID/GID


Test patch: