memories: deny Snapchat-prefixed filenames from timestamp parsing
Snapchat assigns sequential IDs that happen to overlap real epoch values, so the 10-16 digit timestamp regex matched and produced 2002-era dates for files actually saved in 2016/2021. The digits themselves are indistinguishable from a unix timestamp, so we dispatch on the source-app prefix instead. Case-insensitive, extensible for future apps that exhibit the same pattern. Reported cases: Snapchat-1021849065.mp4 → 2002-05-19 (actual 2021) Snapchat-1751031586660373917.jpg → 2002-09-09 (actual 2016) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -206,6 +206,20 @@ pub fn extract_date_from_filename(filename: &str) -> Option<DateTime<FixedOffset
|
|||||||
let timestamp_str = captures.get(1)?.as_str();
|
let timestamp_str = captures.get(1)?.as_str();
|
||||||
let len = timestamp_str.len();
|
let len = timestamp_str.len();
|
||||||
|
|
||||||
|
// Known apps whose filenames carry sequential IDs that happen to
|
||||||
|
// overlap real epoch values (e.g. `Snapchat-1021849065.mp4` parses
|
||||||
|
// as 2002-05-19, but the file was actually saved in 2021). The
|
||||||
|
// digits alone are indistinguishable from a unix timestamp, so we
|
||||||
|
// dispatch on the source-app prefix instead.
|
||||||
|
const NON_TIMESTAMP_PREFIXES: &[&str] = &["snapchat-"];
|
||||||
|
let lower = filename.to_ascii_lowercase();
|
||||||
|
if NON_TIMESTAMP_PREFIXES
|
||||||
|
.iter()
|
||||||
|
.any(|p| lower.starts_with(p))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip autogenerated filenames that start with "10000" (e.g., 1000004178.jpg)
|
// Skip autogenerated filenames that start with "10000" (e.g., 1000004178.jpg)
|
||||||
// These are not timestamps but auto-generated file IDs
|
// These are not timestamps but auto-generated file IDs
|
||||||
if timestamp_str.starts_with("10000") {
|
if timestamp_str.starts_with("10000") {
|
||||||
@@ -640,6 +654,22 @@ mod tests {
|
|||||||
assert!(extract_date_from_filename("IMG_21323906751390.jpeg").is_none());
|
assert!(extract_date_from_filename("IMG_21323906751390.jpeg").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_date_from_filename_snapchat_should_not_match() {
|
||||||
|
// Snapchat-prefixed filenames carry sequential app-assigned IDs
|
||||||
|
// that happen to fall inside plausible epoch ranges. The two
|
||||||
|
// reported cases:
|
||||||
|
// Snapchat-1021849065.mp4 → 10 digits → 2002-05-19
|
||||||
|
// Snapchat-1751031586660373917.jpg → 19 digits → 2002-09-09
|
||||||
|
// Real save dates are 2021 and 2016 respectively per
|
||||||
|
// FileModifyDate; the prefix denylist forces a fall-through to
|
||||||
|
// fs_time.
|
||||||
|
assert!(extract_date_from_filename("Snapchat-1021849065.mp4").is_none());
|
||||||
|
assert!(extract_date_from_filename("Snapchat-1751031586660373917.jpg").is_none());
|
||||||
|
// Case-insensitive match — lowercase variant should also reject.
|
||||||
|
assert!(extract_date_from_filename("snapchat-1021849065.mp4").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
// The obsolete `test_memory_date_priority_*` tests covered the old
|
// The obsolete `test_memory_date_priority_*` tests covered the old
|
||||||
// request-time waterfall in `get_memory_date_with_priority`. Their
|
// request-time waterfall in `get_memory_date_with_priority`. Their
|
||||||
// replacement lives in `crate::date_resolver::tests` (resolver
|
// replacement lives in `crate::date_resolver::tests` (resolver
|
||||||
|
|||||||
Reference in New Issue
Block a user