A calendar's recurrence rules were each allowed 100,000 passes that find nothing before calcard gave up on them, counted afresh for every rule and after every instance found, and nothing bounded the total. Anyone who can put an object on a calendar (a CalDAV or JMAP upload, or an iMIP invitation the server processes) could send a few thousand components whose rules never match, such as FREQ=MONTHLY;BYMONTH=2;BYMONTHDAY=31, or rules that match rarely, and storing or processing that object cost tens of seconds of CPU. The per-period candidate check doesn't catch these: each period is small, there are just very many of them. Expansion now has one budget of 1,000,000 work units for all the rules of the object together (about 50 ms in a release build). Every pass over a period costs a unit, a pass that finds nothing a unit per 16 days it scanned, candidates looked at but not produced a unit each, and so does each time skipped while looking for the next BYHOUR/BYMINUTE/BYSECOND match. When it runs out, expansion stops the way it does at the instance limit: the object is kept and the instances found so far are indexed. The expansion also reports it, so the server logs a calendar rule expansion error naming it. Rules are no longer started once the instance limit is reached. Ordinary rules use a small part of the budget: 3,000 daily, weekly, monthly, yearly or every-leap-day instances expand as before. Test: in calcard's new budget tests, 40 rules that never match took 3,999,960 iterations on the vendored copy and a sparse daily rule 2,922,306 (both over the 1,000,000 asserted); both now stop within the budget and report it. 4,000 never-matching components (412 KB) took 13.7 s (daily), 31.7 s (yearly) and 41.8 s (monthly) in a release build, now 36 ms, 45 ms and 57 ms.
3.1 KiB
calcard, vendored
The published calcard 0.3.14, taken
from crates.io (.crate sha256
c601473ec15a875626bce73db1a1f0fc81e9c949ca25f1463b714947c0a70a1f, the
checksum the server's Cargo.lock had for it) under Apache-2.0 OR MIT.
INBUXA takes it under MIT. The root Cargo.toml patches it in with
[patch.crates-io].
Left out: the top-level resources/ (26 MB of iCalendar, vCard,
JSCalendar and JSContact samples) and .github/, which the server never
builds, and Cargo.lock, Cargo.toml.orig, .cargo_vcs_info.json and
.cargo-ok. The crate's own tests that read resources/ (expand_rrule, parse_ical, parse_vcard,
convert_jscalendar, convert_jscontact and remove_component_ids) can't
run here; the crate's other tests run from this directory with:
cargo test --offline --target-dir ../../target/calcard-tests -- \
--skip expand_rrule --skip parse_ical --skip parse_vcard \
--skip convert_jscalendar --skip convert_jscontact \
--skip remove_component_ids
Changes
Every changed file carries a Coffey Labs notice in its header.
.gitignore:VENDORED.mdis tracked (upstream ignores every*.mdbut the changelog and README).src/datecalc/mod.rs,src/datecalc/rrule_iter.rs,src/datecalc/counter_date.rs,src/icalendar/dates.rs: oneexpand_datescall has a single work budget for all its recurrence rules (MAX_EXPANSION_WORK, 1,000,000 units, about 50 ms in a release build). Upstream only stops a rule after 100,000 passes that find nothing, counted afresh for every instance and every rule, so a calendar of a few thousand rules that never match (FREQ=MONTHLY;BYMONTH=2;BYMONTHDAY=31), or one sparse rule, took seconds to minutes. A pass over a period costs one unit, a pass that finds nothing one per 16 days it scanned, each candidate a pass looks at without producing it one, and each time skipped while looking for the nextBYHOUR/BYMINUTE/BYSECONDmatch one (DateTimeIter::increment_countedcounts those). When the budget runs out, expansion stops: the instances found so far are kept, as when the instance limit is reached, later rules aren't expanded, and the result carries aCalendarErrorType::ExpansionBudgetExhaustederror for the component it stopped at. Rules are no longer started once the instance limit is reached (they couldn't add anything). Thebudget_testsindates.rscover it, andrule_expansion_shares_one_budgetincrates/groupwarefails to build if the server stops using this copy.
Updating
When the server's Cargo.lock moves calcard to a new version, Cargo stops
using this copy and only warns that the patch went unused, so check
Cargo.lock has no calcard entry with a source line. Re-vendor the new
version from ~/.cargo/registry/src/*/calcard-*, dropping the top-level
resources/ and .github/, Cargo.lock, Cargo.toml.orig,
.cargo_vcs_info.json and .cargo-ok (rsync --exclude /resources --exclude /.github ...), repeat the changes above, and update the version
here and in the root Cargo.toml comment.