From d946e5ddd581d07bbea73f2d32861ad6c09d58e4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 7 May 2022 10:30:23 +1000 Subject: [PATCH] Fix for crash with invalid dates on Windows (#1837) https://forums.ankiweb.net/t/bug-report-crashing-when-opening-deck-browser/19768 Caused by a note mtime that was 1000x larger than it should have been. Check DB will now fix this case (but there are others it still does not cover, such as invalid card/note IDs). https://docs.rs/chrono/0.4.19/x86_64-pc-windows-msvc/src/chrono/sys/windows.rs.html#128 --- rslib/src/dbcheck.rs | 9 +++++++-- rslib/src/scheduler/mod.rs | 2 +- rslib/src/timestamp.rs | 25 ++++++++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/rslib/src/dbcheck.rs b/rslib/src/dbcheck.rs index 46fb95c3a..e13c97b5e 100644 --- a/rslib/src/dbcheck.rs +++ b/rslib/src/dbcheck.rs @@ -206,7 +206,8 @@ impl Collection { let nids_by_notetype = self.storage.all_note_ids_by_notetype()?; let norm = self.get_config_bool(BoolKey::NormalizeNoteText); let usn = self.usn()?; - let stamp = TimestampMillis::now(); + let stamp_millis = TimestampMillis::now(); + let stamp_secs = TimestampSecs::now(); let expanded_tags = self.storage.expanded_tags()?; self.storage.clear_all_tags()?; @@ -221,7 +222,7 @@ impl Collection { None => { let first_note = self.storage.get_note(group.peek().unwrap().1)?.unwrap(); out.notetypes_recovered += 1; - self.recover_notetype(stamp, first_note.fields().len(), ntid)? + self.recover_notetype(stamp_millis, first_note.fields().len(), ntid)? } Some(nt) => nt, }; @@ -252,6 +253,10 @@ impl Collection { out.field_count_mismatch += 1; } + if note.mtime > stamp_secs { + note.mtime = stamp_secs; + } + // note type ID may have changed if we created a recovery notetype note.notetype_id = nt.id; diff --git a/rslib/src/scheduler/mod.rs b/rslib/src/scheduler/mod.rs index 577d69ff4..45739d699 100644 --- a/rslib/src/scheduler/mod.rs +++ b/rslib/src/scheduler/mod.rs @@ -90,7 +90,7 @@ impl Collection { .and_then(|v| FixedOffset::west_opt(v * 60)) .unwrap_or_else(|| FixedOffset::west(0)); - let local_tz = TimestampSecs::now().local_utc_offset(); + let local_tz = TimestampSecs::now().local_utc_offset()?; Ok(if self.server { config_tz diff --git a/rslib/src/timestamp.rs b/rslib/src/timestamp.rs index 5a790ea35..d61c0b3fb 100644 --- a/rslib/src/timestamp.rs +++ b/rslib/src/timestamp.rs @@ -5,7 +5,7 @@ use std::time; use chrono::prelude::*; -use crate::define_newtype; +use crate::{define_newtype, prelude::*}; define_newtype!(TimestampSecs, i64); define_newtype!(TimestampMillis, i64); @@ -31,18 +31,33 @@ impl TimestampSecs { TimestampMillis(self.0 * 1000) } + #[cfg(windows)] + pub(crate) fn local_datetime(self) -> Result> { + std::panic::catch_unwind(|| Local.timestamp(self.0, 0)) + .map_err(|_err| AnkiError::invalid_input("invalid date")) + } + + #[cfg(not(windows))] + pub(crate) fn local_datetime(self) -> Result> { + Ok(Local.timestamp(self.0, 0)) + } + /// YYYY-mm-dd pub(crate) fn date_string(self) -> String { - Local.timestamp(self.0, 0).format("%Y-%m-%d").to_string() + self.local_datetime() + .map(|dt| dt.format("%Y-%m-%d").to_string()) + .unwrap_or_else(|_err| "invalid date".to_string()) } /// HH-MM pub(crate) fn time_string(self) -> String { - Local.timestamp(self.0, 0).format("%H:%M").to_string() + self.local_datetime() + .map(|dt| dt.format("%H:%M").to_string()) + .unwrap_or_else(|_err| "invalid date".to_string()) } - pub fn local_utc_offset(self) -> FixedOffset { - *Local.timestamp(self.0, 0).offset() + pub fn local_utc_offset(self) -> Result { + Ok(*self.local_datetime()?.offset()) } pub fn datetime(self, utc_offset: FixedOffset) -> DateTime {