From 576073f8c9fb6f01585944ccb84c517d917b90a0 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Fri, 29 May 2026 18:02:15 +0200 Subject: [PATCH] Initial commit --- .github/workflows/release.yml | 335 ++ .gitignore | 10 + CHANGELOG.md | 12 + Cargo.lock | 3586 +++++++++++++++++ Cargo.toml | 53 + LICENSES/Apache-2.0.txt | 201 + LICENSES/MIT.txt | 23 + README.md | 260 ++ assets/importer-exporter.jpg | Bin 0 -> 7754 bytes dist-workspace.toml | 15 + resources/icals/000.ics | 40 + resources/icals/001.ics | 20 + resources/icals/002.ics | 22 + resources/icals/003.ics | 32 + resources/icals/010.ics | 44 + resources/icals/011.ics | 40 + resources/icals/012.ics | 15 + resources/icals/014.ics | 78 + resources/icals/024.ics | 16 + resources/icals/026.ics | 75 + resources/icals/041.ics | 66 + resources/icals/070.ics | 235 ++ resources/icals/075.ics | 626 +++ resources/icals/077.ics | 625 +++ resources/icals/088.ics | 55 + resources/icals/159.ics | 436 ++ resources/icals/162.ics | 558 +++ resources/icals/178.ics | 190 + resources/icals/182.ics | 331 ++ resources/icals/184.ics | 116 + resources/icals/195.ics | 220 + resources/icals/224.ics | 341 ++ resources/mailbox.gz | Bin 0 -> 2136385 bytes resources/vcards/029-a.vcf | 6 + resources/vcards/029-b.vcf | 6 + resources/vcards/029-c.vcf | 8 + resources/vcards/029-d.vcf | 19 + resources/vcards/029-e.vcf | 36 + resources/vcards/029-f.vcf | 24 + resources/vcards/030.vcf | 11 + resources/vcards/031.vcf | 42 + resources/vcards/034.vcf | 178 + resources/vcards/035.vcf | 353 ++ resources/vcards/036.vcf | 47 + resources/vcards/037.vcf | 80 + resources/vcards/040.vcf | 92 + resources/vcards/042.vcf | 94 + resources/vcards/045.vcf | 204 + resources/vcards/046.vcf | 14 + resources/vcards/048.vcf | 55 + resources/vcards/051.vcf | 23 + resources/vcards/066.vcf | 41 + resources/vcards/rfc-a.vcf | 62 + resources/vcards/rfc-b.vcf | 7 + resources/vcards/rfc-c.vcf | 7 + resources/vcards/rfc-d.vcf | 9 + resources/vcards/rfc-e.vcf | 12 + resources/vcards/rfc-f.vcf | 8 + resources/vcards/rfc-g.vcf | 5 + resources/vcards/rfc-h.vcf | 10 + resources/vcards/rfc-i.vcf | 22 + src/cli.rs | 1364 +++++++ src/dav/client.rs | 898 +++++ src/dav/discover.rs | 327 ++ src/dav/href.rs | 260 ++ src/dav/mod.rs | 12 + src/dav/parse.rs | 904 +++++ src/dav/retry.rs | 116 + src/dav/xml.rs | 251 ++ src/db/blobs.rs | 111 + src/db/dav_ids.rs | 336 ++ src/db/exchange_ews_ids.rs | 265 ++ src/db/exchange_graph_ids.rs | 167 + src/db/ids.rs | 114 + src/db/imap_ids.rs | 292 ++ src/db/imap_state.rs | 110 + src/db/init.rs | 50 + src/db/maildir_ids.rs | 269 ++ src/db/managesieve_ids.rs | 129 + src/db/mod.rs | 18 + src/db/schema.sql | 249 ++ src/db/sources.rs | 105 + src/db/takeout_ids.rs | 177 + src/error.rs | 77 + src/exchange/mod.rs | 7 + src/exchange/tz.rs | 258 ++ src/exchange_ews/autodiscover.rs | 561 +++ src/exchange_ews/calendar_map.rs | 580 +++ src/exchange_ews/client.rs | 445 ++ src/exchange_ews/contact_map.rs | 454 +++ src/exchange_ews/error.rs | 86 + src/exchange_ews/mod.rs | 26 + src/exchange_ews/oauth.rs | 373 ++ src/exchange_ews/parse.rs | 2332 +++++++++++ src/exchange_ews/recurrence.rs | 309 ++ src/exchange_ews/retry.rs | 157 + src/exchange_ews/soap.rs | 117 + src/exchange_ews/types.rs | 314 ++ src/exchange_ews/xml.rs | 340 ++ src/exchange_graph/api.rs | 286 ++ src/exchange_graph/calendar_map.rs | 735 ++++ src/exchange_graph/client.rs | 454 +++ src/exchange_graph/contact_map.rs | 586 +++ src/exchange_graph/error.rs | 51 + src/exchange_graph/mod.rs | 19 + src/exchange_graph/oauth.rs | 534 +++ src/exchange_graph/recurrence.rs | 277 ++ src/exchange_graph/retry.rs | 78 + src/exchange_graph/types.rs | 116 + src/imap/automap.rs | 236 ++ src/imap/client.rs | 652 +++ src/imap/command.rs | 384 ++ src/imap/error.rs | 132 + src/imap/mod.rs | 17 + src/imap/name.rs | 235 ++ src/imap/response.rs | 1093 +++++ src/imap/retry.rs | 352 ++ src/imap/transport.rs | 435 ++ src/inspect/list.rs | 631 +++ src/inspect/mod.rs | 285 ++ src/inspect/tree.rs | 327 ++ src/jmap/account.rs | 215 + src/jmap/blob.rs | 93 + src/jmap/blobxfer.rs | 40 + src/jmap/connect.rs | 66 + src/jmap/error.rs | 70 + src/jmap/http.rs | 766 ++++ src/jmap/inflight.rs | 122 + src/jmap/mod.rs | 17 + src/jmap/request.rs | 685 ++++ src/jmap/retry.rs | 312 ++ src/jmap/session.rs | 250 ++ src/jmap/wire/address_book.rs | 34 + src/jmap/wire/calendar.rs | 59 + src/jmap/wire/calendar_event.rs | 30 + src/jmap/wire/common.rs | 47 + src/jmap/wire/contact_card.rs | 24 + src/jmap/wire/email.rs | 46 + src/jmap/wire/file_node.rs | 62 + src/jmap/wire/identity.rs | 34 + src/jmap/wire/mailbox.rs | 35 + src/jmap/wire/mod.rs | 20 + src/jmap/wire/participant_identity.rs | 25 + src/jmap/wire/sieve_script.rs | 25 + src/lib.rs | 31 + src/logging.rs | 77 + src/main.rs | 105 + src/managesieve/client.rs | 504 +++ src/managesieve/command.rs | 136 + src/managesieve/error.rs | 166 + src/managesieve/mod.rs | 19 + src/managesieve/name.rs | 123 + src/managesieve/response.rs | 623 +++ src/managesieve/retry.rs | 127 + src/secret.rs | 63 + src/sync/emailmeta.rs | 113 + src/sync/export.rs | 501 +++ src/sync/export/email.rs | 253 ++ src/sync/export/flat.rs | 141 + src/sync/export/keyed.rs | 145 + src/sync/export/sieve.rs | 151 + src/sync/export/tree.rs | 321 ++ src/sync/export/uidtype.rs | 149 + src/sync/import_dav.rs | 13 + src/sync/import_dav/calcard.rs | 281 ++ src/sync/import_dav/collections.rs | 416 ++ src/sync/import_dav/coordinator.rs | 520 +++ src/sync/import_dav/items.rs | 653 +++ src/sync/import_dav/tree.rs | 681 ++++ src/sync/import_exchange_ews.rs | 15 + src/sync/import_exchange_ews/attachments.rs | 103 + src/sync/import_exchange_ews/calendar.rs | 292 ++ src/sync/import_exchange_ews/contacts.rs | 227 ++ src/sync/import_exchange_ews/coordinator.rs | 521 +++ src/sync/import_exchange_ews/folders.rs | 588 +++ src/sync/import_exchange_ews/items.rs | 469 +++ src/sync/import_exchange_ews/messages.rs | 262 ++ src/sync/import_exchange_graph.rs | 13 + src/sync/import_exchange_graph/calendar.rs | 443 ++ src/sync/import_exchange_graph/contacts.rs | 201 + src/sync/import_exchange_graph/coordinator.rs | 531 +++ src/sync/import_exchange_graph/folders.rs | 531 +++ src/sync/import_exchange_graph/messages.rs | 202 + src/sync/import_imap.rs | 16 + src/sync/import_imap/coordinator.rs | 1141 ++++++ src/sync/import_imap/fetch.rs | 113 + src/sync/import_imap/folders.rs | 504 +++ src/sync/import_imap/internaldate.rs | 152 + src/sync/import_imap/keywords.rs | 173 + src/sync/import_imap/messages.rs | 158 + src/sync/import_imap/pool.rs | 256 ++ src/sync/import_jmap.rs | 1012 +++++ src/sync/import_jmap/diff.rs | 88 + src/sync/import_jmap/mapping.rs | 1020 +++++ src/sync/import_jmap/pool.rs | 134 + src/sync/import_jmap/tree.rs | 131 + src/sync/import_maildir.rs | 12 + src/sync/import_maildir/coordinator.rs | 504 +++ src/sync/import_maildir/keywords.rs | 202 + src/sync/import_maildir/messages.rs | 726 ++++ src/sync/import_maildir/tree.rs | 495 +++ src/sync/import_managesieve.rs | 10 + src/sync/import_managesieve/coordinator.rs | 638 +++ src/sync/import_managesieve/reconcile.rs | 480 +++ src/sync/import_takeout.rs | 16 + src/sync/import_takeout/calendar.rs | 309 ++ src/sync/import_takeout/contacts.rs | 264 ++ src/sync/import_takeout/coordinator.rs | 382 ++ src/sync/import_takeout/labels.rs | 302 ++ src/sync/import_takeout/mail.rs | 416 ++ src/sync/import_takeout/mbox.rs | 278 ++ src/sync/import_takeout/tree.rs | 210 + src/sync/import_takeout/walk.rs | 267 ++ src/sync/keys.rs | 269 ++ src/sync/mod.rs | 120 + src/sync/prune.rs | 124 + src/types.rs | 142 + tests/integration/baikal.rs | 463 +++ tests/integration/baikal_schema.sql | 147 + tests/integration/cyrus.rs | 393 ++ tests/integration/data.rs | 475 +++ tests/integration/dav_client.rs | 167 + tests/integration/dovecot.rs | 503 +++ tests/integration/error.rs | 37 + tests/integration/imap_client.rs | 283 ++ tests/integration/layouts.rs | 223 + tests/integration/mod.rs | 49 + tests/integration/radicale.rs | 385 ++ tests/integration/sieve_client.rs | 150 + tests/integration/stalwart.rs | 165 + tests/integration/validate.rs | 610 +++ tests/integration/webdav.rs | 321 ++ tests/integration_baikal.rs | 489 +++ tests/integration_cyrus.rs | 320 ++ tests/integration_dovecot.rs | 516 +++ tests/integration_radicale.rs | 459 +++ tests/integration_webdav.rs | 408 ++ tests/live_stalwart.rs | 112 + tests/mock_dav.rs | 1216 ++++++ tests/mock_exchange_ews.rs | 585 +++ tests/mock_exchange_graph.rs | 2066 ++++++++++ tests/mock_imap.rs | 1596 ++++++++ tests/mock_jmap.rs | 1042 +++++ tests/mock_maildir.rs | 758 ++++ tests/mock_managesieve.rs | 1064 +++++ tests/mock_sync.rs | 1431 +++++++ tests/seed_only.rs | 53 + tests/seed_smoke.rs | 285 ++ tests/seeder/admin.rs | 208 + tests/seeder/data.rs | 114 + tests/seeder/error.rs | 28 + tests/seeder/jmap.rs | 244 ++ tests/seeder/mod.rs | 265 ++ tests/seeder/seed.rs | 414 ++ tests/sync_caldav.rs | 369 ++ tests/sync_carddav.rs | 363 ++ tests/sync_imap.rs | 395 ++ tests/sync_jmap.rs | 1150 ++++++ tests/sync_maildir.rs | 651 +++ tests/sync_managesieve.rs | 450 +++ tests/sync_takeout.rs | 785 ++++ tests/sync_webdav.rs | 326 ++ wix/main.wxs | 228 ++ 263 files changed, 78638 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 LICENSES/MIT.txt create mode 100644 README.md create mode 100644 assets/importer-exporter.jpg create mode 100644 dist-workspace.toml create mode 100644 resources/icals/000.ics create mode 100644 resources/icals/001.ics create mode 100644 resources/icals/002.ics create mode 100644 resources/icals/003.ics create mode 100644 resources/icals/010.ics create mode 100644 resources/icals/011.ics create mode 100644 resources/icals/012.ics create mode 100644 resources/icals/014.ics create mode 100644 resources/icals/024.ics create mode 100644 resources/icals/026.ics create mode 100644 resources/icals/041.ics create mode 100644 resources/icals/070.ics create mode 100644 resources/icals/075.ics create mode 100644 resources/icals/077.ics create mode 100644 resources/icals/088.ics create mode 100644 resources/icals/159.ics create mode 100644 resources/icals/162.ics create mode 100644 resources/icals/178.ics create mode 100644 resources/icals/182.ics create mode 100644 resources/icals/184.ics create mode 100644 resources/icals/195.ics create mode 100644 resources/icals/224.ics create mode 100644 resources/mailbox.gz create mode 100644 resources/vcards/029-a.vcf create mode 100644 resources/vcards/029-b.vcf create mode 100644 resources/vcards/029-c.vcf create mode 100644 resources/vcards/029-d.vcf create mode 100644 resources/vcards/029-e.vcf create mode 100644 resources/vcards/029-f.vcf create mode 100644 resources/vcards/030.vcf create mode 100644 resources/vcards/031.vcf create mode 100644 resources/vcards/034.vcf create mode 100644 resources/vcards/035.vcf create mode 100644 resources/vcards/036.vcf create mode 100644 resources/vcards/037.vcf create mode 100644 resources/vcards/040.vcf create mode 100644 resources/vcards/042.vcf create mode 100644 resources/vcards/045.vcf create mode 100644 resources/vcards/046.vcf create mode 100644 resources/vcards/048.vcf create mode 100644 resources/vcards/051.vcf create mode 100644 resources/vcards/066.vcf create mode 100644 resources/vcards/rfc-a.vcf create mode 100644 resources/vcards/rfc-b.vcf create mode 100644 resources/vcards/rfc-c.vcf create mode 100644 resources/vcards/rfc-d.vcf create mode 100644 resources/vcards/rfc-e.vcf create mode 100644 resources/vcards/rfc-f.vcf create mode 100644 resources/vcards/rfc-g.vcf create mode 100644 resources/vcards/rfc-h.vcf create mode 100644 resources/vcards/rfc-i.vcf create mode 100644 src/cli.rs create mode 100644 src/dav/client.rs create mode 100644 src/dav/discover.rs create mode 100644 src/dav/href.rs create mode 100644 src/dav/mod.rs create mode 100644 src/dav/parse.rs create mode 100644 src/dav/retry.rs create mode 100644 src/dav/xml.rs create mode 100644 src/db/blobs.rs create mode 100644 src/db/dav_ids.rs create mode 100644 src/db/exchange_ews_ids.rs create mode 100644 src/db/exchange_graph_ids.rs create mode 100644 src/db/ids.rs create mode 100644 src/db/imap_ids.rs create mode 100644 src/db/imap_state.rs create mode 100644 src/db/init.rs create mode 100644 src/db/maildir_ids.rs create mode 100644 src/db/managesieve_ids.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/schema.sql create mode 100644 src/db/sources.rs create mode 100644 src/db/takeout_ids.rs create mode 100644 src/error.rs create mode 100644 src/exchange/mod.rs create mode 100644 src/exchange/tz.rs create mode 100644 src/exchange_ews/autodiscover.rs create mode 100644 src/exchange_ews/calendar_map.rs create mode 100644 src/exchange_ews/client.rs create mode 100644 src/exchange_ews/contact_map.rs create mode 100644 src/exchange_ews/error.rs create mode 100644 src/exchange_ews/mod.rs create mode 100644 src/exchange_ews/oauth.rs create mode 100644 src/exchange_ews/parse.rs create mode 100644 src/exchange_ews/recurrence.rs create mode 100644 src/exchange_ews/retry.rs create mode 100644 src/exchange_ews/soap.rs create mode 100644 src/exchange_ews/types.rs create mode 100644 src/exchange_ews/xml.rs create mode 100644 src/exchange_graph/api.rs create mode 100644 src/exchange_graph/calendar_map.rs create mode 100644 src/exchange_graph/client.rs create mode 100644 src/exchange_graph/contact_map.rs create mode 100644 src/exchange_graph/error.rs create mode 100644 src/exchange_graph/mod.rs create mode 100644 src/exchange_graph/oauth.rs create mode 100644 src/exchange_graph/recurrence.rs create mode 100644 src/exchange_graph/retry.rs create mode 100644 src/exchange_graph/types.rs create mode 100644 src/imap/automap.rs create mode 100644 src/imap/client.rs create mode 100644 src/imap/command.rs create mode 100644 src/imap/error.rs create mode 100644 src/imap/mod.rs create mode 100644 src/imap/name.rs create mode 100644 src/imap/response.rs create mode 100644 src/imap/retry.rs create mode 100644 src/imap/transport.rs create mode 100644 src/inspect/list.rs create mode 100644 src/inspect/mod.rs create mode 100644 src/inspect/tree.rs create mode 100644 src/jmap/account.rs create mode 100644 src/jmap/blob.rs create mode 100644 src/jmap/blobxfer.rs create mode 100644 src/jmap/connect.rs create mode 100644 src/jmap/error.rs create mode 100644 src/jmap/http.rs create mode 100644 src/jmap/inflight.rs create mode 100644 src/jmap/mod.rs create mode 100644 src/jmap/request.rs create mode 100644 src/jmap/retry.rs create mode 100644 src/jmap/session.rs create mode 100644 src/jmap/wire/address_book.rs create mode 100644 src/jmap/wire/calendar.rs create mode 100644 src/jmap/wire/calendar_event.rs create mode 100644 src/jmap/wire/common.rs create mode 100644 src/jmap/wire/contact_card.rs create mode 100644 src/jmap/wire/email.rs create mode 100644 src/jmap/wire/file_node.rs create mode 100644 src/jmap/wire/identity.rs create mode 100644 src/jmap/wire/mailbox.rs create mode 100644 src/jmap/wire/mod.rs create mode 100644 src/jmap/wire/participant_identity.rs create mode 100644 src/jmap/wire/sieve_script.rs create mode 100644 src/lib.rs create mode 100644 src/logging.rs create mode 100644 src/main.rs create mode 100644 src/managesieve/client.rs create mode 100644 src/managesieve/command.rs create mode 100644 src/managesieve/error.rs create mode 100644 src/managesieve/mod.rs create mode 100644 src/managesieve/name.rs create mode 100644 src/managesieve/response.rs create mode 100644 src/managesieve/retry.rs create mode 100644 src/secret.rs create mode 100644 src/sync/emailmeta.rs create mode 100644 src/sync/export.rs create mode 100644 src/sync/export/email.rs create mode 100644 src/sync/export/flat.rs create mode 100644 src/sync/export/keyed.rs create mode 100644 src/sync/export/sieve.rs create mode 100644 src/sync/export/tree.rs create mode 100644 src/sync/export/uidtype.rs create mode 100644 src/sync/import_dav.rs create mode 100644 src/sync/import_dav/calcard.rs create mode 100644 src/sync/import_dav/collections.rs create mode 100644 src/sync/import_dav/coordinator.rs create mode 100644 src/sync/import_dav/items.rs create mode 100644 src/sync/import_dav/tree.rs create mode 100644 src/sync/import_exchange_ews.rs create mode 100644 src/sync/import_exchange_ews/attachments.rs create mode 100644 src/sync/import_exchange_ews/calendar.rs create mode 100644 src/sync/import_exchange_ews/contacts.rs create mode 100644 src/sync/import_exchange_ews/coordinator.rs create mode 100644 src/sync/import_exchange_ews/folders.rs create mode 100644 src/sync/import_exchange_ews/items.rs create mode 100644 src/sync/import_exchange_ews/messages.rs create mode 100644 src/sync/import_exchange_graph.rs create mode 100644 src/sync/import_exchange_graph/calendar.rs create mode 100644 src/sync/import_exchange_graph/contacts.rs create mode 100644 src/sync/import_exchange_graph/coordinator.rs create mode 100644 src/sync/import_exchange_graph/folders.rs create mode 100644 src/sync/import_exchange_graph/messages.rs create mode 100644 src/sync/import_imap.rs create mode 100644 src/sync/import_imap/coordinator.rs create mode 100644 src/sync/import_imap/fetch.rs create mode 100644 src/sync/import_imap/folders.rs create mode 100644 src/sync/import_imap/internaldate.rs create mode 100644 src/sync/import_imap/keywords.rs create mode 100644 src/sync/import_imap/messages.rs create mode 100644 src/sync/import_imap/pool.rs create mode 100644 src/sync/import_jmap.rs create mode 100644 src/sync/import_jmap/diff.rs create mode 100644 src/sync/import_jmap/mapping.rs create mode 100644 src/sync/import_jmap/pool.rs create mode 100644 src/sync/import_jmap/tree.rs create mode 100644 src/sync/import_maildir.rs create mode 100644 src/sync/import_maildir/coordinator.rs create mode 100644 src/sync/import_maildir/keywords.rs create mode 100644 src/sync/import_maildir/messages.rs create mode 100644 src/sync/import_maildir/tree.rs create mode 100644 src/sync/import_managesieve.rs create mode 100644 src/sync/import_managesieve/coordinator.rs create mode 100644 src/sync/import_managesieve/reconcile.rs create mode 100644 src/sync/import_takeout.rs create mode 100644 src/sync/import_takeout/calendar.rs create mode 100644 src/sync/import_takeout/contacts.rs create mode 100644 src/sync/import_takeout/coordinator.rs create mode 100644 src/sync/import_takeout/labels.rs create mode 100644 src/sync/import_takeout/mail.rs create mode 100644 src/sync/import_takeout/mbox.rs create mode 100644 src/sync/import_takeout/tree.rs create mode 100644 src/sync/import_takeout/walk.rs create mode 100644 src/sync/keys.rs create mode 100644 src/sync/mod.rs create mode 100644 src/sync/prune.rs create mode 100644 src/types.rs create mode 100644 tests/integration/baikal.rs create mode 100644 tests/integration/baikal_schema.sql create mode 100644 tests/integration/cyrus.rs create mode 100644 tests/integration/data.rs create mode 100644 tests/integration/dav_client.rs create mode 100644 tests/integration/dovecot.rs create mode 100644 tests/integration/error.rs create mode 100644 tests/integration/imap_client.rs create mode 100644 tests/integration/layouts.rs create mode 100644 tests/integration/mod.rs create mode 100644 tests/integration/radicale.rs create mode 100644 tests/integration/sieve_client.rs create mode 100644 tests/integration/stalwart.rs create mode 100644 tests/integration/validate.rs create mode 100644 tests/integration/webdav.rs create mode 100644 tests/integration_baikal.rs create mode 100644 tests/integration_cyrus.rs create mode 100644 tests/integration_dovecot.rs create mode 100644 tests/integration_radicale.rs create mode 100644 tests/integration_webdav.rs create mode 100644 tests/live_stalwart.rs create mode 100644 tests/mock_dav.rs create mode 100644 tests/mock_exchange_ews.rs create mode 100644 tests/mock_exchange_graph.rs create mode 100644 tests/mock_imap.rs create mode 100644 tests/mock_jmap.rs create mode 100644 tests/mock_maildir.rs create mode 100644 tests/mock_managesieve.rs create mode 100644 tests/mock_sync.rs create mode 100644 tests/seed_only.rs create mode 100644 tests/seed_smoke.rs create mode 100644 tests/seeder/admin.rs create mode 100644 tests/seeder/data.rs create mode 100644 tests/seeder/error.rs create mode 100644 tests/seeder/jmap.rs create mode 100644 tests/seeder/mod.rs create mode 100644 tests/seeder/seed.rs create mode 100644 tests/sync_caldav.rs create mode 100644 tests/sync_carddav.rs create mode 100644 tests/sync_imap.rs create mode 100644 tests/sync_jmap.rs create mode 100644 tests/sync_maildir.rs create mode 100644 tests/sync_managesieve.rs create mode 100644 tests/sync_takeout.rs create mode 100644 tests/sync_webdav.rs create mode 100644 wix/main.wxs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..091f3af --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,335 @@ +name: Release +permissions: + "contents": "write" + +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install dist + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" + - name: Cache dist + uses: actions/upload-artifact@v7 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/dist + - id: plan + run: | + dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v7 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container && matrix.container.image || null }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + AWS_LC_SYS_PREBUILT_NASM: 1 + permissions: + "attestations": "write" + "contents": "read" + "id-token": "write" + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install Rust non-interactively if not already installed + if: ${{ matrix.container }} + run: | + if ! command -v cargo > /dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + - name: Install dist + run: ${{ matrix.install_dist.run }} + - name: Fetch local artifacts + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "dist ran successfully" + - name: Attest + uses: actions/attest-build-provenance@v4 + with: + subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*" + - id: cargo-dist + name: Post-build + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v7 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v8 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + - name: Fetch local artifacts + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v7 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-22.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Install cached dist + uses: actions/download-artifact@v8 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/dist + - name: Fetch artifacts + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: host + shell: bash + run: | + dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v7 + with: + name: artifacts-dist-manifest + path: dist-manifest.json + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + awk '/^## \[/{c++} c==1' CHANGELOG.md > $RUNNER_TEMP/notes.txt + + # Tag-push releases are created as drafts; the `announce` job un-drafts + # them only after the build succeeds, so watcher notifications don't + # fire on broken builds. + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --draft --title "${{ needs.plan.outputs.tag }}" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* + + publish-homebrew-formula: + needs: + - plan + - host + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PLAN: ${{ needs.plan.outputs.val }} + GITHUB_USER: "axo bot" + GITHUB_EMAIL: "admin+bot@axo.dev" + if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: true + repository: "stalwartlabs/homebrew-tap" + token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + - name: Fetch homebrew formulae + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: Formula/ + merge-multiple: true + - name: Commit formula files + run: | + git config --global user.name "${GITHUB_USER}" + git config --global user.email "${GITHUB_EMAIL}" + + for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do + filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) + name=$(echo "$filename" | sed "s/\.rb$//") + version=$(echo "$release" | jq .app_version --raw-output) + + export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" + brew update + # We avoid reformatting user-provided data such as the app description and homepage. + brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true + + git add "Formula/${filename}" + git commit -m "${name} ${version}" + done + git push + + publish-npm: + needs: + - plan + - host + runs-on: "ubuntu-22.04" + if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} + permissions: + "contents": "read" + "id-token": "write" + steps: + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Fetch npm tarball + uses: actions/download-artifact@v8 + with: + pattern: artifacts-* + path: artifacts/ + merge-multiple: true + - name: Upgrade npm for trusted publishing + run: npm install -g npm@latest + - name: Publish to npm + env: + IS_PRERELEASE: ${{ fromJson(needs.plan.outputs.val).announcement_is_prerelease }} + run: | + TAG_FLAG="" + if [ "$IS_PRERELEASE" = "true" ]; then + TAG_FLAG="--tag next" + fi + npm publish ./artifacts/vandelay-npm-package.tar.gz --provenance --access public $TAG_FLAG + + announce: + needs: + - plan + - host + - publish-homebrew-formula + - publish-npm + if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') && (needs.publish-npm.result == 'skipped' || needs.publish-npm.result == 'success') }} + runs-on: "ubuntu-22.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: recursive + - name: Un-draft release + run: gh release edit "${{ needs.plan.outputs.tag }}" --draft=false --repo "${{ github.repository }}" + + cleanup: + name: Cleanup failed release + needs: + - plan + - build-local-artifacts + - build-global-artifacts + - host + - publish-homebrew-formula + - publish-npm + - announce + if: ${{ failure() && needs.plan.outputs.publishing == 'true' }} + runs-on: "ubuntu-22.04" + permissions: + contents: write + steps: + - name: Delete draft release and tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release delete "${{ needs.plan.outputs.tag }}" --yes --cleanup-tag --repo "${{ github.repository }}" || true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eac15d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +debug +target +**/*.rs.bk +**/mutants.out*/ +.env +*.sqlite +*.md +!README.md +!CHANGELOG.md +specs/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c82f09f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Change Log + +All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [1.0.0] - 2026-05-29 + +### Added +- Initial release. + +### Changed + +### Fixed diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8ae62e4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3586 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "astral-tokio-tar" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277" +dependencies = [ + "filetime", + "futures-core", + "libc", + "portable-atomic", + "rustc-hash", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "bollard" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4" +dependencies = [ + "async-stream", + "base64", + "bitflags", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.4", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-buildkit-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a885520bf6249ab931a764ffdb87b0ceef48e6e7d807cfdb21b751e086e1ad" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tonic-prost", + "ureq", +] + +[[package]] +name = "bollard-stubs" +version = "1.52.1-rc.29.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066" +dependencies = [ + "base64", + "bollard-buildkit-proto", + "bytes", + "prost", + "serde", + "serde_json", + "serde_repr", + "time", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calcard" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d4f7ea9ab08310fba58c0010e3c203aaa2b6464f1d4886d36855d99b6b6036" +dependencies = [ + "ahash", + "chrono", + "chrono-tz", + "hashify", + "jmap-tools", + "mail-builder", + "mail-parser", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "docker_credential" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d" +dependencies = [ + "base64", + "serde", + "serde_json", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ferroid" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee93edf3c501f0035bbeffeccfed0b79e14c311f12195ec0e661e114a0f60da4" +dependencies = [ + "portable-atomic", + "rand 0.10.1", + "web-time", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashify" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1246c0e5493286aeb2dde35b1f4eb9c4ce00e628641210a5e553fc001a1f26" +dependencies = [ + "indexmap 2.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jmap-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751dc449b93fc24f460c346ac2e0a87f410cd3f78971709c4e7b80236432aa30" +dependencies = [ + "hashify", + "serde", + "serde_json", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libsqlite3-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76001fb4daed01e5f2b518aac0b4dc592e7c734da63dbffcf0c64fa612a8d0c" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "mail-builder" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900998f307338c4013a28ab14d760b784067324b164448c6d98a89e44810473b" + +[[package]] +name = "mail-parser" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2420e9ce11c2b0583ca97ddff7ab2398c8a613154e9b72e3bafdbf767f1d7" +dependencies = [ + "hashify", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mockito" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" +dependencies = [ + "assert-json-diff", + "bytes", + "colored", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "pin-project-lite", + "rand 0.9.4", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "quick-xml" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2474bd2e5029e7ccb6abb2ba48cf2383a333851dedf495901544281590c7da7f" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + +[[package]] +name = "rusqlite" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b3492ea85308705c3a5cc24fb9b9cf77273d30590349070db42991202b214c4" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.21.1", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "testcontainers" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd5785b5483672915ed5fe3cddf9f546802779fc1eceff0a6fb7321fac81c1e" +dependencies = [ + "astral-tokio-tar", + "async-trait", + "bollard", + "bytes", + "docker_credential", + "either", + "etcetera", + "ferroid", + "futures", + "http", + "itertools", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.6.2", + "serde", + "serde_json", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "vandelay" +version = "1.0.0" +dependencies = [ + "base64", + "blake3", + "calcard", + "chrono", + "chrono-tz", + "clap", + "crossbeam-channel", + "flate2", + "indexmap 2.14.0", + "mail-parser", + "mockito", + "num_cpus", + "quick-xml", + "regex", + "rusqlite", + "rustls", + "rustls-platform-verifier 0.7.0", + "serde", + "serde_json", + "tempfile", + "testcontainers", + "thiserror 2.0.18", + "time", + "ureq", + "url", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b425262 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "vandelay" +description = "JMAP account migration utility" +version = "1.0.0" +authors = ["Stalwart Labs LLC "] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/stalwartlabs/vandelay" +homepage = "https://github.com/stalwartlabs/vandelay" +edition = "2024" +readme = "README.md" + +[package.metadata.wix] +upgrade-guid = "5C8148E7-34A9-4216-A802-FFB8345AECC4" +path-guid = "FA9E97EB-5E5C-4809-9018-9DC1EDB0E93D" +license = false +eula = false + +[[bin]] +name = "vandelay" +path = "src/main.rs" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0" +indexmap = { version = "2.14.0", features = ["serde"] } +rusqlite = { version = "0.40", features = ["bundled", "blob"] } +blake3 = "1.8.5" +time = { version = "0.3.47", features = ["serde-well-known", "macros"] } +url= "2.5.8" +thiserror = "2.0.18" +ureq = { version = "3.3.0", features = ["json", "rustls-no-provider", "platform-verifier"] } +clap = { version = "4.6.1", features = ["derive"] } +crossbeam-channel = "0.5.15" +num_cpus = "1.17.0" +base64 = "0.22.1" +mail-parser = "0.11.3" +calcard = "0.3" +rustls = { version = "0.23.40", default-features = false, features = ["aws-lc-rs", "std", "tls12", "logging"] } +rustls-platform-verifier = "0.7" +regex = "1.11" +flate2 = "1.1.9" +quick-xml = "0.40" +chrono = { version = "0.4", default-features = false, features = ["std"] } +chrono-tz = { version = "0.10", default-features = false } + +[dev-dependencies] +mockito = "1.7.2" +tempfile = "3.13" +testcontainers = { version = "0.27", features = ["blocking"] } + +[profile.dist] +inherits = "release" +lto = "thin" diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a110c4 --- /dev/null +++ b/README.md @@ -0,0 +1,260 @@ +

Vandelay

+ +

+ The JMAP importer-exporter +
+ One-shot account migration and backup across JMAP, IMAP, CalDAV, CardDAV, WebDAV, ManageSieve, Maildir, Google Takeout and Microsoft Exchange. +

+ +
+ +

+ crates.io +   + build +   + docs.rs +   + license +

+ +
+ +He's an importer-exporter. + +- Jerry: Well, what does *he* do? +- George: He's an importer. +- Jerry: Just imports, no exports? +- George: He's an importer/exporter, okay? + +
+  + +## About + +Vandelay is a one-shot account-migration utility for JMAP, the JMAP analogue of `imapsync` generalised to every JMAP data type (mail, contacts, calendars, identities, sieve scripts, file storage). It imports an account from a wide range of source protocols into a local SQLite "archive", then exports that archive into a target JMAP server. Import and export never talk to each other, only to the SQLite archive. One archive holds exactly one account. + +Because the archive is a self-contained SQLite file that fully describes one account, vandelay doubles as a per-account backup tool: run an import on a schedule to capture a fresh snapshot, keep the resulting SQLite file as your backup, and restore it later by running an export against a JMAP target. + +## Features + +- **Many source protocols:** + - JMAP + - IMAP + - CalDAV + - CardDAV + - WebDAV + - ManageSieve + - Maildir++ + - Google Takeout + - Microsoft Exchange via EWS (*experimental*) + - Microsoft Exchange Online via Graph (*experimental*) +- **One target protocol:** JMAP, with type-by-type stateless re-matching on every run. +- **Convergent:** Re-running an interrupted import or export picks up where it left off without bookkeeping flags. +- **Multi-threaded, no async runtime:** Blocking HTTP with per-server concurrency caps respected automatically. +- **Content-addressed blobs:** Emails, sieve scripts and file-storage payloads are stored once by BLAKE3 hash and deduplicated across the archive. +- **Dry-run everywhere:** Every command supports `--dry-run` to compute the full plan without writing. +- **Source-change protection:** An archive remembers which account it was filled from; pointing it at a different one fails unless explicitly permitted. +- **Read-only inspection:** A built-in `inspect` command dumps any object type from an archive for verification. + +## CLI quick reference + +``` +vandelay [args...] +``` + +All actions accept a set of global flags (verbosity, worker pool size, retry policy, TLS handling) in addition to action-specific ones. The most useful are: + +| Flag | Purpose | +| --- | --- | +| `-j, --threads ` | Worker pool size (default: logical CPUs). | +| `--dry-run` | Compute the full plan; perform no writes. | +| `-v`, `-vv`, `-vvv` | Increase log verbosity. | +| `-q, --quiet` | Warnings and errors only. | +| `--max-retries ` | Max retries per request on transient failures (default 5). | +| `--allow-invalid-certs` | Accept self-signed / invalid TLS certs. | + +Credentials should be supplied via the `VANDELAY_PASSWORD` / `VANDELAY_TOKEN` / `VANDELAY_EWS_CLIENT_SECRET` / `VANDELAY_GRAPH_TOKEN` environment variables, or via an interactive prompt; passing them on the command line is supported but not recommended. + +### Import + +``` +vandelay import [source-args...] +``` + +Reads a source account into the local SQLite `ARCHIVE` (created if absent). Every importer accepts `--allow-source-change` to override the archive's source-identity guard. + +#### JMAP + +``` +vandelay import jmap \ + --url \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ + (--account-id | --account-name ) \ + [--objects ] \ + +``` + +Imports a single JMAP account. `--objects` accepts a comma-separated list of object tokens (`mailbox,email,calendar,calendarevent,addressbook,contactcard,identity,sievescript,participantidentity,filenode`); default is everything the server advertises. + +#### IMAP + +``` +vandelay import imap \ + --url imap(s)://host[:port] \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN] --auth-user ) \ + [--include ...] [--exclude ...] [--exclude-special ...] \ + [--folder ...] [--subscribed-only] [--noautomap] \ + [--include-deleted] [--allow-cleartext] [--compress] \ + [--fetch-batch ] [--imap-connections <1..8>] \ + +``` + +Imports mail (and only mail) from any IMAP server. Folder selection is via `--include`/`--exclude` regexes (mutually exclusive with the exact-match `--folder`); `--exclude-special` drops by SPECIAL-USE role. + +#### CalDAV + +``` +vandelay import caldav \ + --url \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ + [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ + +``` + +Discovers the user's CalDAV principal (or accepts a URL pointing straight at a calendar-home or calendar), then imports calendars and events. + +#### CardDAV + +``` +vandelay import carddav \ + --url \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ + [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ + +``` + +Same shape as `caldav`, but for address books and contacts. + +#### WebDAV + +``` +vandelay import webdav \ + --url \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ + [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ + +``` + +Imports a plain WebDAV file collection as a JMAP `FileNode` tree. + +#### ManageSieve + +``` +vandelay import managesieve \ + --url sieve(s)://host[:port] \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN] --auth-user ) \ + [--allow-cleartext] \ + +``` + +Imports sieve scripts only. Each script is content-addressed in the blob table; the active script is recorded. + +#### Maildir + +``` +vandelay import maildir \ + [--include ...] [--exclude ...] [--folder ...] \ + [--noautomap] [--include-deleted] +``` + +Reads a local Maildir++ tree (a directory with `cur/`, `new/`, `tmp/`). No network. Folder selection mirrors the IMAP importer. + +#### Google Takeout + +``` +vandelay import takeout [--noautomap] +``` + +Scans a directory tree recursively for `.mbox`, `.ics` and `.vcf` files and imports them. Tailored to Google Takeout layouts but works on any such tree; system-label role assignment can be disabled with `--noautomap`. + +#### Microsoft Exchange (EWS) + +``` +vandelay import exchange-ews \ + [--url ] [--mailbox ] \ + [--mailbox-kind primary|archive|public-folders] \ + (--auth-basic [--auth-password ] \ + | --auth-bearer [TOKEN] [--ews-tenant --ews-client-id \ + (--ews-device-code | --ews-client-secret )]) \ + [--ews-connections <1..8>] [--ews-getitem-batch ] [--ews-attachment-batch ] \ + [--ews-no-syncfolderitems] \ + +``` + +Imports a mailbox via EWS, against either on-prem Exchange Server or Exchange Online. Autodiscover is used when `--url` is omitted (a `--mailbox` SMTP address is then required). Supports Basic, pre-acquired bearer, interactive device-code OAuth, and app-only client-credentials OAuth. + +#### Microsoft Exchange (Graph) + +``` +vandelay import exchange-graph \ + (--client-id [--tenant ] | --access-token [TOKEN]) \ + [--user ] \ + [--mailbox-kind primary|archive] \ + [--objects mail,calendar,contacts] \ + [--event-body-format text|html] \ + [--graph-connections <1..16>] [--top <1..1000>] \ + +``` + +Imports a mailbox from Exchange Online via Microsoft Graph. Without `--access-token`, the interactive device-code flow is used. `public-folders` is rejected here (use `exchange-ews` instead). + +### Export + +``` +vandelay export \ + --url \ + (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ + (--account-id | --account-name ) \ + [--objects ] [--prune [--yes]] \ + +``` + +Stateless re-export of `ARCHIVE` into a target JMAP server account. The default behaviour is upsert-only: matched items are updated, unmatched local items are created, but pre-existing target items not covered by the archive are left alone. + +`--prune` enables destructive reconciliation: target objects that do not match anything in the archive are deleted. The confirmation prompt can be skipped with `--yes` for automation. Export speaks JMAP only; no other target protocols are currently supported. + +### Inspect + +``` +vandelay inspect [TYPE] [--limit ] [--offset ] +``` + +Read-only dump of a local archive. This command never opens a network connection and never writes to the archive. + +- Omit `TYPE` for a per-type summary (counts of every object kind plus blob storage stats). +- Pass an object type to dump it: `mailbox`, `email`, `identity`, `sievescript`, `addressbook`, `contactcard`, `calendar`, `calendarevent`, `participantidentity`, `filenode`. +- `mailbox` and `filenode` render as a tree (`--limit`/`--offset` are ignored); all other types use a paginated list and respect `--limit` and `--offset`. + +## Testing + +```sh +cargo build +cargo clippy --all-targets +cargo test +``` + +Live integration tests against a Stalwart server and container-based smoke tests against third-party servers (Dovecot, Cyrus, Radicale, Baikal, Apache `mod_dav`) are gated behind `--ignored` and require Docker plus the seeder fixtures. + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Copyright + +Copyright (C) 2020, Stalwart Labs LLC diff --git a/assets/importer-exporter.jpg b/assets/importer-exporter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1fa96d2a6fffe9312d8f661ae4148a732f788af1 GIT binary patch literal 7754 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 eI^AFxT~_lix8{?0)WpcFd(poQCh+sq>i+More Information +UID:1205712057-8-1055761864 +TRANSP:TRANSPARENT +STATUS:CONFIRMED +DTSTART;VALUE=DATE:20080316 +DTSTAMP:20080310T010500Z +SUMMARY:Partly Cloudy 48F / F | 9C / C +CREATED:20080316T213048Z +DTEND;VALUE=DATE:20080317 +LOCATION:Leavenworth\, WA +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/002.ics b/resources/icals/002.ics new file mode 100644 index 0000000..fd642c0 --- /dev/null +++ b/resources/icals/002.ics @@ -0,0 +1,22 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 3.0//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +SEQUENCE:1 +DESCRIPTION:Saturday - Mostly cloudy. A chance of snow showers in the mo + rning...then a chance of rain or snow showers in the afternoon. Highs in + the mid 40s to lower 50s. Chance of precipitation 40 percent. \nMore Information + +UID:1205647257-7--150403793 +TRANSP:TRANSPARENT +STATUS:CONFIRMED +DTSTART;VALUE=DATE:20080315 +DTSTAMP:20080310T010500Z +SUMMARY:Partly Cloudy 48F / 25F | 9C / -4C +CREATED:20080316T213048Z +DTEND;VALUE=DATE:20080316 +LOCATION:Leavenworth\, WA +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/003.ics b/resources/icals/003.ics new file mode 100644 index 0000000..d27b6b6 --- /dev/null +++ b/resources/icals/003.ics @@ -0,0 +1,32 @@ +BEGIN:VCALENDAR +PRODID:-//ABC Corporation/NONSGML iCal4j 1.0//EN +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20160504T175305Z +DTSTART;TZID=America/New_York:20160514T130000 +SUMMARY:Simple event +DURATION:PT1H +UID:20160504T175305Z-uidGen@fe80:0:0:0:c8d8:72ff:fe94:1087%awdl0 +LOCATION:Mo's bar - back room +DESCRIPTION:We'll continue with the unfinished business from last time\,\ + nin particular:\nCan names start wih a number?\nWhat if they are all num + eric?\nReuse of names - is it valid\nI remind the attendees we have spen + t 3 months on these subjects. We need closure!!! +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20160504T175311Z +DTSTART;TZID=America/New_York:20160514T130000 +SUMMARY:Simple Meeting +DURATION:PT1H +UID:20160504T175311Z-uidGen@fe80:0:0:0:c8d8:72ff:fe94:1087%awdl0 +ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Person 1:mailto:person1@example.com +ATTENDEE;ROLE=OPT-PARTICIPANT;CN=Person 2:mailto:person2@example.com +ORGANIZER;CN=Person 1:mailto:person1@example.com +LOCATION:Mo's bar - back room +DESCRIPTION:We'll continue with the unfinished business from last time\,\ + nin particular:\nCan names start wih a number?\nWhat if they are all num + eric?\nReuse of names - is it valid\nI remind the attendees we have spen + t 3 months on these subjects. We need closure!!! +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/010.ics b/resources/icals/010.ics new file mode 100644 index 0000000..da39cd5 --- /dev/null +++ b/resources/icals/010.ics @@ -0,0 +1,44 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Calendar +BEGIN:VTIMEZONE +TZID:Customized Time Zone +BEGIN:STANDARD +DTSTART:16010101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DESCRIPTION:[private] +RRULE:FREQ=WEEKLY;UNTIL=20201217T153500Z;INTERVAL=1;BYDAY=TU,TH;WKST=SU +UID:1173422081SZR322 +SUMMARY:[private] +DTSTART;TZID=Customized Time Zone:20200825T103500 +DTEND;TZID=Customized Time Zone:20200825T115000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20200901T175645Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION: [private] +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/resources/icals/011.ics b/resources/icals/011.ics new file mode 100644 index 0000000..4f72c0e --- /dev/null +++ b/resources/icals/011.ics @@ -0,0 +1,40 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:ical +X-WR-TIMEZONE:US/Central +X-WR-CALDESC: +BEGIN:VEVENT +UID:98765432-ABCD-DCBB-999A-987765432123 +DTSTART;TZID=US/Central:20170601T090000 +DTEND;TZID=US/Central:20170601T170000 +DTSTAMP:20170727T044436Z +EXDATE;TZID=US/Central:20170706T090000,20170713T090000,20170720T090000,20 + 170803T090000 +LAST-MODIFIED:20170727T044435Z +RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20170814T045959Z;BYDAY=TH +SEQUENCE:0 +SUMMARY:Recurring weekly meeting from June 1 - Aug 14 (except July 6, July 13, July 20, Aug 3) +END:VEVENT +BEGIN:VEVENT +UID:98765432-ABCD-DCBB-999A-987765432123 +RECURRENCE-ID;TZID=US/Central:20170629T090000 +DTSTART;TZID=US/Central:20170703T090000 +DTEND;TZID=US/Central:20170703T120000 +DTSTAMP:20170727T044436Z +LAST-MODIFIED:20170216T143445Z +SEQUENCE:0 +SUMMARY:Last meeting in June moved to Monday July 3 and shortened to half day +END:VEVENT +BEGIN:VEVENT +UID:12354454-ABCD-DCBB-999A-2349872354897 +DTSTART;TZID=US/Central:20171201T130000 +DTEND;TZID=US/Central:20171201T150000 +DTSTAMP:20170727T044436Z +LAST-MODIFIED:20170727T044435Z +SEQUENCE:0 +SUMMARY:Single event on Dec 1 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/resources/icals/012.ics b/resources/icals/012.ics new file mode 100644 index 0000000..04eb089 --- /dev/null +++ b/resources/icals/012.ics @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART;TZID=Europe/Germany:20241022T230000 +DTEND;TZID=TZID=Europe/Germany:20241023T000000 +RRULE:FREQ=DAILY;WKST=MO;COUNT=4 +DTSTAMP:20241009T153220Z +UID:2m6mt1p89l2anl74915ur3hsgm@google.com +CREATED:20241009T153058Z +LAST-MODIFIED:20241009T153205Z +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:TestCal_AllDayRepeatingEvent +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/resources/icals/014.ics b/resources/icals/014.ics new file mode 100644 index 0000000..ff8cc07 --- /dev/null +++ b/resources/icals/014.ics @@ -0,0 +1,78 @@ +BEGIN:VCALENDAR +PRODID:-//lanyrd.com//Lanyrd//EN +X-ORIGINAL-URL:http://lanyrd.com/topics/nodejs/nodejs.ics +X-WR-CALNAME;CHARSET=utf-8:Node.js conferences +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:Dyncon 2011 +LOCATION;CHARSET=utf-8:Stockholm, Sweden +URL:http://lanyrd.com/2011/dyncon/ +UID:d4c826dfb701f611416d69b4df81caf9ff80b03a +DTSTART:20110312T200000Z +DTEND;VALUE=DATE:20110314 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:[Async]: Everything Express +LOCATION;CHARSET=utf-8:Brighton, United Kingdom +URL:http://lanyrd.com/2011/asyncjs-express/ +UID:480a3ad48af5ed8965241f14920f90524f533c18 +DTSTART;VALUE=DATE:20110324 +DTEND;VALUE=DATE:20110325 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:JSConf US 2011 +LOCATION;CHARSET=utf-8:Portland, United States +URL:http://lanyrd.com/2011/jsconf/ +UID:ed334cc85db5ebdff5ff5a630a7a48631a677dbe +DTSTART;VALUE=DATE:20110502 +DTEND;VALUE=DATE:20110504 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:NodeConf 2011 +LOCATION;CHARSET=utf-8:Portland, United States +URL:http://lanyrd.com/2011/nodeconf/ +UID:25169a7b1ba5c248278f47120a40878055dc8c15 +DTSTART;VALUE=DATE:20110505 +DTEND;VALUE=DATE:20110506 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:BrazilJS +LOCATION;CHARSET=utf-8:Fortaleza, Brazil +URL:http://lanyrd.com/2011/braziljs/ +UID:dafee3be83624f3388c5635662229ff11766bb9c +DTSTART;VALUE=DATE:20110513 +DTEND;VALUE=DATE:20110515 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:Falsy Values +LOCATION;CHARSET=utf-8:Warsaw, Poland +URL:http://lanyrd.com/2011/falsy-values/ +UID:73cad6a09ac4e7310979c6130f871d17d990b5ad +DTSTART;VALUE=DATE:20110518 +DTEND;VALUE=DATE:20110521 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:nodecamp.eu +LOCATION;CHARSET=utf-8:Cologne, Germany +URL:http://lanyrd.com/2011/nodecampde/ +UID:b728a5fdb5f292b6293e4a2fd97a1ccfc69e9d6f +DTSTART;VALUE=DATE:20110611 +DTEND;VALUE=DATE:20110613 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:Rich Web Experience 2011 +LOCATION;CHARSET=utf-8:Fort Lauderdale, United States +URL:http://lanyrd.com/2011/rich-web-experience/ +UID:47f6ea3f28af2986a2192fa39a91fa7d60d26b76 +DTSTART;VALUE=DATE:20111129 +DTEND;VALUE=DATE:20111203 +END:VEVENT +BEGIN:VEVENT +SUMMARY;CHARSET=utf-8:Foobar +UID:sdfkf09fsd0 +DTSTART;VALUE=DATE:Next Year +DTEND;VALUE=DATE:20111203 +END:VEVENT + +END:VCALENDAR diff --git a/resources/icals/024.ics b/resources/icals/024.ics new file mode 100644 index 0000000..e019cc5 --- /dev/null +++ b/resources/icals/024.ics @@ -0,0 +1,16 @@ +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:eb9e1bd2-ceba-499f-be77-f02773954c72 +SUMMARY:Event with an alarm +DESCRIPTION:This is an event with an alarm. +ORGANIZER;CN=stomlinson@mozilla.com:"mailto:stomlinson@mozilla.com" +DTSTART;TZID="America/Los_Angeles":20130418T110000 +DTEND;TZID="America/Los_Angeles":20130418T120000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20130418T175632Z +DTSTAMP:20130418T175632Z +SEQUENCE:3 +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/026.ics b/resources/icals/026.ics new file mode 100644 index 0000000..c6b3f5b --- /dev/null +++ b/resources/icals/026.ics @@ -0,0 +1,75 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +VERSION:2.0 +X-WR-CALNAME:Test +PRODID:-//Apple Inc.//macOS 11.6.7//EN +X-WR-TIMEZONE:America/Vancouver +CALSCALE:GREGORIAN +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND:20220704T235000Z +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +ORGANIZER;SCHEDULE-AGENT=CLIENT:mailto:tentative@example.com +UID:31a1ffc9-9b76-465b-ae4a-cadb694c9d37 +DTSTAMP:20220704T225759Z +DESCRIPTION: +STATUS:TENTATIVE +SEQUENCE:0 +SUMMARY:Tentative +LAST-MODIFIED:20220704T230434Z +DTSTART:20220704T230000Z +CREATED:20220704T225437Z +ATTENDEE;CN="tentative@example.com";CUTYPE=INDIVIDUAL;EMAIL="tentative@e + xample.com";PARTSTAT=TENTATIVE;ROLE=REQ-PARTICIPANT;X-NUM-GUESTS=0:mailt + o:tentative@example.com +END:VEVENT +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/Vancouver:20220705T100000 +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +ORGANIZER;SCHEDULE-AGENT=CLIENT:mailto:confirmed@example.com +UID:F00F3710-BF4D-46D3-9A2C-1037AB24C6AC +DTSTAMP:20220704T231538Z +DESCRIPTION: +STATUS:CONFIRMED +SEQUENCE:0 +SUMMARY:Confirmed +LAST-MODIFIED:20220704T231538Z +DTSTART;TZID=America/Vancouver:20220705T090000 +CREATED:20220704T231522Z +ATTENDEE;CN="confirmed@example.com";CUTYPE=INDIVIDUAL;EMAIL="confirmed@e + xample.com";PARTSTAT=CONFIRMED;ROLE=REQ-PARTICIPANT;X-NUM-GUESTS=0:mailt + o:confirmed@example.com +END:VEVENT +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/Vancouver:20220706T140000 +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +ORGANIZER;SCHEDULE-AGENT=CLIENT:mailto:cancelled@example.com +UID:8CAB46C5-669F-4249-B1AD-52BFA72F4E0A +DTSTAMP:20220704T231547Z +DESCRIPTION: +STATUS:CANCELLED +SEQUENCE:0 +SUMMARY:Cancelled +LAST-MODIFIED:20220704T231559Z +DTSTART;TZID=America/Vancouver:20220706T130000 +CREATED:20220704T231539Z +ATTENDEE;CN="cancelled@example.com";CUTYPE=INDIVIDUAL;EMAIL="cancelled@e + xample.com";PARTSTAT=cancelled;ROLE=REQ-PARTICIPANT;X-NUM-GUESTS=0:mailt + o:cancelled@example.com +END:VEVENT +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/Vancouver:20220707T100000 +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +UID:99F615DE-82C6-4CEF-97B8-CD0D3E1EE0D3 +DTSTAMP:20220704T234828Z +DESCRIPTION: +SEQUENCE:0 +SUMMARY:No Status +LAST-MODIFIED:20220704T234828Z +DTSTART;TZID=America/Vancouver:20220707T090000 +CREATED:20220704T234823Z +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/041.ics b/resources/icals/041.ics new file mode 100644 index 0000000..c845cd3 --- /dev/null +++ b/resources/icals/041.ics @@ -0,0 +1,66 @@ +BEGIN:VCALENDAR +BEGIN:VEVENT +CREATED:20231214T150120Z +LAST-MODIFIED:20231214T150224Z +DTSTAMP:20231214T150224Z +UID:df21da35-3a0b-434a-9211-da9f9095fb16 +SUMMARY:test export import +CATEGORIES:Professionnel +DTSTART;TZID=Europe/Paris:20231215T150000 +DTEND;TZID=Europe/Paris:20231215T160000 +TRANSP:OPAQUE +LOCATION:dijon +DESCRIPTION;ALTREP="data:text/html,test":test +END:VEVENT +BEGIN:VTIMEZONE +TZID: 1 +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240229T160626Z +LAST-MODIFIED:20231221T141000Z +DTSTAMP:20231221T141000Z +UID:040000008200E00074C5B7101A82E008000000008D1F7DAB8632DA01000000000000000 + 0100000008EF89A92C90D1342BF1F2CB714276CCC +SUMMARY:Atelier groupes portail/Nuage +PRIORITY:5 +STATUS:CONFIRMED +DTSTART;TZID= 1:20240108T110000 +DTEND;TZID= 1:20240108T120000 +CLASS:PUBLIC +DESCRIPTION;LANGUAGE=fr-FR:Désolé pour ces changements de date\, j'espèr + e que ce dernier créneau conviendra à tout le monde. \n\nJe vous propose + cette date pour un atelier sur les soucis avec les commandes à passer sur + les serveurs nextcloud pour la mise en place des groupes. Dites moi si ça + ne convient pas. Merci !\n +TRANSP:OPAQUE +SEQUENCE:3 +LOCATION;LANGUAGE=fr-FR:https://visio-agents.education.fr/ +X-MICROSOFT-CDO-APPT-SEQUENCE:3 +X-MICROSOFT-CDO-OWNERAPPTID:2122095757 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +X-MICROSOFT-LOCATIONDISPLAYNAME:https://visio-agents.education.fr/ +X-MICROSOFT-LOCATIONSOURCE:None +X-MICROSOFT-LATITUDE:0 +X-MICROSOFT-LONGITUDE:0 +X-MOZ-RECEIVED-SEQUENCE:3 +X-MOZ-RECEIVED-DTSTAMP:20231221T141000Z +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/070.ics b/resources/icals/070.ics new file mode 100644 index 0000000..bbee664 --- /dev/null +++ b/resources/icals/070.ics @@ -0,0 +1,235 @@ +BEGIN:VCALENDAR +PRODID:-//Offline Calendar//iCal Import/Export 2.8.1//EN +VERSION:2.0 +METHOD:PUBLISH +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/London +TZURL:http://tzurl.org/zoneinfo/Europe/London +X-LIC-LOCATION:Europe/London +BEGIN:DAYLIGHT +TZOFFSETFROM:+0000 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19810329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19961027T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:-000115 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:18471201T000115 +RDATE:18471201T000115 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0000 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19160521T020000 +RDATE:19160521T020000 +RDATE:19170408T020000 +RDATE:19180324T020000 +RDATE:19190330T020000 +RDATE:19200328T020000 +RDATE:19210403T020000 +RDATE:19220326T020000 +RDATE:19230422T020000 +RDATE:19240413T020000 +RDATE:19250419T020000 +RDATE:19260418T020000 +RDATE:19270410T020000 +RDATE:19280422T020000 +RDATE:19290421T020000 +RDATE:19300413T020000 +RDATE:19310419T020000 +RDATE:19320417T020000 +RDATE:19330409T020000 +RDATE:19340422T020000 +RDATE:19350414T020000 +RDATE:19360419T020000 +RDATE:19370418T020000 +RDATE:19380410T020000 +RDATE:19390416T020000 +RDATE:19400225T020000 +RDATE:19460414T020000 +RDATE:19470316T020000 +RDATE:19480314T020000 +RDATE:19490403T020000 +RDATE:19500416T020000 +RDATE:19510415T020000 +RDATE:19520420T020000 +RDATE:19530419T020000 +RDATE:19540411T020000 +RDATE:19550417T020000 +RDATE:19560422T020000 +RDATE:19570414T020000 +RDATE:19580420T020000 +RDATE:19590419T020000 +RDATE:19600410T020000 +RDATE:19610326T020000 +RDATE:19620325T020000 +RDATE:19630331T020000 +RDATE:19640322T020000 +RDATE:19650321T020000 +RDATE:19660320T020000 +RDATE:19670319T020000 +RDATE:19680218T020000 +RDATE:19720319T020000 +RDATE:19730318T020000 +RDATE:19740317T020000 +RDATE:19750316T020000 +RDATE:19760321T020000 +RDATE:19770320T020000 +RDATE:19780319T020000 +RDATE:19790318T020000 +RDATE:19800316T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19161001T030000 +RDATE:19161001T030000 +RDATE:19170917T030000 +RDATE:19180930T030000 +RDATE:19190929T030000 +RDATE:19201025T030000 +RDATE:19211003T030000 +RDATE:19221008T030000 +RDATE:19230916T030000 +RDATE:19240921T030000 +RDATE:19251004T030000 +RDATE:19261003T030000 +RDATE:19271002T030000 +RDATE:19281007T030000 +RDATE:19291006T030000 +RDATE:19301005T030000 +RDATE:19311004T030000 +RDATE:19321002T030000 +RDATE:19331008T030000 +RDATE:19341007T030000 +RDATE:19351006T030000 +RDATE:19361004T030000 +RDATE:19371003T030000 +RDATE:19381002T030000 +RDATE:19391119T030000 +RDATE:19451007T030000 +RDATE:19461006T030000 +RDATE:19471102T030000 +RDATE:19481031T030000 +RDATE:19491030T030000 +RDATE:19501022T030000 +RDATE:19511021T030000 +RDATE:19521026T030000 +RDATE:19531004T030000 +RDATE:19541003T030000 +RDATE:19551002T030000 +RDATE:19561007T030000 +RDATE:19571006T030000 +RDATE:19581005T030000 +RDATE:19591004T030000 +RDATE:19601002T030000 +RDATE:19611029T030000 +RDATE:19621028T030000 +RDATE:19631027T030000 +RDATE:19641025T030000 +RDATE:19651024T030000 +RDATE:19661023T030000 +RDATE:19671029T030000 +RDATE:19711031T030000 +RDATE:19721029T030000 +RDATE:19731028T030000 +RDATE:19741027T030000 +RDATE:19751026T030000 +RDATE:19761024T030000 +RDATE:19771023T030000 +RDATE:19781029T030000 +RDATE:19791028T030000 +RDATE:19801026T030000 +RDATE:19811025T020000 +RDATE:19821024T020000 +RDATE:19831023T020000 +RDATE:19841028T020000 +RDATE:19851027T020000 +RDATE:19861026T020000 +RDATE:19871025T020000 +RDATE:19881023T020000 +RDATE:19891029T020000 +RDATE:19901028T020000 +RDATE:19911027T020000 +RDATE:19921025T020000 +RDATE:19931024T020000 +RDATE:19941023T020000 +RDATE:19951022T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:BDST +DTSTART:19410504T010000 +RDATE:19410504T010000 +RDATE:19420405T010000 +RDATE:19430404T010000 +RDATE:19440402T010000 +RDATE:19450402T010000 +RDATE:19470413T010000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19410810T030000 +RDATE:19410810T030000 +RDATE:19420809T030000 +RDATE:19430815T030000 +RDATE:19440917T030000 +RDATE:19450715T030000 +RDATE:19470810T030000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19681026T230000 +RDATE:19681026T230000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0000 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19960101T000000 +RDATE:19960101T000000 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20241005T113036Z +UID:17281276213728ad54d03afa44d1ca60b8c52afaece9e@sufficientlysecure.org +SUMMARY:event with alarms android +STATUS:CONFIRMED +DTSTART;TZID=Europe/London:20241005T130000 +DTEND:20241005T130000Z +LAST-MODIFIED:20241005T112701Z +BEGIN:VALARM +TRIGGER:-PT30M +ACTION:DISPLAY +DESCRIPTION:event with alarms android +END:VALARM +BEGIN:VALARM +TRIGGER:-PT25M +ACTION:DISPLAY +DESCRIPTION:event with alarms android +END:VALARM +BEGIN:VALARM +TRIGGER:-PT5M +ACTION:DISPLAY +DESCRIPTION:event with alarms android +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/075.ics b/resources/icals/075.ics new file mode 100644 index 0000000..851a8db --- /dev/null +++ b/resources/icals/075.ics @@ -0,0 +1,626 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/London +X-TZINFO:Europe/London[2024a] +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:-000115 +TZNAME:Europe/London(STD) +DTSTART:18471201T000000 +RDATE:18471201T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19160521T020000 +RDATE:19160521T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19161001T030000 +RDATE:19161001T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19170408T020000 +RDATE:19170408T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19170917T030000 +RDATE:19170917T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19180324T020000 +RDATE:19180324T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19180930T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19190330T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19201025T030000 +RDATE:19201025T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19210403T020000 +RDATE:19210403T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19211003T030000 +RDATE:19211003T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19220326T020000 +RDATE:19220326T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19221008T030000 +RDATE:19221008T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19230422T020000 +RDATE:19230422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19240413T020000 +RDATE:19240413T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19230916T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19250419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19270410T020000 +RDATE:19270410T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19280422T020000 +RDATE:19280422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19290421T020000 +RDATE:19290421T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19300413T020000 +RDATE:19300413T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19310419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19251004T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19330409T020000 +RDATE:19330409T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19331008T030000 +RDATE:19331008T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19340422T020000 +RDATE:19340422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19350414T020000 +RDATE:19350414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19360419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19380410T020000 +RDATE:19380410T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19341007T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19390416T020000 +RDATE:19390416T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19400225T020000 +RDATE:19400225T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19410504T020000 +RDATE:19410504T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19410810T030000 +RDATE:19410810T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19420405T020000 +RDATE:19420405T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19420809T030000 +RDATE:19420809T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19430404T020000 +RDATE:19430404T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19430815T030000 +RDATE:19430815T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19440402T020000 +RDATE:19440402T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19440917T030000 +RDATE:19440917T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19450402T020000 +RDATE:19450402T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19391119T030000 +RDATE:19391119T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19450715T030000 +RDATE:19450715T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19460414T020000 +RDATE:19460414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19470316T020000 +RDATE:19470316T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19470413T020000 +RDATE:19470413T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19451007T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19470810T030000 +RDATE:19470810T030000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19471102T030000 +RDATE:19471102T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19480314T020000 +RDATE:19480314T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19490403T020000 +RDATE:19490403T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19481031T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19501022T030000 +RDATE:19501022T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19511021T030000 +RDATE:19511021T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19521026T030000 +RDATE:19521026T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19500416T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19540411T020000 +RDATE:19540411T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19550417T020000 +RDATE:19550417T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19560422T020000 +RDATE:19560422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19570414T020000 +RDATE:19570414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19580420T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19600410T020000 +RDATE:19600410T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19531004T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19610326T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19640322T020000 +RDATE:19640322T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19611029T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19651024T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19650321T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19671029T030000 +RDATE:19671029T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+010000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19681027T000000 +RDATE:19681027T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19680218T020000 +RDATE:19680218T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19711031T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19761024T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19720319T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19781029T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19811025T020000 +RDATE:19811025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19821024T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19841028T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19881023T020000 +RDATE:19881023T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19891029T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19931024T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19810329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19961027T020000 +RDATE:19961027T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:(DST) +DTSTART:19970330T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:(STD) +DTSTART:19971026T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20241023T173412Z +LAST-MODIFIED:20241023T173630Z +DTSTAMP:20241023T173630Z +UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 +SUMMARY:event +X-MOZ-LASTACK:20241023T173630Z +DTSTART;TZID=Europe/London:20241023T190000 +DTEND;TZID=Europe/London:20241023T200000 +TRANSP:OPAQUE +X-MOZ-GENERATION:3 +X-MOZ-SNOOZE-TIME:20241023T174130Z +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT1M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT24M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/077.ics b/resources/icals/077.ics new file mode 100644 index 0000000..524f069 --- /dev/null +++ b/resources/icals/077.ics @@ -0,0 +1,625 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/London +X-TZINFO:Europe/London[2024a] +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:-000115 +TZNAME:Europe/London(STD) +DTSTART:18471201T000000 +RDATE:18471201T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19160521T020000 +RDATE:19160521T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19161001T030000 +RDATE:19161001T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19170408T020000 +RDATE:19170408T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19170917T030000 +RDATE:19170917T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19180324T020000 +RDATE:19180324T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19180930T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19190330T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19201025T030000 +RDATE:19201025T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19210403T020000 +RDATE:19210403T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19211003T030000 +RDATE:19211003T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19220326T020000 +RDATE:19220326T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19221008T030000 +RDATE:19221008T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19230422T020000 +RDATE:19230422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19240413T020000 +RDATE:19240413T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19230916T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19250419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19270410T020000 +RDATE:19270410T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19280422T020000 +RDATE:19280422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19290421T020000 +RDATE:19290421T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19300413T020000 +RDATE:19300413T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19310419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19251004T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19330409T020000 +RDATE:19330409T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19331008T030000 +RDATE:19331008T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19340422T020000 +RDATE:19340422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19350414T020000 +RDATE:19350414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19360419T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19380410T020000 +RDATE:19380410T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19341007T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19390416T020000 +RDATE:19390416T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19400225T020000 +RDATE:19400225T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19410504T020000 +RDATE:19410504T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19410810T030000 +RDATE:19410810T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19420405T020000 +RDATE:19420405T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19420809T030000 +RDATE:19420809T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19430404T020000 +RDATE:19430404T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19430815T030000 +RDATE:19430815T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19440402T020000 +RDATE:19440402T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19440917T030000 +RDATE:19440917T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19450402T020000 +RDATE:19450402T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19391119T030000 +RDATE:19391119T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19450715T030000 +RDATE:19450715T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19460414T020000 +RDATE:19460414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19470316T020000 +RDATE:19470316T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+020000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(DST) +DTSTART:19470413T020000 +RDATE:19470413T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19451007T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+020000 +TZNAME:Europe/London(DST) +DTSTART:19470810T030000 +RDATE:19470810T030000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19471102T030000 +RDATE:19471102T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19480314T020000 +RDATE:19480314T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19490403T020000 +RDATE:19490403T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19481031T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19501022T030000 +RDATE:19501022T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19511021T030000 +RDATE:19511021T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19521026T030000 +RDATE:19521026T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19500416T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19540411T020000 +RDATE:19540411T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19550417T020000 +RDATE:19550417T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19560422T020000 +RDATE:19560422T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19570414T020000 +RDATE:19570414T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19580420T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19600410T020000 +RDATE:19600410T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19531004T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19610326T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19640322T020000 +RDATE:19640322T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19611029T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19651024T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19650321T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19671029T030000 +RDATE:19671029T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+010000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19681027T000000 +RDATE:19681027T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19680218T020000 +RDATE:19680218T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19711031T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19761024T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19720319T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19781029T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19811025T020000 +RDATE:19811025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19821024T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19841028T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19881023T020000 +RDATE:19881023T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19891029T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19931024T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:Europe/London(DST) +DTSTART:19810329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:Europe/London(STD) +DTSTART:19961027T020000 +RDATE:19961027T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:(DST) +DTSTART:19970330T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:(STD) +DTSTART:19971026T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20241023T131035Z +LAST-MODIFIED:20241023T141941Z +DTSTAMP:20241023T141941Z +UID:b9a23b47-f109-4e7a-908c-75e925b27def +SUMMARY:event with alarms +X-MOZ-LASTACK:20241023T141941Z +DTSTART;TZID=Europe/London:20241023T150000 +DTEND;TZID=Europe/London:20241023T160000 +TRANSP:OPAQUE +X-MOZ-GENERATION:6 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT15M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT45M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/088.ics b/resources/icals/088.ics new file mode 100644 index 0000000..9c5288a --- /dev/null +++ b/resources/icals/088.ics @@ -0,0 +1,55 @@ +BEGIN:VCALENDAR +X-SOURCE-URL:https://github.com/pimutils/khal/issues/152#issuecomment-933635248 +VERSION:2.0 +PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN +BEGIN:VTIMEZONE +TZID:Western/Central Europe +BEGIN:STANDARD +DTSTART:19501029T020000 +RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19500326T020000 +RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=3 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +SUMMARY:(omitted) +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 +DTSTAMP:20211004T150245Z +UID:BF5109494E67AAE20025875100566D31-Lotus_Notes_Generated +RECURRENCE-ID;RANGE=THISANDFUTURE:20211101T150000Z +SEQUENCE:0 +RDATE;TZID="Western/Central Europe";VALUE=PERIOD:20211101T160000/20211101T + 163000,20211206T160000/20211206T163000,20220103T160000/20220103T163000,202 + 20207T160000/20220207T163000 +ATTENDEE;CN="(omitted)";PARTSTAT=ACCEPTED;ROLE=CHAIR;RSVP=FAL + SE:mailto:omitted@example.com +CLASS:PUBLIC +TRANSP:OPAQUE +X-LOTUS-APPTTYPE:3 +X-LOTUS-AUDIOVIDEOFLAGS:0 +X-LOTUS-BROADCAST:FALSE +X-LOTUS-CHANGE-INST-DATES:20211101T150000Z\,20211206T150000Z\,20220103T150 + 000Z\,20220207T150000Z +X-LOTUS-CHILD-UID:567EFBAF6CBD07FC0025875100566D3B +X-LOTUS-INITIAL-RDATES:20211101T150000Z\,20211206T150000Z\,20220103T150000 + Z\,20220207T150000Z +X-LOTUS-LASTALL-RDATES;TZID="Western/Central Europe":20211101T160000\,2021 + 1206T160000\,20220103T160000\,20220207T160000 +X-LOTUS-NOTESVERSION:2 +X-LOTUS-NOTICETYPE:I +X-LOTUS-RECURID;RANGE=THISANDFUTURE:20211101T150000Z +X-LOTUS-UPDATE-SEQ:2 +X-LOTUS-UPDATE-WISL:$W:1\;$O:1\;$M:1\;RequiredAttendees:1\;INetRequiredNam + es:1\;AltRequiredNames:1\;StorageRequiredNames:1\;OptionalAttendees:1\;INe + tOptionalNames:1\;AltOptionalNames:1\;StorageOptionalNames:1\;ApptUNIDURL: + 1\;STUnyteConferenceURL:1\;STUnyteConferenceID:1\;SametimeType:1\;WhiteBoa + rdContent:1\;STRoomName:1\;$S:2\;$B:2\;$L:2\;$E:2\;$R:2 +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/159.ics b/resources/icals/159.ics new file mode 100644 index 0000000..3a22d08 --- /dev/null +++ b/resources/icals/159.ics @@ -0,0 +1,436 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Last.fm Limited Event Feeds//NONSGML//EN +X-WR-CALNAME:Last.fm Events +X-WR-CALDESC:Event listing - supplied by http://www.Last.fm + +BEGIN:VEVENT +UID:LFMEVENT-1407206 +URL;VALUE=URI:http://www.last.fm/event/1407206+Gerard+van+Maasakkers+en+de+vaste+mannen+-+Deze+Jongen +DTSTAMP:20100210T203000Z +DTSTART:20100210T203000 +DTEND:20100210T235900 +SUMMARY:Gerard van Maasakkers en de vaste mannen - Deze Jongen +DESCRIPTION:Gerard van Maasakkers + \n\nhttp://www.last.fm/event/1407206+Gerard+van+Maasakkers+en+de+vaste+mannen+-+Deze+Jongen +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1361665 +URL;VALUE=URI:http://www.last.fm/event/1361665+Millennium+Jazz+Orchestra+en+Frank+Marocco +DTSTAMP:20100212T200000Z +DTSTART:20100212T200000 +DTEND:20100212T235900 +SUMMARY:Millennium Jazz Orchestra en Frank Marocco +DESCRIPTION:Millennium Jazz Orchestra\, Frank Marocco + \n\nhttp://www.last.fm/event/1361665+Millennium+Jazz+Orchestra+en+Frank+Marocco +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1358028 +URL;VALUE=URI:http://www.last.fm/event/1358028+Zu+at+Burgerweeshuis+on+12+February+2010 +DTSTAMP:20100212T210000Z +DTSTART:20100212T210000 +DTEND:20100212T235900 +SUMMARY:Zu + The New Earth Group at Burgerweeshuis +DESCRIPTION:Zu\, The New Earth Group + \n\nhttp://www.last.fm/event/1358028+Zu+at+Burgerweeshuis+on+12+February+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1319333 +URL;VALUE=URI:http://www.last.fm/event/1319333+Fink+at+Burgerweeshuis+on+13+February+2010 +DTSTAMP:20100213T210000Z +DTSTART:20100213T210000 +DTEND:20100213T235900 +SUMMARY:Fink at Burgerweeshuis +DESCRIPTION:Fink + \n\nhttp://www.last.fm/event/1319333+Fink+at+Burgerweeshuis+on+13+February+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1315921 +URL;VALUE=URI:http://www.last.fm/event/1315921+Love+For+Lunch +DTSTAMP:20100219T093200Z +DTSTART:20100219T000000 +DTEND:20100219T235900 +SUMMARY:Love For Lunch +DESCRIPTION:Gare du Nord + \n\nhttp://www.last.fm/event/1315921+Love+For+Lunch +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1361649 +URL;VALUE=URI:http://www.last.fm/event/1361649+Amsterdam+Klezmer+Band+at+Theater+Bouwkunde+on+19+February+2010 +DTSTAMP:20100219T203000Z +DTSTART:20100219T203000 +DTEND:20100219T235900 +SUMMARY:Amsterdam Klezmer Band at Theater Bouwkunde +DESCRIPTION:Amsterdam Klezmer Band + \n\nhttp://www.last.fm/event/1361649+Amsterdam+Klezmer+Band+at+Theater+Bouwkunde+on+19+February+2010 +LOCATION:Theater Bouwkunde\, Netherlands +GEO:6.153074;52.253485 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1334619 +URL;VALUE=URI:http://www.last.fm/event/1334619+Bettie+Serveert+at+Burgerweeshuis+on+19+February+2010 +DTSTAMP:20100219T210000Z +DTSTART:20100219T210000 +DTEND:20100219T235900 +SUMMARY:Bettie Serveert + The Monroes at Burgerweeshuis +DESCRIPTION:Bettie Serveert\, The Monroes + \n\nhttp://www.last.fm/event/1334619+Bettie+Serveert+at+Burgerweeshuis+on+19+February+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1358751 +URL;VALUE=URI:http://www.last.fm/event/1358751+Ethisch+Reveil +DTSTAMP:20100219T210000Z +DTSTART:20100219T210000 +DTEND:20100219T235900 +SUMMARY:Ethisch Reveil +DESCRIPTION:Gentle Veincut\, Fine China Superbone + \n\nhttp://www.last.fm/event/1358751+Ethisch+Reveil +LOCATION:Havenkwartier\, Netherlands +GEO:6.168775;52.24939 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1392314 +URL;VALUE=URI:http://www.last.fm/event/1392314+The+Monolith+Deathcult+at+Burgerweeshuis+on+26+February+2010 +DTSTAMP:20100226T150100Z +DTSTART:20100226T000000 +DTEND:20100226T235900 +SUMMARY:The Monolith Deathcult + Erebus at Burgerweeshuis +DESCRIPTION:The Monolith Deathcult\, Erebus + \n\nhttp://www.last.fm/event/1392314+The+Monolith+Deathcult+at+Burgerweeshuis+on+26+February+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1264138 +URL;VALUE=URI:http://www.last.fm/event/1264138+Loefah+at+Burgerweeshuis+on+27+February+2010 +DTSTAMP:20100227T163800Z +DTSTART:20100227T000000 +DTEND:20100227T235900 +SUMMARY:Loefah + 2562 + more at Burgerweeshuis +DESCRIPTION:Loefah\, 2562\, Cavemen + \n\nhttp://www.last.fm/event/1264138+Loefah+at+Burgerweeshuis+on+27+February+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1391634 +URL;VALUE=URI:http://www.last.fm/event/1391634+JW+Roy+weet+het+zeker +DTSTAMP:20100227T203000Z +DTSTART:20100227T203000 +DTEND:20100227T235900 +SUMMARY:JW Roy weet het zeker +DESCRIPTION:JW Roy + \n\nhttp://www.last.fm/event/1391634+JW+Roy+weet+het+zeker +LOCATION:theater bouwkunde\, Netherlands +GEO:6.153073;52.253485 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1361670 +URL;VALUE=URI:http://www.last.fm/event/1361670+Millennium+Jazz+Orchestra+at+Theater+Bouwkunde+on+3+March+2010 +DTSTAMP:20100303T210000Z +DTSTART:20100303T210000 +DTEND:20100303T235900 +SUMMARY:Millennium Jazz Orchestra + Grant Stewart at Theater Bouwkunde +DESCRIPTION:Millennium Jazz Orchestra\, Grant Stewart + \n\nhttp://www.last.fm/event/1361670+Millennium+Jazz+Orchestra+at+Theater+Bouwkunde+on+3+March+2010 +LOCATION:Theater Bouwkunde\, Netherlands +GEO:6.153074;52.253485 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1283011 +URL;VALUE=URI:http://www.last.fm/event/1283011+Suimasen+at+Burgerweeshuis+on+6+March+2010 +DTSTAMP:20100306T103300Z +DTSTART:20100306T000000 +DTEND:20100306T235900 +SUMMARY:Suimasen at Burgerweeshuis +DESCRIPTION:Suimasen + \n\nhttp://www.last.fm/event/1283011+Suimasen+at+Burgerweeshuis+on+6+March+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1302136 +URL;VALUE=URI:http://www.last.fm/event/1302136+El+Pino+and+the+Volunteers+at+Burgerweeshuis+on+6+March+2010 +DTSTAMP:20100306T203000Z +DTSTART:20100306T203000 +DTEND:20100306T235900 +SUMMARY:El Pino and the Volunteers + Coparck + more at Burgerweeshuis +DESCRIPTION:El Pino and the Volunteers\, Coparck\, Suimasen + \n\nhttp://www.last.fm/event/1302136+El+Pino+and+the+Volunteers+at+Burgerweeshuis+on+6+March+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1323437 +URL;VALUE=URI:http://www.last.fm/event/1323437+Wouter+Hamel+theatertour +DTSTAMP:20100311T200000Z +DTSTART:20100311T200000 +DTEND:20100311T235900 +SUMMARY:Wouter Hamel theatertour +DESCRIPTION:Wouter Hamel + \n\nhttp://www.last.fm/event/1323437+Wouter+Hamel+theatertour +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1400318 +URL;VALUE=URI:http://www.last.fm/event/1400318+Dobet+Gnahor%C3%A9 +DTSTAMP:20100312T192400Z +DTSTART:20100312T000000 +DTEND:20100312T235900 +SUMMARY:Dobet Gnahoré +DESCRIPTION:Dobet Gnahoré + \n\nhttp://www.last.fm/event/1400318+Dobet+Gnahor%C3%A9 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1347202 +URL;VALUE=URI:http://www.last.fm/event/1347202+Waylon +DTSTAMP:20100319T203000Z +DTSTART:20100319T203000 +DTEND:20100319T235900 +SUMMARY:Waylon +DESCRIPTION:Waylon + \n\nhttp://www.last.fm/event/1347202+Waylon +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1359365 +URL;VALUE=URI:http://www.last.fm/event/1359365+NO+blues+%40+Bouwkunde +DTSTAMP:20100320T113800Z +DTSTART:20100320T000000 +DTEND:20100320T235900 +SUMMARY:NO blues @ Bouwkunde +DESCRIPTION:No Blues + \n\nhttp://www.last.fm/event/1359365+NO+blues+%40+Bouwkunde +LOCATION:Theater Bouwkunde\, Netherlands +GEO:6.153074;52.253485 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1394328 +URL;VALUE=URI:http://www.last.fm/event/1394328+Schwarzblut+at+Burgerweeshuis+on+20+March+2010 +DTSTAMP:20100320T200000Z +DTSTART:20100320T200000 +DTEND:20100320T235900 +SUMMARY:Schwarzblut + Deadcell + more at Burgerweeshuis +DESCRIPTION:Schwarzblut\, Deadcell\, animAdverto + \n\nhttp://www.last.fm/event/1394328+Schwarzblut+at+Burgerweeshuis+on+20+March+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1352883 +URL;VALUE=URI:http://www.last.fm/event/1352883+King+Me+-+Cords+Live +DTSTAMP:20100326T200000Z +DTSTART:20100326T200000 +DTEND:20100326T235900 +SUMMARY:King Me & Cords Live +DESCRIPTION:King Me\, Cords + \n\nhttp://www.last.fm/event/1352883+King+Me+-+Cords+Live +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1322037 +URL;VALUE=URI:http://www.last.fm/event/1322037+The+Opposites +DTSTAMP:20100403T203000Z +DTSTART:20100403T203000 +DTEND:20100403T235900 +SUMMARY:The Opposites +DESCRIPTION:The Opposites + \n\nhttp://www.last.fm/event/1322037+The+Opposites +LOCATION:Burgerweeshuis\, Netherlands +GEO:; +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1387725 +URL;VALUE=URI:http://www.last.fm/event/1387725+Isbells+at+Burgerweeshuis+on+17+April+2010 +DTSTAMP:20100417T210000Z +DTSTART:20100417T210000 +DTEND:20100417T235900 +SUMMARY:Isbells at Burgerweeshuis +DESCRIPTION:Isbells + \n\nhttp://www.last.fm/event/1387725+Isbells+at+Burgerweeshuis+on+17+April+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1326470 +URL;VALUE=URI:http://www.last.fm/event/1326470+Anathema+at+Burgerweeshuis+on+22+April+2010 +DTSTAMP:20100422T210000Z +DTSTART:20100422T210000 +DTEND:20100422T235900 +SUMMARY:Anathema at Burgerweeshuis +DESCRIPTION:Anathema + \n\nhttp://www.last.fm/event/1326470+Anathema+at+Burgerweeshuis+on+22+April+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1400310 +URL;VALUE=URI:http://www.last.fm/event/1400310+Michiel+Borstlap+Eldorado +DTSTAMP:20100423T210000Z +DTSTART:20100423T210000 +DTEND:20100423T235900 +SUMMARY:Michiel Borstlap Eldorado +DESCRIPTION:Michiel Borstlap + \n\nhttp://www.last.fm/event/1400310+Michiel+Borstlap+Eldorado +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1067247 +URL;VALUE=URI:http://www.last.fm/event/1067247+Heideroosjes+at+Burgerweeshuis+on+9+May+2010 +DTSTAMP:20100509T203000Z +DTSTART:20100509T203000 +DTEND:20100509T235900 +SUMMARY:Heideroosjes at Burgerweeshuis +DESCRIPTION:Heideroosjes + \n\nhttp://www.last.fm/event/1067247+Heideroosjes+at+Burgerweeshuis+on+9+May+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1334621 +URL;VALUE=URI:http://www.last.fm/event/1334621+Roosbeef+at+Deventer+Schouwburg+on+11+May+2010 +DTSTAMP:20100511T200000Z +DTSTART:20100511T200000 +DTEND:20100511T235900 +SUMMARY:Roosbeef at Deventer Schouwburg +DESCRIPTION:Roosbeef + \n\nhttp://www.last.fm/event/1334621+Roosbeef+at+Deventer+Schouwburg+on+11+May+2010 +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1136119 +URL;VALUE=URI:http://www.last.fm/event/1136119+Heideroosjes +DTSTAMP:20100519T203000Z +DTSTART:20100519T203000 +DTEND:20100519T235900 +SUMMARY:Heideroosjes +DESCRIPTION:De Heideroosjes + \n\nhttp://www.last.fm/event/1136119+Heideroosjes +LOCATION:Deventer Schouwburg\, Netherlands +GEO:6.161613;52.255862 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1327278 +URL;VALUE=URI:http://www.last.fm/event/1327278+Aesun+at+Burgerweeshuis+on+9+June+2010 +DTSTAMP:20100609T112900Z +DTSTART:20100609T000000 +DTEND:20100609T235900 +SUMMARY:Aesun at Burgerweeshuis +DESCRIPTION:Aesun + \n\nhttp://www.last.fm/event/1327278+Aesun+at+Burgerweeshuis+on+9+June+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1334624 +URL;VALUE=URI:http://www.last.fm/event/1334624+Peter+Pan+Speedrock+at+Burgerweeshuis+on+11+June+2010 +DTSTAMP:20100611T203000Z +DTSTART:20100611T203000 +DTEND:20100611T235900 +SUMMARY:Peter Pan Speedrock + Paceshifters at Burgerweeshuis +DESCRIPTION:Peter Pan Speedrock\, Paceshifters + \n\nhttp://www.last.fm/event/1334624+Peter+Pan+Speedrock+at+Burgerweeshuis+on+11+June+2010 +LOCATION:Burgerweeshuis\, Netherlands +GEO:6.155862;52.254341 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1405016 +URL;VALUE=URI:http://www.last.fm/event/1405016+Ground+Zero+Festival +DTSTAMP:20100703T133200Z +DTSTART:20100703T000000 +DTEND:20100703T235900 +SUMMARY:Ground Zero Festival +DESCRIPTION:TBA + \n\nhttp://www.last.fm/event/1405016+Ground+Zero+Festival +LOCATION:Recreatieplas Bussloo\, Netherlands +GEO:6.1333333;52.2 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1398450 +URL;VALUE=URI:http://www.last.fm/event/1398450+Xnoizz+Flevo+Festival +DTSTAMP:20100819T191900Z +DTSTART:20100819T000000 +DTEND:20100822T235900 +SUMMARY:Xnoizz Flevo Festival +DESCRIPTION:Nog Onbekend + \n\nhttp://www.last.fm/event/1398450+Xnoizz+Flevo+Festival +LOCATION:Recreatieplas Bussloo\, Netherlands +GEO:6.1333333;52.2 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1135861 +URL;VALUE=URI:http://www.last.fm/event/1135861+A+Campingflight+to+Lowlands+Paradise+2010 +DTSTAMP:20100820T160500Z +DTSTART:20100820T000000 +DTEND:20100822T235900 +SUMMARY:A Campingflight to Lowlands Paradise 2010 +DESCRIPTION:Kees van Hondt + \n\nhttp://www.last.fm/event/1135861+A+Campingflight+to+Lowlands+Paradise+2010 +LOCATION:Evenemententerrein Walibi World\, Netherlands +GEO:6.227748;52.24453 +END:VEVENT + +BEGIN:VEVENT +UID:LFMEVENT-1397948 +URL;VALUE=URI:http://www.last.fm/event/1397948+Clock+live%40+Kajak +DTSTAMP:20100917T141500Z +DTSTART:20100917T000000 +DTEND:20100917T235900 +SUMMARY:Clock live@ Kajak +DESCRIPTION:Clockrockz + \n\nhttp://www.last.fm/event/1397948+Clock+live%40+Kajak +LOCATION:Kajak\, Netherlands +GEO:6.092868;52.236304 +END:VEVENT + +END:VCALENDAR \ No newline at end of file diff --git a/resources/icals/162.ics b/resources/icals/162.ics new file mode 100644 index 0000000..dc5c31c --- /dev/null +++ b/resources/icals/162.ics @@ -0,0 +1,558 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +BEGIN:VEVENT +UID:20060425T220628Z-5616-1000-1-25@embrace +DTSTART;VALUE=DATE:20061004 +DTEND;VALUE=DATE:20061005 +SEQUENCE:3 +SUMMARY:GNOME 2.16.1 Stable Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220833Z-5616-1000-1-90@embrace +DTSTART;VALUE=DATE:20070108 +DTEND;VALUE=DATE:20070109 +SEQUENCE:4 +SUMMARY:GNOME 2.17.5 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.17.5 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154249Z-17150-100-8562-140@embrace +DESCRIPTION:GNOME 2.17.5 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T221010Z-5616-1000-1-48@embrace +DTSTART;VALUE=DATE:20070110 +DTEND;VALUE=DATE:20070111 +SEQUENCE:4 +SUMMARY:GNOME 2.17.5 Development Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041844 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220746Z-5616-1000-1-32@embrace +DTSTART;VALUE=DATE:20061204 +DTEND;VALUE=DATE:20061205 +SEQUENCE:4 +SUMMARY:GNOME 2.17.3 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.17.3 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154209Z-17150-100-8562-122@embrace +DESCRIPTION:GNOME 2.17.3 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220728Z-5616-1000-1-30@embrace +DTSTART;VALUE=DATE:20061120 +DTEND;VALUE=DATE:20061121 +SEQUENCE:4 +SUMMARY:GNOME 2.16.2 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.16.2 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154149Z-17150-100-8562-113@embrace +DESCRIPTION:GNOME 2.16.2 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220857Z-5616-1000-1-38@embrace +DTSTART;VALUE=DATE:20070122 +DTEND;VALUE=DATE:20070123 +SEQUENCE:4 +SUMMARY:GNOME 2.18.0 Beta 1 (2.17.90) Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.18.0 Beta 1 (2.17.90) Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154337Z-17150-100-8562-161@embrace +DESCRIPTION:GNOME 2.18.0 Beta 1 (2.17.90) Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T221522Z-5616-1000-1-53@embrace +DTSTART;VALUE=DATE:20061218 +DTEND;VALUE=DATE:20061219 +SEQUENCE:5 +SUMMARY:UI Change Announcement Period +CLASS:PUBLIC +LAST-MODIFIED:20060830T041923 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +DESCRIPTION:UI Change Announcement Period: All user interface changes + must be announced to gnome-doc-list@. +END:VEVENT +BEGIN:VEVENT +UID:20060425T221023Z-5616-1000-1-45@embrace +DTSTART;VALUE=DATE:20070228 +DTEND;VALUE=DATE:20070301 +SEQUENCE:3 +SUMMARY:GNOME 2.18.0 Release Candidate (2.17.92) +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T221110Z-5616-1000-1-48@embrace +DTSTART;VALUE=DATE:20070314 +DTEND;VALUE=DATE:20070315 +SEQUENCE:3 +SUMMARY:GNOME 2.18.0 Final Release! +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060826T130552Z-26732-100-1-50@embrace +DTSTAMP:20060826T130552Z +DTSTART;VALUE=DATE:20070411 +DTEND;VALUE=DATE:20070412 +TRANSP:TRANSPARENT +SEQUENCE:3 +SUMMARY:Gnome 2.18.1 Stable Release +CLASS:PUBLIC +CREATED:20060830T041843 +LAST-MODIFIED:20060830T041843 +END:VEVENT +BEGIN:VEVENT +UID:20060425T220931Z-5616-1000-1-39@embrace +DTSTART;VALUE=DATE:20070124 +DTEND;VALUE=DATE:20070125 +SEQUENCE:3 +SUMMARY:GNOME 2.18.0 Beta 1 (2.17.90) Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220950Z-5616-1000-1-41@embrace +DTSTART;VALUE=DATE:20070131 +DTEND;VALUE=DATE:20070201 +SEQUENCE:3 +SUMMARY:GNOME 2.16.3 Stable Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220719Z-5616-1000-1-29@embrace +DTSTART;VALUE=DATE:20061108 +DTEND;VALUE=DATE:20061109 +SEQUENCE:3 +SUMMARY:GNOME 2.17.2 Development Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060825T153859Z-23493-100-1-52@embrace +DTSTAMP:20060825T153859Z +DTSTART;VALUE=DATE-TIME; + TZID=/softwarestudio.org/Olson_20011030_5/America/New_York: + 20061007 +DTEND;VALUE=DATE-TIME; + TZID=/softwarestudio.org/Olson_20011030_5/America/New_York: + 20061009T235900 +SUMMARY:Boston Summit 2006 +CREATED:20060830T041843 +LAST-MODIFIED:20060830T041843 +CLASS:PUBLIC +TRANSP:TRANSPARENT +SEQUENCE:2 +DESCRIPTION:http://live.gnome.org/Boston2006 +END:VEVENT +BEGIN:VEVENT +UID:20060425T221353Z-5616-1000-1-51@embrace +DTSTART;VALUE=DATE:20061016 +DTEND;VALUE=DATE:20061017 +SEQUENCE:3 +SUMMARY:End of new (app) modules proposal period +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T222220Z-5616-1000-1-58@embrace +DTSTART;VALUE=DATE:20070122 +DTEND;VALUE=DATE:20070123 +SEQUENCE:3 +SUMMARY:UI Freeze +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:UI Freeze: No UI changes may be made without approval from + the release-team and notification to the GDP. +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T221534Z-5616-1000-1-54@embrace +DTSTART;VALUE=DATE:20061218 +DTEND;VALUE=DATE:20061219 +SEQUENCE:5 +SUMMARY:Module inclusion discussion heats up. +CLASS:PUBLIC +LAST-MODIFIED:20060830T041915 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220717Z-5616-1000-1-29@embrace +DTSTART;VALUE=DATE:20070214 +DTEND;VALUE=DATE:20070215 +SEQUENCE:2 +SUMMARY:GNOME 2.18.0 Beta 2 (2.17.91) +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T221117Z-5616-1000-1-49@embrace +DTSTART;VALUE=DATE:20070409 +DTEND;VALUE=DATE:20070410 +SEQUENCE:4 +SUMMARY:GNOME 2.18.1 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.18.1 Tarballs Due +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220832Z-5616-1000-1-36@embrace +DTSTART;VALUE=DATE:20061218 +DTEND;VALUE=DATE:20061219 +SEQUENCE:4 +SUMMARY:GNOME 2.17.4 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.17.4 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154230Z-17150-100-8562-131@embrace +DESCRIPTION:GNOME 2.17.4 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220644Z-5616-1000-1-26@embrace +DTSTART;VALUE=DATE:20061016 +DTEND;VALUE=DATE:20061017 +SEQUENCE:6 +SUMMARY:GNOME 2.17.1 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.17.1 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T153408Z-17150-100-8562-82@embrace +DESCRIPTION:GNOME 2.17.1 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220752Z-5616-1000-1-33@embrace +DTSTART;VALUE=DATE:20061206 +DTEND;VALUE=DATE:20061207 +SEQUENCE:3 +SUMMARY:GNOME 2.17.3 Development Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220658Z-5616-1000-1-27@embrace +DTSTART;VALUE=DATE:20061018 +DTEND;VALUE=DATE:20061019 +SEQUENCE:3 +SUMMARY:GNOME 2.17.1 Development Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T222230Z-5616-1000-1-59@embrace +DTSTART;VALUE=DATE:20070115 +DTEND;VALUE=DATE:20070116 +SEQUENCE:3 +SUMMARY:New APIs must be fully documented +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220736Z-5616-1000-1-31@embrace +DTSTART;VALUE=DATE:20061122 +DTEND;VALUE=DATE:20061123 +SEQUENCE:3 +SUMMARY:GNOME 2.16.2 Stable Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T222150Z-5616-1000-1-56@embrace +DTSTART;VALUE=DATE:20070108 +DTEND;VALUE=DATE:20070109 +SEQUENCE:4 +SUMMARY:API/ABI Freeze +CLASS:PUBLIC +LAST-MODIFIED:20060830T041946 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +DESCRIPTION:API/ABI Freeze: developer APIs should be frozen at this + point. +END:VEVENT +BEGIN:VEVENT +UID:20060425T221019Z-5616-1000-1-44@embrace +DTSTART;VALUE=DATE:20070226 +DTEND;VALUE=DATE:20070227 +SEQUENCE:4 +SUMMARY:GNOME 2.18.0 Release Candidate (2.17.92) Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.18.0 Release Candidate (2.17.92) Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154458Z-17150-100-8562-197@embrace +DESCRIPTION:GNOME 2.18.0 Release Candidate (2.17.92) Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T221031Z-5616-1000-1-46@embrace +DTSTART;VALUE=DATE:20070305 +DTEND;VALUE=DATE:20070306 +SEQUENCE:4 +SUMMARY:Hard Code Freeze +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:Hard Code Freeze: no source code changes can be made without + approval from the release-team. Translation and documentation can + continue. +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220955Z-5616-1000-1-42@embrace +DTSTART;VALUE=DATE:20070212 +DTEND;VALUE=DATE:20070213 +SEQUENCE:4 +SUMMARY:GNOME 2.18.0 Beta 2 (2.17.91) Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.18.0 Beta 2 (2.17.91) Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154441Z-17150-100-8562-188@embrace +DESCRIPTION:GNOME 2.18.0 Beta 2 (2.17.91) Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T221051Z-5616-1000-1-47@embrace +DTSTART;VALUE=DATE:20070312 +DTEND;VALUE=DATE:20070313 +SEQUENCE:6 +SUMMARY:GNOME 2.18.0 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +DESCRIPTION:Hard Code Freeze ends\, but other freezes remain in effect + for the stable branch. +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T155041Z-17150-100-8562-228@embrace +DESCRIPTION:GNOME 2.18.0 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220850Z-5616-1000-1-37@embrace +DTSTART;VALUE=DATE:20061220 +DTEND;VALUE=DATE:20061221 +SEQUENCE:3 +SUMMARY:GNOME 2.17.4 Development Release +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T222207Z-5616-1000-1-57@embrace +DTSTART;VALUE=DATE:20070108 +DTEND;VALUE=DATE:20070109 +SEQUENCE:4 +SUMMARY:Feature and Module Freeze +CLASS:PUBLIC +LAST-MODIFIED:20060830T041953 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +DESCRIPTION:Feature and Module Freeze: new modules and functionality are + chosen now. +END:VEVENT +BEGIN:VEVENT +UID:20060425T220941Z-5616-1000-1-40@embrace +DTSTART;VALUE=DATE:20070129 +DTEND;VALUE=DATE:20070130 +SEQUENCE:4 +SUMMARY:GNOME 2.16.3 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.16.3 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154417Z-17150-100-8562-179@embrace +DESCRIPTION:GNOME 2.16.3 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T221456Z-5616-1000-1-52@embrace +DTSTART;VALUE=DATE:20061218 +DTEND;VALUE=DATE:20061219 +SEQUENCE:5 +SUMMARY:String Change Announcement Period +CLASS:PUBLIC +LAST-MODIFIED:20060830T041858 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +TRANSP:TRANSPARENT +DESCRIPTION:String Change Announcement Period: All string changes must be + announced to both gnome-i18n@ and gnome-doc-list@. +END:VEVENT +BEGIN:VEVENT +UID:20060425T221625Z-5616-1000-1-55@embrace +DTSTART;VALUE=DATE:20070212 +DTEND;VALUE=DATE:20070213 +SEQUENCE:3 +SUMMARY:String Freeze +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:String Freeze: no string changes may be made without + confirmation from the l10n team (gnome-i18n@) and notification to both + the release team and the GDP (gnome-doc-list@). +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +UID:20060425T220717Z-5616-1000-1-28@embrace +DTSTART;VALUE=DATE:20061002 +DTEND;VALUE=DATE:20061003 +SEQUENCE:5 +SUMMARY:GNOME 2.16.1 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +X-EVOLUTION-ALARM-UID:20060425T221751Z-9858-1000-1-227@embrace +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.16.1 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154129Z-17150-100-8562-104@embrace +DESCRIPTION:GNOME 2.16.1 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:20060425T220714Z-5616-1000-1-28@embrace +DTSTART;VALUE=DATE:20061106 +DTEND;VALUE=DATE:20061107 +SEQUENCE:4 +SUMMARY:GNOME 2.17.2 Tarballs Due +CLASS:PUBLIC +LAST-MODIFIED:20060830T041843 +X-EVOLUTION-ALARM-UID:20060425T221750Z-9858-1000-1-227@embrace +DTSTAMP:20060825T152756Z +CREATED:20060830T041843 +DESCRIPTION:GNOME 2.17.2 Tarballs Due +TRANSP:TRANSPARENT +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20060825T154128Z-17150-100-8562-104@embrace +DESCRIPTION:GNOME 2.17.2 Tarballs Due +ACTION:DISPLAY +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/178.ics b/resources/icals/178.ics new file mode 100644 index 0000000..310812c --- /dev/null +++ b/resources/icals/178.ics @@ -0,0 +1,190 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME:Belgische feestdagen +PRODID:-//Apple Computer\, Inc//iCal 1.5//EN +X-WR-RELCALID:440DCB50-039E-11D8-88DC-000A957CAA38-CALP +X-WR-TIMEZONE:Europe/Brussels +CALSCALE:GREGORIAN +X-WR-CALDESC:Alle officiële Belgische feestdagen van 2001 tot en met 200 + 5. +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041101 +DTEND;VALUE=DATE:20041102 +SUMMARY:Allerheiligen +UID:8684B909-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050101 +DTEND;VALUE=DATE:20050102 +SUMMARY:Nieuwjaar +UID:868590B6-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040520 +DTEND;VALUE=DATE:20040521 +SUMMARY:O.-L.-Heer Hemelvaart +UID:8684D7A0-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050711 +DTEND;VALUE=DATE:20050712 +SUMMARY:Feest van de Vlaamse Gemeenschap +UID:86850D80-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041115 +DTEND;VALUE=DATE:20041116 +SUMMARY:Feest van de Dynastie +UID:86856160-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040721 +DTEND;VALUE=DATE:20040722 +SUMMARY:Nationale Feestdag +UID:86857F3C-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040501 +DTEND;VALUE=DATE:20040502 +SUMMARY:Feest van de Arbeid +UID:86857095-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050721 +DTEND;VALUE=DATE:20050722 +SUMMARY:Nationale Feestdag +UID:86858207-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041225 +DTEND;VALUE=DATE:20041226 +SUMMARY:Christmas Day +UID:8685BD6D-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20051115 +DTEND;VALUE=DATE:20051116 +SUMMARY:Feest van de Dynastie +UID:8685648E-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040711 +DTEND;VALUE=DATE:20040712 +SUMMARY:Feest van de Vlaamse Gemeenschap +UID:86850AA0-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050501 +DTEND;VALUE=DATE:20050502 +SUMMARY:Feest van de Arbeid +UID:86857376-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20051101 +DTEND;VALUE=DATE:20051102 +SUMMARY:Allerheiligen +UID:8684BC36-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050327 +DTEND;VALUE=DATE:20050328 +SUMMARY:Pasen +UID:8685327B-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040531 +DTEND;VALUE=DATE:20040601 +SUMMARY:Pinkstermaandag +UID:86859C8A-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20051111 +DTEND;VALUE=DATE:20051112 +SUMMARY:Wapenstilstand +UID:8684CB8D-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20051225 +DTEND;VALUE=DATE:20051226 +SUMMARY:Kerstmis +UID:8684FEAD-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041225 +DTEND;VALUE=DATE:20041226 +SUMMARY:Kerstmis +UID:8684FB9D-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050927 +DTEND;VALUE=DATE:20050928 +SUMMARY:Feest van de Waalse Gemeenschap +UID:868523A1-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050516 +DTEND;VALUE=DATE:20050517 +SUMMARY:Pinkstermaandag +UID:86859F92-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050505 +DTEND;VALUE=DATE:20050506 +SUMMARY:O.-L.-Heer Hemelvaart +UID:8684DA8A-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040411 +DTEND;VALUE=DATE:20040412 +SUMMARY:Pasen +UID:86852F70-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040927 +DTEND;VALUE=DATE:20040928 +SUMMARY:Feest van de Waalse Gemeenschap +UID:86852086-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050328 +DTEND;VALUE=DATE:20050329 +SUMMARY:Paasmaandag +UID:868542CD-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041111 +DTEND;VALUE=DATE:20041112 +SUMMARY:Wapenstilstand +UID:8684C881-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040412 +DTEND;VALUE=DATE:20040413 +SUMMARY:Paasmaandag +UID:86853FA8-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050815 +DTEND;VALUE=DATE:20050816 +SUMMARY:Maria Tenhemelopneming +UID:8684EE84-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20050515 +DTEND;VALUE=DATE:20050516 +SUMMARY:Pinksteren +UID:8685B0B5-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040530 +DTEND;VALUE=DATE:20040531 +SUMMARY:Pinksteren +UID:8685AD9E-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040815 +DTEND;VALUE=DATE:20040816 +SUMMARY:Maria Tenhemelopneming +UID:8684E6C3-1F33-11D7-B218-000393A91E82-RID +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/182.ics b/resources/icals/182.ics new file mode 100644 index 0000000..239bb0f --- /dev/null +++ b/resources/icals/182.ics @@ -0,0 +1,331 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME:Dryway +PRODID:-//Apple Computer\, Inc//iCal 1.5//EN +X-WR-RELCALID:9618E652-9A12-11D9-B9F6-003065D7F79A-CALP +X-WR-TIMEZONE:US/Eastern +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:US/Eastern +LAST-MODIFIED:20050420T000902Z +BEGIN:DAYLIGHT +DTSTART:20050403T070000 +TZOFFSETTO:-0400 +TZOFFSETFROM:+0000 +TZNAME:EDT +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:20051030T020000 +TZOFFSETTO:-0500 +TZOFFSETFROM:-0400 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:20060402T010000 +TZOFFSETTO:-0400 +TZOFFSETFROM:-0500 +TZNAME:EDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20050321T140804Z +UID:9529B04C-9A10-11D9-B9F6-003065D7F79A-RID +DTSTART;TZID=US/Eastern:20050528T100000 +SUMMARY:Dry Way +DTEND;TZID=US/Eastern:20050528T150000 +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050624T100000 +SUMMARY:Dry Way +UID:CE57543A-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230707Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050612T100000 +SUMMARY:Dryway +UID:DF840B5C-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020207Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050618T100000 +SUMMARY:Dryway +UID:E9403F63-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020156Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050619T100000 +SUMMARY:Dryway +UID:EF7BAD31-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020211Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050625T100000 +SUMMARY:Dryway +UID:F34776C8-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020201Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050708T100000 +SUMMARY:Dry Way +UID:F4910F82-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230737Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050626T100000 +SUMMARY:Dryway +UID:F8A5E477-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020215Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050702T100000 +SUMMARY:Dryway +UID:FA4FEEB4-9A5D-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020223Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050703T100000 +SUMMARY:Dryway +UID:02390F02-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020220Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050710T100000 +SUMMARY:Dry Way +UID:052543DA-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230741Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050807T100000 +SUMMARY:Dry Way +UID:088A8B2D-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230902Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050717T100000 +SUMMARY:Dry Way +UID:0AE6CC6A-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230748Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050729T100000 +SUMMARY:Dry Way +UID:0FBDCD3D-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230756Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050730T100000 +SUMMARY:Dry Way +UID:119B40AD-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230759Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050731T100000 +SUMMARY:Dry Way +UID:1AD30FA1-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230815Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050806T100000 +SUMMARY:Dry Way +UID:33CAF8EE-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230857Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050812T100000 +SUMMARY:Dry Way +UID:377F37E6-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230906Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050814T100000 +SUMMARY:Dry Way +UID:3B8484C7-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230915Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050820T100000 +SUMMARY:Dry Way +UID:3FB197BA-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230919Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050821T100000 +SUMMARY:Dry Way +UID:417A378D-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230922Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050904T100000 +SUMMARY:Dryway +UID:439E54E7-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T015827Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050826T100000 +SUMMARY:Dry Way +UID:468221CC-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230928Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050819T100000 +SUMMARY:Dry Way +UID:48966612-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T020128Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050828T100000 +SUMMARY:Dry Way +UID:4B4CE621-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T230936Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050903T100000 +SUMMARY:Dryway +UID:4EB4963F-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T015805Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050917T100000 +SUMMARY:Dryway +UID:527DD9D5-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T015833Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20051009T100000 +SUMMARY:Dry Way +UID:544182EE-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T231026Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20050918T100000 +SUMMARY:Dryway +UID:56D4C4F4-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050322T015840Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20051008T100000 +SUMMARY:Dry Way +UID:670DB0B6-9A5E-11D9-ACB4-003065D7F79A-RID +DTSTAMP:20050321T231023Z +DESCRIPTION:Friday releases are for four hours and begin at 11 a.m. Satu + rday releases last five hours and begin at 10 a.m. Sunday releases are f + our hours beginning at 10 a.m. Flows vary from 900-1100 cfs. +DURATION:PT5H +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/184.ics b/resources/icals/184.ics new file mode 100644 index 0000000..fbee29e --- /dev/null +++ b/resources/icals/184.ics @@ -0,0 +1,116 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME:EXDATE Example +PRODID:-//Apple Computer\, Inc//iCal 2.0//EN +X-WR-RELCALID:6E3F1EC4-081F-4DBF-8261-8938E9B32A70 +X-WR-TIMEZONE:Australia/Brisbane +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VTIMEZONE +TZID:Australia/Brisbane +LAST-MODIFIED:20051209T055354Z +BEGIN:STANDARD +DTSTART:19321213T204552 +TZOFFSETTO:+1000 +TZOFFSETFROM:+0000 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19420101T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19420329T020000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19420927T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19430328T020000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19431003T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19440326T020000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19711031T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19720227T030000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19891029T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19900304T030000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19901028T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19910303T030000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19911027T030000 +TZOFFSETTO:+1100 +TZOFFSETFROM:+1000 +TZNAME:EST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19920301T030000 +TZOFFSETTO:+1000 +TZOFFSETFROM:+1100 +TZNAME:EST +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DURATION:PT1H +DTSTAMP:20051209T055346Z +EXDATE;TZID=Australia/Brisbane:20060103T120000 +EXDATE;TZID=Australia/Brisbane:20060104T120000 +UID:DDC5B090-1B46-4C1D-8A3C-D305516A0B90 +SEQUENCE:9 +DTSTART;TZID=Australia/Brisbane:20060101T120000 +SUMMARY:Repeating Event +DESCRIPTION:This is a simple repeating event with a couple of days exclu + ded. +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=7 +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/195.ics b/resources/icals/195.ics new file mode 100644 index 0000000..5236373 --- /dev/null +++ b/resources/icals/195.ics @@ -0,0 +1,220 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME:2005 Chicago Bears Schedule +PRODID:-//Apple Computer\, Inc//iCal 1.5//EN +X-WR-RELCALID:202B7038-AFA2-11D9-A2CD-000A95AD128C +X-WR-TIMEZONE:Canada/Mountain +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VTIMEZONE +TZID:Canada/Mountain +LAST-MODIFIED:20050418T003804Z +BEGIN:DAYLIGHT +DTSTART:20050403T090000 +TZOFFSETTO:-0600 +TZOFFSETFROM:+0000 +TZNAME:MDT +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:20051030T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0600 +TZNAME:MST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:20060402T010000 +TZOFFSETTO:-0600 +TZOFFSETFROM:-0700 +TZNAME:MDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050808T180000 +DTEND;TZID=Canada/Mountain:20050808T210000 +SUMMARY:Dolphins @ Bears +UID:202B352E-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:2 +DTSTAMP:20050418T000046Z +DESCRIPTION:Preseason +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050812T180000 +DTEND;TZID=Canada/Mountain:20050812T210000 +SUMMARY:Bears @ Rams +UID:202B39C2-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:2 +DTSTAMP:20050418T000056Z +DESCRIPTION:Preseason +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050820T180000 +DTEND;TZID=Canada/Mountain:20050820T210000 +SUMMARY:Bears @ Colts +UID:202B3BCB-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:2 +DTSTAMP:20050418T000107Z +DESCRIPTION:Preseason +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050826T180000 +DTEND;TZID=Canada/Mountain:20050826T210000 +SUMMARY:Bills @ Bears +UID:202B3DA8-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:3 +DTSTAMP:20050418T000120Z +DESCRIPTION:Preseason +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050901T180000 +DTEND;TZID=Canada/Mountain:20050901T210000 +SUMMARY:Browns @ Bears +UID:202B3F83-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:2 +DTSTAMP:20050418T000134Z +DESCRIPTION:Preseason +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050911T110000 +DTEND;TZID=Canada/Mountain:20050911T140000 +SUMMARY:Bears @ Redskins +UID:202B4156-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000400Z +DESCRIPTION:Week #1 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050918T110000 +DTEND;TZID=Canada/Mountain:20050918T140000 +SUMMARY:Lions @ Bears +UID:202B4354-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000354Z +DESCRIPTION:Week #2 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20050925T110000 +DTEND;TZID=Canada/Mountain:20050925T140000 +SUMMARY:Bengals @ Bears +UID:202B4522-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000509Z +DESCRIPTION:Week #3 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051009T110000 +DTEND;TZID=Canada/Mountain:20051009T140000 +SUMMARY:Bears @ Browns +UID:202B46E9-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000809Z +DESCRIPTION:Week #4 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051016T110000 +DTEND;TZID=Canada/Mountain:20051016T140000 +SUMMARY:Vikings @ Bears +UID:202B48CF-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000821Z +DESCRIPTION:Week #5 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051023T141500 +DTEND;TZID=Canada/Mountain:20051023T171500 +SUMMARY:Ravens @ Bears +UID:202B4AA6-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T000838Z +DESCRIPTION:Week #6 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051030T110000 +DTEND;TZID=Canada/Mountain:20051030T140000 +SUMMARY:Bears @ Lions +UID:202B4C7A-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001042Z +DESCRIPTION:Week #7 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051106T110000 +DTEND;TZID=Canada/Mountain:20051106T140000 +SUMMARY:Bears @ Saints +UID:202B4E54-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001115Z +DESCRIPTION:Week #8 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051113T110000 +DTEND;TZID=Canada/Mountain:20051113T140000 +SUMMARY:49ers @ Bears +UID:202B5047-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001149Z +DESCRIPTION:Week #9 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051120T110000 +DTEND;TZID=Canada/Mountain:20051120T140000 +SUMMARY:Panthers @ Bears +UID:202B5215-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001229Z +DESCRIPTION:Week #10 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051127T110000 +DTEND;TZID=Canada/Mountain:20051127T140000 +SUMMARY:Bears @ Buccaneers +UID:202B53E0-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001322Z +DESCRIPTION:Week #11 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051204T110000 +DTEND;TZID=Canada/Mountain:20051204T140000 +SUMMARY:Packers @ Bears +UID:202B55AB-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001408Z +DESCRIPTION:Week #12 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051211T110000 +DTEND;TZID=Canada/Mountain:20051211T140000 +SUMMARY:Bears @ Steelers +UID:202B5795-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001555Z +DESCRIPTION:Week #13 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051218T193000 +DTEND;TZID=Canada/Mountain:20051218T223000 +SUMMARY:Falcons @ Bears +UID:202B5963-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:8 +DTSTAMP:20050418T001943Z +DESCRIPTION:Week #14 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20051225T150000 +DTEND;TZID=Canada/Mountain:20051225T180000 +SUMMARY:Bears @ Packers +UID:202B5B31-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:3 +DTSTAMP:20050418T001737Z +DESCRIPTION:Week #15 +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Canada/Mountain:20060101T110000 +DTEND;TZID=Canada/Mountain:20060101T140000 +SUMMARY:Bears @ Vikings +UID:202B5D2A-AFA2-11D9-A2CD-000A95AD128C +SEQUENCE:4 +DTSTAMP:20050418T001843Z +DESCRIPTION:Week #16 +END:VEVENT +END:VCALENDAR diff --git a/resources/icals/224.ics b/resources/icals/224.ics new file mode 100644 index 0000000..039b144 --- /dev/null +++ b/resources/icals/224.ics @@ -0,0 +1,341 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME;VALUE=TEXT:Lord of the Rings +PRODID:-//Apple Computer\, Inc//iCal 1.0//EN +X-WR-RELCALID;VALUE=TEXT:B4B8ABF0-1ECC-11D7-A916-00306553216E +X-WR-TIMEZONE;VALUE=TEXT:America/New_York +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040105 +DTEND;VALUE=DATE:20040106 +SUMMARY:Fellowship enters Moria +UID:C1C7837D-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040109 +DTEND;VALUE=DATE:20040110 +SUMMARY:Fellowship reaches Lorien +UID:C1C78BF1-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040117 +DTEND;VALUE=DATE:20040118 +SUMMARY:Passing of Gandalf +UID:C1C79008-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040207 +DTEND;VALUE=DATE:20040208 +SUMMARY:Fellowship leaves Lorien +UID:C1C793E2-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040217 +DTEND;VALUE=DATE:20040218 +SUMMARY:Death of Boromir +UID:C1C797C6-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040220 +DTEND;VALUE=DATE:20040221 +SUMMARY:Meriadoc & Pippin meet Treebeard +UID:C1C79D1B-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040222 +DTEND;VALUE=DATE:20040223 +SUMMARY:Passing of King Ellesar +UID:C1C7A154-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040224 +DTEND;VALUE=DATE:20040225 +SUMMARY:Ents destroy Isengard +UID:C1C7A52F-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040226 +DTEND;VALUE=DATE:20040227 +SUMMARY:Aragorn takes the Paths of the Dead +UID:C1C7A939-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040305 +DTEND;VALUE=DATE:20040306 +SUMMARY:Frodo & Samwise encounter Shelob +UID:C1C7AD08-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040308 +DTEND;VALUE=DATE:20040309 +SUMMARY:Deaths of Denethor & Theoden +UID:C1C7B0EF-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040318 +DTEND;VALUE=DATE:20040319 +SUMMARY:Destruction of the Ring +UID:C1C7B4A3-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20030317;BYMONTH=3 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040329 +DTEND;VALUE=DATE:20040330 +SUMMARY:Flowering of the Mallorn +UID:C1C7B8A3-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040404 +DTEND;VALUE=DATE:20040405 +SUMMARY:Gandalf visits Bilbo +UID:C1C7BD03-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040417 +DTEND;VALUE=DATE:20040418 +SUMMARY:An unexpected party +UID:C1C7C0EF-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040423 +DTEND;VALUE=DATE:20040424 +SUMMARY:Crowning of King Ellesar +UID:C1C7C4DA-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040519 +DTEND;VALUE=DATE:20040520 +SUMMARY:Arwen leaves Lorian to wed King Ellesar +UID:C1C7C8A0-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040611 +DTEND;VALUE=DATE:20040612 +SUMMARY:Sauron attacks Osgilliath +UID:C1C7CC66-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040613 +DTEND;VALUE=DATE:20040614 +SUMMARY:Bilbo returns to Bag End +UID:C1C7D01C-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040623 +DTEND;VALUE=DATE:20040624 +SUMMARY:Wedding of Ellesar & Arwen +UID:C1C7D3CE-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040704 +DTEND;VALUE=DATE:20040705 +SUMMARY:Gandalf imprisoned by Saruman +UID:C1C7D799-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=7 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040724 +DTEND;VALUE=DATE:20040725 +SUMMARY:The ring comes to Bilbo +UID:C1C7DB68-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=7 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040726 +DTEND;VALUE=DATE:20040727 +SUMMARY:Bilbo rescued from Wargs by Eagles +UID:C1C7DF1F-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=7 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040803 +DTEND;VALUE=DATE:20040804 +SUMMARY:Funeral of King Theoden +UID:C1C7E2CC-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040829 +DTEND;VALUE=DATE:20040830 +SUMMARY:Saruman enters the Shire +UID:C1C7E6A6-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040910 +DTEND;VALUE=DATE:20040911 +SUMMARY:Gandalf escapes from Orthanc +UID:C1C7EA5F-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040914 +DTEND;VALUE=DATE:20040915 +SUMMARY:Frodo & Bilbo's birthday +UID:C1C7EE1A-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040915 +DTEND;VALUE=DATE:20040916 +SUMMARY:Black riders enter the Shire +UID:C1C7F1FC-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040918 +DTEND;VALUE=DATE:20040919 +SUMMARY:Frodo and company rescued by Bombadil +UID:C1C7F5C4-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20040928 +DTEND;VALUE=DATE:20040929 +SUMMARY:Frodo wounded at Weathertop +UID:C1C7F99E-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041016 +DTEND;VALUE=DATE:20041017 +SUMMARY:Boromir reaches Rivendell +UID:C1C7FD5C-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041017 +DTEND;VALUE=DATE:20041018 +SUMMARY:Council of Elrond +UID:C1C80115-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041025 +DTEND;VALUE=DATE:20041026 +SUMMARY:End of War of the Ring +UID:C1C804F4-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041116 +DTEND;VALUE=DATE:20041117 +SUMMARY:Bilbo reaches the Lonely Mountain +UID:C1C808BE-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041205 +DTEND;VALUE=DATE:20041206 +SUMMARY:Death of Smaug +UID:C1C80C80-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20041216 +DTEND;VALUE=DATE:20041217 +SUMMARY:Fellowship begins Quest +UID:C1C8107A-1ECC-11D7-A916-00306553216E +DTSTAMP:20040103T033800Z +SEQUENCE:1 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20030325 +DTEND;VALUE=DATE:20030326 +SUMMARY:Destruction of the Ring +UID:5EAE2BA6-D5D6-11D7-BA10-000393D17D20 +DTSTAMP:20030824T015752Z +SEQUENCE:2 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 +END:VEVENT +END:VCALENDAR diff --git a/resources/mailbox.gz b/resources/mailbox.gz new file mode 100644 index 0000000000000000000000000000000000000000..0a07e9a35aff9423d6d7fee7183190cb171ae8c5 GIT binary patch literal 2136385 zcmV)gK%~DPiwFpY(1B$D18rqNePsw z;!az*k%Vj|vgOB;Fn<5fT*RRk1WRY)e!n)&4KAMgI{eNIszkPzU+_8QNmB|+7> z?6c3lUrxiIuiBv-{p5%3cIe$(L3pjy)yPwS8TFKtS9Z0Quh!@nCzo@!x88eg&%g6J zHFfXDH>!Eucz<P~6dsHQm<;#lTt1gmd(GzM z)jo!eN8up*&W&$s>c#AW>f2ikp92cB30ld#E$EeGWRs)F3uzstYPp2f)30{9c-kl&=Nh@?Cp~dv zulCf_+gqnJBR%H#19j<#u0QYw>O~yUVcq)J<@VMir*k=K{lja=HTA20;P0yIhlDP% zNB`-&es6p0jTc4kwU<3RAvC}ea%CrPSMyaTZ~et_3VC}5FW&flFZ(wyjQn6wQ-p-u zTgNn~H;A(*el!dsKjv@zL626uz4d{>m2F(p_cirq6vqd)Du;h|u4tF>$OjyUt)Bgl_8YtLD5|N8?XB&to)k9Q;0CzS8eZHV2L-}C?J0@%tObW|{m{l5NyUi4@wp%#3Hf+)l^q(E6gG*Y@2W&2)Kr&f~kI_=Z+R zOXVq}c+~A$kCM)hUWe~b-d2uND^zL)do|Me5lf;`lFOJ3_nnZ=Q@dc>L^_@7Q>1ed zC~})7odu_PQqZJx*-3?TE_ga${Zgd!j%a4_ahjR8iB?wfRqORvO*8Y~G|kMfO*7>r zab($CIZHV7D3%bS{#l(32zEq0L8hY7a2SL!VKd0vtG6H2)%j(HWqTq;eC23{qGc1^ z`klg;$twh2p@Drr!rK-Pe&S!#yV*A<#aZ&#_hPzK{Ls*iqWey^>-W6br&@0NmO#pn z9W%8h936~?By#>Pt4`@Si1dON&<0wg>qstR-|G*9pvP3#4|+i`WKx}B_HggvIup(2 zEfB>9C+K-ERc)UKZK$qyPtS*RY*+)0N%W)_5I}A`mrj=y9*-#g4UJCNe4V1;A(PLa z;?V7O{dV8I9(b|UrY~3sZj6Ye7omiav;n*7R8P zKeaa++!BBX>Q{$kE4x^>?3`^CS5n9;$Ida4^Sr&3B&;67;~{68+JRlMnKHR?`-V<* zPnJx}b@C;PzBFrIu+xe~kCzVQhhDr{`*pcgeDiahxAW!Y%n7B*;&Ztg(fWKrWphOk z`h*a73?Z)MO1YxrSm{}Qn!C!n;S-l`=-#N0bT@ssA3cf;K!KgPi``kYiTSel#owC8 z7gyH1_ZP<(Z?E28oLi@7QwECkrfW{MW>@IID%kVS**7jx0wSWv04Z^Vfm}p&=U7l> z-<-W^E?_poHu|Gp><@|L9}r8UDLX$Z5bll}jt_UL#Zs%=CHh!#y>_-xDSBDASfSr? z-Huzb%U-+X?W9Hw-9gkPZq*zR38Cv=BUv<{F|)(aA28(rat(W9zCW|D=D$DP>%Zs1 zd>$Jsd?jpb5vn|gsTI~_YUU))y}>nznNx6VP~58<0tNlQE9afXffMED3qyj20-Aty z_gz{`HoMHZ&y_!J;(l);e)v(4tyGHDto=+J+1`5b`s&Sj9Y33m6a4&rmblGOWwUkK z@r$dotMg|4#ew{!4=%5MId7_~U*0tj^CvsJmUa~F=r;)fC+h#v25A9Zx9|7HHAR?0 zMApqHHzbapA%4}n^T11gl;FI(l_{aenb0tNG!S|M6n38b`Fwk7p-WuklqWm)lWC7w=D+{3QKx+&n+OeAhTW zd;2Q&$-BnMN%9Gg^Pc|uyBAj{_1FH3Bl$yqoT)pXt_F!9FBG;l z@erNQ5K|Dkk}yrOx6<2S$%OX?~$-4z!sm0O?5>wO7pyBt*A2p z%>QO}WTSAFR#B3VODDMslw@ZqHB&v@e{p92qA%|J%^g6DR_7!(42au30*X1h-Cza9ZLZA4s6QoWS(f#v)pC(wo>so*Ec{MBnR|+Jl~KD=%M}SY6K(We=o1l&$J)4f z?zcixBx~vjMlu>)y)%?_fxd3%a<*NxCdc*D!$V%62Nu5?J#gOnKSv8JZdeN}lE}kK z6|ex*2evuf#OQU8Pd7Rq+(* zVx&*>{7&-3dGqv&zVGtCIp4pB81dKuR~PT*zAp*z<=qb24Ded-QYvzj^ashj=q~&*s|e5Vn@A=$AhfDuv@xMLiVCW8El?fTWk|oUoX(14n1=7L?76B;@lpKI!^yFjyrxodY&%eGQEN=z9 z&VMr+9x!q+8#I{6I&;tK#>C_4IvwUnckBgj=Wu6-M2cbZX`g?&;UN#9aL5PuekZ=+ zzgyS5h{LqjjqCA(O$$mtST2)tBM{}c+Fq{*c#~|A{5^C#9sM_t@gffE!rql(^KnO+ zr8xQgjv3vdD`2#TxbxIYv|gHeaJVxWpj<5P7+C^uq?cZz(Vww&<@QLDIottkcao*Z zZq($@$@V*Alu~5P63~O57t^nwTGyiem=)!n za(Z8n?C;?&lfBko4Cg=n0w}DybQ2F7uoEemg$|uL6GH04!}W#lq(0yIo3TDM#QOBw zSf4T^%nqU~Jfz(nWpG*U{7#)7>jjw?t#JMV7JNP_!#<5EI!TruC`Sa|gEqjX-c8F@MM4n70?UwLY}! z@W%W>6uSeCt6;q^nqK$3;fxAcT+Y}tcZ_V7mP8Ohm<8#-S=K)B=0xwkcgL8@?a*8# zGB4Z4Au#4xqN4i}|mxt10OA-Oql1)K}hT zk{lV=f{r`>y(owe2RILcTPU2QGTf88YSdf)$Y{6=7qwtaO)xc)%xTI=lJ6huu#>?! zBOev%lP8GMvV=rP&;xqHn)tja6777!-e$CiMURQ@{vU3i1elcez6fAzAq>8shcucj zPyd)ZJuG|)-&+KH(j5Ie!&SA3Q{BnmeZSLT!DilTaIehl&t+fT;UgQ=uK&Y#7nfJ_ z$2G(IgW)LF0=LkW9W`+K^w*{SixxS;pKRGsZ-e{a|Ni&HVtQ^A(JK(k=P^ks0G2sMA4IU_6(dz^wZCH9yzPRyl`ia`!Z zaL*Mm7cI(+zu?l)lG7q&B3r^tx;(fZk(d_EkbeM!#jB@LhV>VI|5`<1dr3);ozi4_yNDeQ7og>r+52<15Mrsbo3K-Rs1(`mK1~*06WLSA40Gi$V{Z@J>M*^M z!Wu?_2?D>ba z2u&+1X&HRU^*$D%#+2Rz)O?2ClUm&(3|?^I&0fvKsWU6&=PI6$ylsO{t?_!k@XXg2 zobieqcGlYO^WRQycbT^r?)EV*H}_&Zmzb@?6KCF?eJk~R@?`p%7YD7NGZyoX7#tS{ zZBKhG@&$x*ZOm&S!+j?~+jjFfzg(2xJO{>nP^=lW-OQJd%jvYcIg>t;cDL~1&BtsL zA?~XKH5ztk`e}nHZITiX*29hGa*sza>u&}a2WPR*VhCEeleg= z!|kmo=*IVMNH;AS(F?Scpj*sDJy(A4pWGe>(&0X&TPS(6|zeKF^7s?ze)^Ca+=< zVJ{a81>1VnDBFd~9ISeTxGyHt^I}`)vVunfUn?y?p35r=3#fnjE1~{RAMbOxLV0bj zaO2~@LJ|?Q>3!zYVglO@dcEKtxD`RmqxHxo!FQqOc8F5PpX1v0)=nCg*nodWNGYaT zqS@_H7gpwEbUUkAw2>5baA83bwUU{&|v3bz~+)>Zh5_OE<3 z@Vw4Td^^V99BIlSu~^p3LI$w}q+wFdzQM5O%Kd+Q5cnjUC9wEm6!|JET#MF7@6xo) z9Q%m*=5RNUn2z5a^|S*eVFvUBsj0zFgik})YWwbW=v$*`XthGguVj``Ms_laB$U?3 zugsNGISppG$5_H6Yg>^q%-iMOybpi13wh!RWy{G`E$110A<58jPKx>E6E4+5*vtkW zO}4W48%U?hJB5NnH0Lh3l_-lxUx1m z-rjocTk0|hNm>QEgZ#AI$ZxCPMv&N%t2l~@KyuhEk-syNR)G>isJxJX_)>8?Ivt4@ z6X^_*r2@BU$L&Gr>9d@?C$eyhzz~l2UrMHnJCGt9kK~CPI#z-(vucTrhjh#g_nnv= znvLp7X+^Rnpjez(xT4A7#CD3!be{MUlcw^-7e4$U*TzJt?x5bERUzwylO; zDwK0G=nU2^&#~8X-e5r@=AhN*OsSlAm&AQ9Itf zF^b2!hLq}8LbO*iq*T|Y(HBcXI`creyAK1pP}^H|wWh#bR~r+#>nbgD!(UTZj|<-T zenrunpFsjXttLb+N&A)qoqBAW>2Z|O7@2S_4^SXh<#&D5S2^p-Q-+G%yTqv6Hr?dN zn1euAFubvI!Ia}kegzj*ff;JdW~iP=T)+o(p+wRrLoejWp7t%jrZ_ahtP|-hy`H6x z2&{gW2Vpajdh?;dW~zUTA{vI*I!+d2HmdeIUQc2-bkbBB&oj z_bu{UJue9pQ-ts~H;XW~3r_xFiA3K_Ud=nOICbDblI*agr{|Cg?@5 zI@5RAvZe#ayfJ+clTaWzDa1_p1fQ-QladK4HD|@-iMcWX5BHtO1V`1}NqKddU@j=u ztsPUyAD14J324$;-^XH^VDj+h-4I~@kE=dl{1vBQ9WTxJ*VUCa!Tpz5gBAfEvoI{= z{S{F)i6d!cbM4B7xo?{J+qo~t{byjEdGy4LBmi#ue2uTJR?_+EYL3?Tel$>i-yLTA zZWP1$u{^P_B7tdh#J(!|P~Qcy&lQiVh1H4uTu`j*bFLIB<;RFUP5MZou!Rr*yNG=? zm$Od(m)L(vV*kz7sTE?seihp9jo4q@NZNkt>DoT2sbkWP&UC%9i4MoZ$3dV_-6%}! z0gt*nu5L!cB`REuwNkTJ^?=`uh)YzaO!_k1_v-G*7b?x>a$a!2Mm>a0LOyG`lE<#L7jjt9|4j?@Eg%+#28(}6bA#^nXbj#mb!3E2vECe%)+vE5VKwC zj4-H&n36b1(P)^VyqJmpAIt*`Z(>VCC1_J28;hnX2A3z$rRY3PnVNG(^hh&myF-uB z)uksx7yx(w@85Unt3RoWzb2+7n3cx#p_?OlvKJ5ZE+TI#s znK9}#V@#(|Qq7;wF0U@lU+CO8bmaxm3)16g8DxvXIH-f zVJqcQu3V&t>GOyaT?`QL@cU-tB>VCG*;SKBnE<67pl-A*hMp@BDg}|Jf2Vhj@#pMQ zMI2r*im`~JUz&Vcz#jgHzn3FKgk1X7R8>Qsh6z4hA>LJu#iz5&BQ}n=kEI2wPf|P40L$Ydc^aByZ4ELQVs-@!5X=SN0Cs zPEBP|kX5JhlaEWB=B+~@tO{w+qfw6LRk1C@=GWgWCf=2QHd z{$}9an{VjX^fz&TXuhFeG5lLXX;b!I!rw_C>?!Xv$!^=c_s zTA-|_N6UlH<#W%EO`Ktvb}kuc1!et7c4+7Ayi;WZXt`WmaOSJ>#pSr}mox{=Wv7p> z=9vbBucco1rVI${*6bDoEpV7QUu7y5hb6zp8@XYb8L4j)iL+VWDe*JU;Ggosb9pGX z{*ib{2Bu$=8M4vN`>rYt-A9MrySSC$nT94j4YWUdDZ4G7`aR zWD#bi5vsdImL9g{A&fQvH}3_(ErO#F6@BmdVFzQU8ghuI$d4zX*d4g)2am z1Zr#7LW`yycrly}#wEu+)6g;v1)z+Z!iz`(JVWn-q~3rLwP^T~iUNX-h*-2>0-GN% zFKHm=31Lqgk~GY4--)DAY*d=d#Wz1>`inrZt|y^%>@1cv=1;mn(wKVq%e2NUJ=z)* ze6|Ynj`i1P+BN>gz0JSvIxF_Ng;pL0HI$vCk65Dip}I*s9&tt4blXdl2hvB;UASHM zin-jMldO#lCzcS2y9|iz(SVhG-9+fQM+_sPSe>qpd%or7!m1Z2X$BM48WrqBL?@y z+&jN`b@uk;>=JE0PKcKcxm|KR-j`|g=pZNUQ4~)yTvQjn{fzQlkn*4hrJj3TpEIC5@rmm$4J6k}&Q)sRDn4bgg z(JD|5iH2qtNHcRM=I&TeBjfi1U7QBZu_i&*a@N_ldNCw2U*};O#Iw%cG~TIXbRLyk ziH5psl}zD#Xc(hsnDAs?V>FKFD@!$fPEqOv2~Vb*%j_6)d?Kiyo}D+9H@Ne|U;tK~ zAzsQuT%+)Bwx`#oP)VonVbRlj5V&5#C}WzX;)&4f2V4$0Lf zDDpmg?GY|m`q=3Gpf%zaVqBq!#iG&m^;n_~ARd`ULrDY$1W5>3B>8y)Y(x(c-IW3R z?yVdL7J!n|EFSY9Nhj|qQytDPLxV`a@Bxf&G$`UMI*O%QA$Z^tFsXed!R`BYO_;AaO zCJ86^=$;sp$o@hpC11?vtyc}FQnIW61}D$63=_-Qa{l#RqsLbBOs~<`Qu=69UZZu3 z9zBrI?0o4_Us6e|jPIWCC5h)}b@hK$1Hh9e0I|eu%nVRh8#4sd)dN$&Ymb0u;tGGC zSXJ=9%~}cS>e+Szal!loIh>`uu#epI4=jN5Yl+xS+Uk9TY;9f_Cti=$|DZ@*l7MXr zDZUNvGwibJkuWpK1KHz9XGs;`uPJTPLcv0v7=F<4B0@o=h(q)s;&1ZFvHg3LHzfIy zJ$Sx`4~v8eIZq-*Y4hC?Epd+s7wMk-<^D@Z5e%^bw33LHbhg`9y-8OG4Da?xB!Vf7 zWIhgz)`6XV#OAr}t$`x*FRi}a_Ww`Ie9@TJ;oAy$I9D_nF1B!L+ zQMpFh*rOIrI===FKfUnbFXQ=orr=j4?vT&f*7;ZM3|mIRdZsME*rJ#VNb`@Ym?0+6-2je?vp4%!|rgX{>1Z%vg}heE*?XxK`%DNvqG+sP%Ln*;?~;nG>vDZ$Ti&^&n6 zBC-zzzht=Y)udOjo5iE0VxS%fWq?h-R(J@TS(9Gb&MnHSu1>oV^C(sRT$A3c{AZVv zfmRIEPDytQ*uKkxA1|MC`GYx}ZOCiTi83s2}+L3P> z%V^c9aB^0+o(d;7ma0v1=EO#}3Eo^+OL1p0be$n1m{Zd6$>#WVU9H5i>uPg6TQ>NS z1znN+JOudoG|L~D(W6O%xp()|Eh#Eq&%%|;-ueE(7jxVjf-H!I?Wmkx%T;Un6&}r|*SG5YI=ZkB{m7_eT z)#aZO*Omv`Y!RGt!Ko(V8t+~E4d^IrJ0<_wwY-z^!Oj3|G`JlE_e2a{H_tDW=Y_$( z+Ow;UWtS>+%+5ZVy3yuylN~-j{*)k=%azJGyX_S0O8yV+a@WpvO1VFvw6I%g=YGO; zbt6OWH0dw(x1blq;7B`<;sy&8&nUfpHw=bSl3}{#Qc{Uqgw!XFgLZfJrn#)NL}zd0 zrJYH~n4L$v!j!oy%+J|(p-)0aJgzBD4S#cqbf46xZ#m71ze0Wui6C$Mb{L@TQxZtk z{5%Xnp-NVc4zfi5krSeKNanjZZ`RbiFzAfX$^B@Y8bE)o{`T#)}36l=v5Jtl0ubDSw9 zOosbTJSJ7ARLv3LEuAbC`mz)#*7TShS5Y-F6~Q}a()k{fC!YQSoe~+3b*DsipxSqc z_vEWi0{2`U+i^&*Ef#I-)rn2p`~s~L3mb^&b-wK~kk0yLAm2fx?Rxj>-t&-M0wGiD zY*NZS7+hy`2{N~L z?~WsN9hd=B?4$6N+vQ5VEWH|&x*N;;9L^|83sxKvEC!uOJC#jv3ZhCUmEeia4uJ?L z|D~K_>Gnvqq!|8Qf4VfJ#5f{#)5OveG2Z7OMjc>b29+8WaBEA|c~&~z!I*v$M0EP? z_LdRyrSud5LL3kvOVAhw9Jbr>pi2j%>l>Zk%;hg6YTA#vLM2M{Z*Psg*pkxwew1o~ z(BdR)vD9*EkGgktVvvU}mvP1*4e43Of4iJ+ryz0J(Rk2?wwvcsOeH{I*#4lX*ECslz=O;#A2xD4=X}_<_!i zu+Q!J|I{FxB0XwP-ehzMXi0<$cp}?T|8sy}@V1PjiPtRYl2v5gyML-|Rx5NSaA)e; z)@u4=jKG5*B-at+w{N`mEvyHu4r%}}>E~4O*?HSoVHCD!)|-ry8hau8HyWB#Lb@o* zTCQj}XX(~SH*_?^eJAX{aa1anmtz0RgJMng-#BUJi<aTCkwgU^*3rp?UD&GDrqjjyFb>WY%ax&=|$0L_>yHl>sS zT5Hl11N5}MpDIA|92M%~udv^Cv3HJuBtG5Z(wg+raHKo_)d-w^jaP8!GNnfO3-8 zK7va<5<`;IDI6o=SELQ9ND10sE}zq>svMDna;>;dW`vUt$uh%zC+whDYUZ5fD@QB^ zignpRwUIxX(gq(+I?IF7+Tg;czf3}B`SFBKr<%*zWjIPd%%S{Wi}Lqv7gu!FEv`5l z&E+_SNjfL{_WJHWkew(waOT8G#Tk<*?uM#6Vk&$cxIK<*3MNXF-=8%33Yb>iBoRdL zYPd1y3Yz|39lv9#sWeh1#%`w*O%y3yRPi3NLIxL*OG@3VBe~t@=r71F6`et9sRjm_ zZmc-1jr}%4`XMwd=#3fn6Aok5{Q2F7x38Kq*ww}RCM%Lr?DKQ7y`G2Qe;x3HfUuo8 zd)_hHrQ73r)$ljToNhYKHc2SpY(QP$0#Y(`lO+D z#u|&D!lwPh80#~V0DD}O92Fj2uw|`B_uZkW?MPFUtPZ1@($T5@h&Zc6zxe_%m-T+= zuf}Wv1=y?+nWdxf4pDd-XmE;PGZ_Yk&sTs`dkOYfJhmeig(-aWB?qO;b+gZQaiW-y zHB~8Q4t?2+-Ry6~zx{YDUG*4S_{7;(A}G{zxf`D+iIb9+Lga^Tkrr&Cj21TNd|0}W z^iNrqB%1bu>zc`dE+;wA#0H+qf&Q~PPx3_s(&DgxU?E}C2aEg>E8hX1m?xREQ25zF z&6W@4b-SqI86*j&Bf0mi!XO7DpatN~s!9MXhn5Vkqw9c~b**D-kos4K<6wZm4-E^-WVcO-+= z+#e;eo02Jf#)&k@T&6*h79KECfhrPufl|T4-R%YUk=|R{<}{K`>YZWmY&Z10Su%O! z4u>9zgL@OgU{Km*oXZ8+XIqY>4b!y9+Xi-xMK;COh{1U>-Pnuem`T3nA>2_%jD1+7 z&^s~YX&Lz5(+dqbJDm+cp#K^W6SA0~G^cD~Vh%YtV4r>8|EDYIO9`Pf=O~ADmZDhq zFh7q~7i5yHgBlM7#}G~G2c2lSTzI-QpOE{V;omsHId&0~iU@{CXUX$OJIAnk(yi9H zW;=|vSj~;!A^tp(xe31eC%gb|yFwt63LO=#Iwz5^# zFi(C~B`ahW@loOBb*!(uJg=*&P(7w;F6B1Qf8JT4_5CS`;7l-Csyi`X1Vl+sO9P8YsFlxuwqL%8YqKNFqr8;Bf~wWPt7i9 zReeeiV_gpr(Lj6JGp_w#9XoG1PSGk9Ntars65eOF7#gWC+4Ipm5CqJ!+ zd->HwB-mEPY+%lg>3he@ALt(^Udtz8DqEO|&bY2L-{f*PUd}1or6*IXE=LFnC{DJ- zRdghA$$1#?P^{W8J{8;f+#fn_rR}r|xj*D8Ih&1757s=1ez?4Qk8aEHcxNt7JU8Is z-BenspF}u3JRt`?pSdoO^Q}fW6s=;;DpYc1%Pv1J!r|eYa}>z0rD*XiT0BdQT)$GW zzk4?+4+7agDKVl8e3-&9NxYks*C%a8>~YfV8hONqs^9tU>|x>!t+Pl$yvWGFhDbZL zf>X20YG~*4QbdJg^=hdOK+^<6zgooAoq2W7z;vJRt8sRot|yboG#u0Dj$-1Vv96?I zYVmSOU#aMujP%HKy4*`m5sneZbcnJNQN%a!2AtbLv=6b|FN(5U?mP6dfUa{WYJjyS_eJA$Oa`BW~yynZRFv0nt zSht^dqv0IGK3ZHjX>PW8qGoIhp00dJciBTO`_M{SwF{12%;hT9s}I#m!Fkj__I&YS zO=sEjC;U=o+Hbq0t+SDmwk(|q)t@S9%Muz_Z*kx`OJv3A0=DZjxw?7|(cj)`kUlo% z%Kxn4fu%z;me`n=(C?)-E$gzNF#ls}-ph^APg*)wL;_#TOOxlB7yQowYmlZ~tq*lo zs=<)<*OzP!R{FNLbjIxtS2Eebl6Lz18&vI76pbuZSL;8xb3>j&(q48&o1Yl2*oeZ0 z%H6*HDnv$s^pNw}E@-8y$mo(cx}vWTJdX7-I}yL3^|*36VP{a?0s^5IBn)&i-4J)6w!}uCWVxnY|njfI=pB@rcNo0#4zkC-9c1g<6s;wWg=0CZrMal z|4VmIgEbWoi3Qi+?Hj&qVpV62$>L8UY%Lh{C1Ha8sSAx2O4B#R zA1CB$O32@tBjqo)q_J3g3HMwFVa5cDm?MfN5}9q2UZLK-c?Q)H`D`8et}bQrHX`CZ zPsrn$Hz()S)dnOzj@}kgudbdF_ILxElK1Eab~!|CO_8t8|NlhLFQ0Y^soMp@x#~J; znWdCoY=-+ztQd`4@o2dohRcIuT`R^(v3aVk7>g#IALaGL)BjE@Mk!Y)*u|pt>X(gM zF_sbXRuW&=jU;~dgkQ=y^0idJUC}tQZoykIjzDS`4wwonc0_W2lzFO!5)pBRrDURn z^{ylp)zwDjqH|`NQoR8@yEVE-A^Nbdvfbrp`qTDSAD(wEC}5>?t00teypcvUVwgJ0 zgd={4R-~$SAc4q4Pa$(hzq7qXB8EH0+T)-f5Nz)=I$~OaiaI(%&P6;8i^38pWyz2n z4y~hfSfWJ~krc*9%uk!6qaq1`k?L{CY=fxGxl-v^7>+0WEUv2Ut)9t*OA7B!bsC;u zuT;n})Wm#l<@Ul{mxV@2Yn?LOcfyG)rza;BaN^UYs#Glnigi2n<&RDaniDTOX{uh; z;-|kry!HIlo4L%`$=Ph{qSoh&{LaxxrWhBLQ+J)+of?lMXa<}> z#TOu9l~~9mU^@d&RM8x$>xRlj81T{ryNDLZ_KG(1ha|uZ_ni=IC+8e3mmsq|DApv{ zr@4H&vXEfQr1Kk|J@NFvG+%NN(ROUoj8L?%Tq%{FEHJ%cGC!vp-xD)@DaP=%lJ0%pWy10Hp4Q;Q;j<6YLl#`t~SVAe$ipX{8^e%Pk=qKZaI0{Iuxoxew~!|d@7t# zhI_pG_0*+bg*e}gLP8|9=MNn_pS3H+Y`#Q4s#ec+|KZ-cv`E+L$9H)yCk7WMJ!9od z^rN~0ojxfaRZo@@tQWy!wj1>noi@9fOPjd_tkCA_N!~dwHgooA;dJ?_=1@*%>66UD zwFvVfVziY1&3M&*_WSV0CT#~nO|6PDtYz^oQ-q#i`NGcSol4HBmgvBX=M(LDBr^ZCRMlFIMCRA6qxI0yR^k%$Q0K@q?n30Qm||! z4ozosVk4HxVyh@)VMAu?<5_Hfp33Uv#4w%cuN8$!exqbDu{lg~Rd|0WUMMKQiwDOG z7|cD+bh$CbK?S_pwuO10tb_4q!k5$lu1*K-cyxq}H9JWEM_E_`Aj9u~i-PVzb?Wdf zkFYue`3wkgs^V=r=`*Q4)DhToksbj*hDe=2WNIZK(@Tzxw{xkHD;jzTw;gc3Q}_lW zeEVoRRZVw-BmjN#(F|)ykI-&EiGh>mqa$v6SctCWnzAwO=oXP?2;GNV#0SyoCL?Zd zWz}nGIN%Q?%g3}zr7Og)ah--V#-mr<7b|_s(f2Z)xz=dw_Q6gzkcn!S5oKMPKxm{1EkV>6{$v6-qi2QNNso@GR&|!JZy3*wn zDz`^Z>0%&#`C2i*a=rvB1o@N!CK>L#qJgJPr?k8-wiG;8pn;|8aj`%&FyAa5FP!Q@ zs>C81=zIk<@FKi+2RdWCrXG^Be8H~bSnWb3UtChLdW5Rt|Eg62bm*bGl6o%yNX&_g zp}Bxge%qD5^6OUqdZ@8ga_p=|F1z*`m4rx=S1AR-^qF@(B59#?*BqB+^*VQ6SGgK~hl9KPa0Y+DvwR0>Q z2(*0K+WKeuv!-#>dC|lHLDEzX;P7 zL5y#bmyiDRUi+3E@qH6cM;rp_^rj%0v;dR#5L|snO0ja%e7}$C0oqS5k*1>UafdY2 z&;)GuPMcT9uQO6Jhki60?~h;aOGyW=#iOgfa^&j!=5h1wl~%!e@%&4?3&HyLZjECo-vjJs=Td>Mc>jc*vyk4KJKS zB)C-_kH?Dms2BTO*%txsQcNW2pg|*E%pO5Dsk=fGn1T)XGXWq2-fEAcIOy|4QsqQ$ zRhEM`NQ1`nKy?%s!aiC8h~c^Ou_+a~TOG1&h4?BWm0QU1?<_G}fuC zoF&7$M9${HOcJ3C_dSU)`4gM_$}XwKsb%1?f(Ubx%Qub^5oXa;%c^ntiCOS#mSO1d z?3{(hWX_T**%~s;98AtVD#Lu+rSkIYmdZ2Yhd86Mb}ma0d`kTIle#3rJJzjuz?_h2 z<4U-k?nw__O_(Y-tnVl0&zV$Imy`xaa}akvc$FUE$D}pV!m3%a!W*D z%U_(fE`;U++WNIrky(+pu3tX}w6$u|W=KW(9A{umGY${vEZ0b&A*E{Iq5>w$|`AF(Cg@y4bpm# zwGj4KsEBkS?)oQcL7NZ)z2|OFDiB`5k*;c~6-6Gh#rx1o02(u+G9J-retAEo^cE&u zr?dpu`3rY`6N_Qu15Ip@bgNib3sES_i~ zB3?QjwYN_POIJ)6JR012J<@=9U)Buk{p_WS(;*z}4GCh!RwpB5#E!+eCb8e7amIIYsd4qve#K zW#F*_pKcbLCk^T~$?5mE7*z8l^~UoVR;5Uz$qsR)-fb7E1|kX|~6oOD~@Y<)DKY^$Li@=Zobx zM>F-Bjs4X$Vr8D6=Jvv<*r1767YjK_oc!JiJOmi3w}IsJaD`On#^@?^KhXBYqzCV$ zH56EVigIg-KsUXYy&7`ML+dO^z5k5*@G%Rv(_vn991yY+!A8z6vXp2m+gn_xG+n${ zm_M=lR1%Wci%bD-6}tE8?BGI+HKx7t_Er>!$ZEBGGR?89$?*f5LC`2B{ z+z0AL(z}tqWlC6cfKW7qnO9p-CEf^|SI4^1E@YKXz)>5QSB!{}l&a06HGfXja^ zg=|*j^6Qqc;p78}nKIgDg;Nh8AuY}~c$}ox)oMibgT68B9F>aQd~s5Y0KFqv7Ladt zMb+bw+qZvfo?pCcZf~*fs>_3Wzl{i&?sR)Z&60?B>^yjnwn1-ocF_6B%*O2FGRU9; ze$+#HG`cUsC?V@YG$sm>il~rDT8J=19+2=uimb&;{Ppj8U8c;wm<|y1gNJBPHU&c) zMqv`j112CSGou%?o7Ox}3)8GLilOQ!k`5EwGu z@9BKQp39DPQnb^J130*eOaHK+7va01N3cVLDmyi~bW+!st`?Jy3CpO+B{b7p&rg`9 zJZ8A>iD8#cPEYL>RpdqRSczem8qOmOd;U~k3B!Jb6F!GuKRwzP<>!TR!M15(IqP4m zwND-G0)GB&mqy8Nq%_Jrehyh^5kKdGCY$1Nb+sWrS63V1bak~7uWRUR#*DTl#3A>^ za<3tk=&)TZLk!@1q1Ormwl%c8_}=pd`x&}w6A|UtzjN1X?qnq}u~Z`Nq*YyRXv{-r zB#Mi;;H&|LH>=W}zV0V|j;5L}?R7ly2qWZ}n=+YplJofC&4@3Xom06&t-!UlS8pXr zqfBnD8SZ=Hhvh=?^k_MLxHLRg;D@KgCYqDDnFUkLR<->~_~9d*WV8J6cTZ3FC0V9p z7f{FyOZ(!k=*6eMfMtH$g<10J7iL+&G9hj(R)IIcAnIx(ETXP9$0X`1#U>iOv%De> z8FF?Vt(<#_nV4W<$n4wXb))B_+}czhX7qgVrSTstdfcE-Ix0_f5!BUhk;!|;9hWWD zKshiSgbmX=JwTTyr8L4Gz%ayo0AFJ4zGg!<6HTXD%6iK^Qu(kO>g20`cQ`CB; znIXfC2zjHPLjKN_M8vRpixOs|`z+EMJCn|N+T4Gilr>Fy+@;k}z}pLNMuW~zlV1RB zK$5>F4ak@aON;kXtyoy60hu!)270*13ydvX*@{fLAB^I=PSCe*X&i4r?2AUQI-c5F z_|302k$ER)IZlCoWc^=*u;mZHO5YPHx^eq`FWh+?+n?KG%|y0CnVIgdHnXR))Qk9L zOUsw69ASQm{zHfGtkUui-<%_kd@bdG7UIUUq?7fl1Ks%OR7%Tfi_O%J43?1`a_Yuz zucqtGOD9w4>mS~p{j8#(eM=mLD_fJSY3%L?oMv&by>&2(!h@dQO1?<<5__cNC;^qA zYI=Bm#y_|fBs3tZmscrKT$&%(p+r@NdGqx~sv2QB*{|Et!B92Zw@*!AGRjai**66l}xCq|U9w_AWM_j0_V3i71eia61mz!Jy=xN<^c_>WnU~(XPg#*5q-1rd3t86Fo_0nY(mw$yv5xhY38a31kST2k{{gM(=a-l|)yj7-PY* zb1AMk{!iwLx!+deia0;pY;iH8p~vzGB&L{WKVK@|m-DV2Tlc*wY!`NU=`o}1yL0Zt z^Qz)aLQi$D3>Qi6Oc#^;=z`+*Mp1m=SXI*Q)!qlvb)a!|)EfeSXS}a2n{UnzNcW{1 zLtmq1doXqhr0Vqi>`n5;;mfnj3*n4~m;dMGhk@7fuZe0Mo;rQkdwIC88tAg zZuZ@Fbl`Bxh1&Crl~Q)t&N7~6uLq-j_2$EQ^Pp(aCAn<;wei=p{aH<)%gvz7vn2Q( zd5X&;ybegCe@D=+8As^-{rv=S@W`uoud;vj#@SOJU9E`^0}hR&Ttnr{guc~csi^Fu z(-T!_mdi?=R8Q#dwm?_ZcIZfi5mC6uy>{SJM~ZIlRBHZrLl{-{h$I>g=t0_v)RxJbU%Fd7|DlFE1Of zn!3x$Yd0dsb@{r%C9z)n=p1t!Wh6+fJqk%3he03q?{@kQW|RlY1Pej0;|`&L>t3vi zqrwEaT~x~%NZ0a6v~&VLXWnw%q(L{}7&G_Qv`2>y%>!yRffVyW5Sz$24I~NSgF_eV z;>TT%d~@3<+b!h?Y4Lsl2Sit9MY#Mx%m9Q22>T+s$suI@ar9fyyewXOIyOif*{nh_ zq6tNN;@|uWtMtVH)vC#ZPTsCoQsEbWaa%3<67K~A@27w8Cn%nY0j^$?0DRW`cnSZ> zdpbcn$k)yHP1T_Py}h_nZ=1(B*Y{`VztF=MmrZqXs^0vfKAxR6t+}YsKv=uRg5;LI z8jRk(!u@b?U0=QZaKKCFE7|Wey`^0!q8e=2z4Y1$tcTRk9feLRCrAIMS^cdU6<)s? zTz(ulgU0axyl)H~r}3A<=gzB--QnlP=$A_8{ABd{&B4zf8b9}=T>t*K`=5XLC*6ks zJgKy={#vd4quebYziN-{i{{(E{Csw}y>)nWS$v~6f*j^~+nASeHW3JQwY{}EvZr`t zGTis%mN=>&9owtfJ{H1b1-C@GSvjswGR~HqYJpo~TD9Jw6Jl8_!gNEEC13iI4h8yG zELKT*s1!?9@r#!~S$uR0xc9eRCaSQ0nJClV%lH&*Y3^02IpvI+XN^j^79Q33>uMdH zzphr*`s?bsdjCf**v?#EK2GwyOa*!A_a${xbhtZ-3z58oYFa@^?EXHOem{B%*+iGq zz*E|T4^$vl=dtX747~w?#^qvK_mPY4Hb`vLDKt+pf%O@BnesIG&hcC zpc6t=!$6vcNsUBZ(y;H2Tig^4RYTd`ftDv6!p6CQm>52W9VTUmfZonaR67|UtAI1* zAlBU91U;SifSfT;nzWtiLFZXk-%OEM8!a3Ule>$xFjA@HEv zeQ|QZhl;kD7TBCS7=u%D-W#^S)uT{FSdVtbt1%4(d<6JoEm!FAd2izLOjsk-u#_VwgDpt@Pad_(m!sbW{$_t7=!NTwnqV(;dTL|bZ+5(fQ}d5X)$ zlS$3F)s-V9VlQc#0GN&8;~Sv2&a$yvDH`uMwL*Elq8sUZkqq|@#ctVodlf~gbkfMJ zM)(&3>E9Z=W&i9&@b;EF$4`2DA8yI!Ej=2$<>ZT2)uA6%vzH@(;IA9HMavF3 z%FA9Dy47O)X`xd1Ln&8ryCo<0hn%hLr&#>IG;m7-HJXs(gke9dm5_?^qUKfl^(m+1G5pCwEWRPtAHrX zIkOrCTW`>@h{X|*B~smVrV-IaC&ZY|^*hCV%g7&Mrlw`3X2t&wI^Nv@Nj(E1#oXLf zihx6fhM>Wj^BFXvLNPoTuchXTFO@vSw(P1lX}!>2g+8%eHop0s#M8k*@rgSl9V#0# z@(=hVG9Oi|dHsxb85|~0q&|qE-lw(~#-G>*eW(t+xP3q$9^k`>?ZzEz2$h;2z4gWq zo*ah$9g)lQvmKv%DScwf2O9WEo52TkvIMSAY#u)pOE&$j-=*K=YtEsF={)I~KX%j~ zU)TQlruN56{?Z?O@;;O19dpyA!xT0zuy~udxM$L#FC=eV z_95%8bU5g1KJ|HS`r&K+uq`dnAATwy$X)@KXbxPGmSuAb(#z>4PoTp2CtYvvlc^IR z&jgRPl+th>_1)+e`^c5cL?#7&bmEHq{YkDa2Gxr25BxDKI>gow^;4|l!~S-d++qHF zX(Ynasj=v3z({|RAjd;k$@dfV_`z(#hu-yx2#HzoWFzujHuc?PH$Tj7hv{sG`yR+` zln*Syq94*L#z(S`A0A)!fjsrJO$(XXm0EsNHmzj1@5!chk}ni0q}nv{&sS|0!DAI4 zQ1dufC^qd}`Lw!tswI6uO9;=Kjn`O0b(oFTSmKp0X-z8=H?|Afn)c;O5idT=1tR*l zUFfi|exbwJ{7-_Q8^fAVbe4$K(2{#yZA5mjtIbL8b+r-sy{z9GXR$nMfgWpEN;D{zYx&~l zL<1S_dlC(fNH)l?EE>#*#|olBC1+PlkBSEKrutveV0F=;SSS{?XyAPLFwTW%xj;1d zwyPr+HeVg_e?@~YFB$;uAmfvsWx~4+w~dLxnP83)tRXaO(07q@2#}d+h>=OxGzM-q zOT3IopRP5Xgsnv4QHN|AwMr&j*NsMB||3l!A;*k0}!-YXr+2w%&dzSl`)aPI#wqY)T^tCZEtZSB^1A= zsX~v}6yN9&Txm>x!~OYSuj)(*J2!-L(NCYF0%6@u>T;DRE%$&~(HtUinblM_ry1^1 znP+xIt7b3vGdcQil9fFl8Wq~Hl{=6h4^x=`a@-k3ucF#J zo+HrB-PDu8Or_?@vE!5~3ns193bT`@vx^r#oyc*Ffa)XBNm#q*BINVZhZ_({V)}4| ziu}Kgbb!i?Lm5l5Npb}m6-w5tzZMJmWY*q%IqK>B)KQdmqqd(4wL0;miEQK#dL*t+ zCo;bo#qoh{628Ah96|2ESh*b$vB8EN9Q}S;E)4r7UNC zr4`$@h5o(@J?`sZl3baCu~g;G#}}gP*KWw&1@tma{YMTXJwwvQjoa%YP&7gtypDAI zrHclJ&ziC@*@$#%E^f8kH<3q#>okoN9lr70ki$hucwhmK*!v#60tyjvO=>iEPeocu z1QRLr9q=UGk|^>!yoNm@8*QYqw!GBG8L9u(8u=v3v5-KFwKoEis7d$}aV}EYyOwUc zj9d~)m?7Z>c#cLAybEdgfH+}^gC$;$F@L@+G|ta6h>Jq`hq3g3B4}T}GeXE)-eM0u zf-Uut+vwiW66uwD{J0}fcrltquvqK2QR11G+F=k8Jm@aC-1L|@b1=-K0%;2lql}^z zw>^Ta%OgYf1iEtoE<_34q66EGxj!#Wa!Ocnba_G&462W!=2|asJ1v(Mh!_UxLDh4| zbX`es!_vXDBO=|X7QQd3HfChnTVps>r%<)HLRv>ETBx_Dms(2Kl}EcAp5}HvZj?*c zIYggE8En+PkrOiU)k$BdF(C>)bseZZmNV~lA7K{-&rHLzeSEB2NJ`psJRU;IRl0;~ zs)Y%fmoaQ z)`8JndT5qK+{6G>0!c{JcMWmrymali0Fm!<;TWIrIIykM_~pl<@<}I zDuxj~b#`!J?MtSaY7w$^X`&%cxT6&+QpUSnl`k^LY4*4Si*Tr0x1|jdgAexQ0$ygfk;jV$jCj$LTL!I3oqd=!3>l$4Np|o zL-F_U7Ib9l+_k8zJ3CADJc!?Pe%>T}lrT8pI>B;nB+JcId7q#nMoy}`6VoIfqZ)D^ zID1}-R!qh6h)TUsR`^ZPjcE@NlSqNmKHB;CeD6%k9uLg%p$DUb=l}u_ipyst-oH=G zIHN|t-QR+WC36o6^o$0-;l-GA(NWR$R*#+zqnjmuJszNX9iYg3)@QJGD;aWr7-s240dKYLX&|Q;jvDg>;eJI1oa?98K%AUh3*5e)=Y2$_c0dX3Y=O9#p*! zDA*+$34~q`Rum|ZS zx_9(6yE(cfFhgE*dwz^k=bZWkl!32AY*b9mr>5biWG=mThs}39sYKP0gS$^!Y0wZ_ zv}2mjqz~K43d$SNNoGZd3^tw!{l$Ts)JvpFn{Fvd#dIdAe2Wtzf_z{(OC#DQH=S`$ zsQ31k=krn+7EIjk1Egt%bZ1?Jf<_a&<1g;`_c~X=*yS_=r&^xTjyr5>Nmvb13?jkt zc8{hI$goc0BIBGkXO0#U2%kyEi~Q?B!p2=L!mFin=?)7rhzezNLqN@Oe!>r6$Rm!E z_`2wkNCTAfwAt)q;{Ofogn9`@gi1iy_9IX>{k^e}Vi0cl#`1E+33IYpRxnt0U=lOY zDS*^ED~+vVV`vg#$3@mTz15Kfydg;=;4LIG!X|}!dO=tBpfx#h8T6NN zH#ClUkT_mwDzVXrv9-_C0@Uk(@CqSNn%ZiID(7yR%j3r~7&n}a%>G1$8e-to9wBsS z?G2HNd;7eoq-+3^9(gc;FC#oK(stqAkl--t)8%hVG3ntbjwVD`Yn`k*5clU%&QBUb zav`7#ekIJ-km7_pNNI#b7Kj6b1eb($$QNA$1B6@9iqaS`kqLs3XqpDg5Jrp$-3f(c zPpelW4Il1}oaL}TG;-7<`JOnQxfwH$NubBorUwYrxV|Ea^BAUOFw#hka^Y%oZlvpE z2M{j%bn!aY1SNDSXF&^hp_u0y1w|Zye)WHh_bsL^0|| zAibG1dPE7}pxHGNGZ*{87PU`%Zbi`H{9SO(2g2PC&Z5WV^6lya-A$k9_SRlv3XqZ` z!1+mea2ESRkEmGK)ty1k=%a|Pic$1;`JQm|$(w0(5v7oj{G&l`qvFZ(yX`^T+uKi2 zkw70MNbhR~g|%c+_L8++oF7gW>c1~cDi*m@4B#egY;#2eI z<8zD0+WQFZihTE2?Ggnx;M^7a8N1&grrtB<)%*jmz*t3Sug_8OIcZj(q@fFgVDxum zvsA8>!yzk%{R-wV@pIL&fL<)B{FSTTk$ggbm=BH1Q49aPGLUjvBfC-Mjj5rK+|RUt z3(AxD{p#%puBq0C9Kx#O8MA#NIs3?m7x~X<**w;WQ{%OD5_$~>>Ud&NK(Cz)m_LBV zN`OXUZG}ofHW`D|{T9%~+v(!unBK z3r{QH#H+;kX*5S(k=9QSpgGdx=HUaeO@$A?U@4j*F5O(U>y`%-Mx1CKT;qV?p0&@T3S3J;^2;V7fTjEu#*vmme2NLAO4L3G6}a zMw$=^k=5FcZeS-3jA#+sW}-AUO(w9^9veai+TxZ;c`;d6VuNBQI~vNS&8`wbgIgCH zbr2YW9M`_f60nNKeF@P=5s3bfZi{ZfXM`VFE)VqyX!uE5DNC^ik<$6O(O}@ge2b3F zH(qZ@l;W0eJa=bqpcO_YIkGf1nl8}#T#Ts^L^PDZvWX#kFD*>*NZx?W_Dm8bnuyf( zQJV>uo7fD~T!0Ui33xu2yG@xiG==O3ASq09z0X6iP^Ky*nYF}1cyjW|IP=$F!DK#uMO6KyW0J*%S|IK@yO{iDWrSh4jYmx`1cZ6Axu~U zA2NkjHRKW82(vic?;8*yT2PeO_arDmn7F1fP&*J z0hu*CAUOdsNEv4u^NcP8Ga7^q8It=n>B5FV&9Q#uPZK2=#^%hl7Z0lRP`b{rEwxA& z_|Mv|lEUMc6G5TRIW{aJg`In5JCW8@c{mp&C_+?MO8Ym)C&Amqg+y9%mUfA|Ad3)t8%UaCJEti^{!U(Zb8KnB8)02w31OIIvRp|toy zS<8X74q<-mdf{BP!8kLy;1h{TD7j8^q7QPOH!*d)+4$F|IJ$)nb)@qU1U_JN+=}yc)j z);I7yBm7DvYp|_r6p7>tSWJurn%i3%Q=n}C72dP&9lm<5#k@?A2+COhUVQAz0taQQ zoVxSFQS^K9&h_~+1`_0+ddAdFggAnSq9Ih_-#=c$u!?f|IU6xI8;aL5!+lpV>Xp-K z=&gOXY%9 zvA=jY`od#d5E%7scPcEb+o|wDL{z0xnp@?t%2f`FwR|Nr8iMrZk;6zq7CkVr`hx z3gRFTQHd2V5->(Fc)671ijz{l##o{y6y$43aQ7L>?mSDR<0_6uh|Itg9BGw~rZDQU zmS52vk}cwAtmxk=sSiByQUT*`^{x>}O;SUDjA$^d+Jv`1C-|hUHWYrsY#3Gt1N4__ z8mURy(y0)LW6+hLP2XpaWFXDj0dQy@p{p%T9hNmYbX+&jwBQ;Qd6`E!GpTExv^k;W zcHnj~@kt!t&w_=om1lO>oY5#q0|w9sRkCLX!;){LO zgy^YB*qe6qT{au61iaV>&E-b=2iN*U)I!bcxu1r&lQ`-GYyd@V6V#(BW!_{I$mV6#%+Sr|6_z<@}~ zQz92Z>A+5g41(!lMkUrZIr`1{iDfsN^Az)V8}k+G>%1Xvu^w$h{$d_&GalpD{l1); z89_TD@ca&hD#RY&?g*xec)-DW!dV4dx3@qGIf#MN1AVwuIL(5?`7i*k$%CTe#*C+t z90`Ujc^i~uJTm~3fX-wT_FuppESzYn4*m9)#>T|Wn?!6sERP-}eq+8KqE!SirSiy@ zF0DS05a-=Ut9+bLDtW(C`L%lHWZ9f^!osXeDvLXo_z~zeY(M3+NnO2y^}b-ri7=Vu z9C(VF6QqUVhTu6|R5>$4%h`j-=F{mDfmd!2+^478-kO=uX9yveG?H9WU3Fe&2nHFQ zev_xH-LPs&}G9+OtXOAAig+mx8Cqbv3Naxz&DjKH_Szr>a z(@gg05)E1LC5)y42Ik%ipjm1G@zE#KHaNh(JLzQ)jOEVtQ&s(E5ruUk9ay8&KJ-RJ zV@*TCg#U=g)L@JtB5QQ)Xc8*UzU9xu5q>qU1|E>%zr7{(mRsXkPR8enbyPEp8U{_c zXo(>^duDa04JZN>ezS-O$)~rp7*Oo+)vU=3@ zKTE2Zh~?b1N7_W0iCF35uod`@pnGa=Hz)RlDenfOPZGYcR{Tk$*GmY_wMdQ9^Z_9I z^ruhwPJ~uabpG?xpXdVXJ8u~4(icL|47#CMMKrn7bYpLOqt5ks zUzjQ=GVjtDjc3fdx}jP;Bb&LMlyOzBlq;eDQ0CsUjLS+BNBX0o1B(g7)W}ZQ*0AfJK4D?d$H(_$&_$NYc+LBG1^e*6eHB;f~SP?Z76(-2isHtmEoqF z2%+M!HWfs5re`r^j<1CTWH2#J2~VjwC-iirrNcSGCOwEH@HzM?{$>OXi66pQNKTtB zY$MSw(WLkaVx0)xM2sEqeTjmJfz#yBk*F2AqKO(IX99K-j%oLOk!`$C1hbB7gk4FU z!8P{Vqh6dS3oKfCpXsI=gbZL=$KkB50YL=QyNTo-M-O_mJF z=Or!(VV|hr(^giM7+FaO2Y7X=B0YF{$e{55>Ae)Gmh;s<9NJF4Fj1QfQ-udHQYCOG zBLxbO%vA*nB)PJhl26QpY+TfqLL4h5IsA}favO|6WRPwm3QF*El-l1fud5&-ne2x{z)cl^t26Le_}!k zwMwqN5y`HF^RYM)7__I`naCnK0E5L^)5}ZOV z&8yMQ0A73)~W4D#7&xY<(Qea5*MPzc%c!3c#%8`qa z#BzuQP8aQyI7qg&Sms_YABDRx1Xl)nejILHZ734At~L~lTUVQk#;vPW<8kGJ(NUhE z+PceRl7qc^lIJDHa{}b<&)JJ^_y1?_Yunn!vTQ$x=ixt`;`VcaDqC3cJJoSwj7jK9 zNG6a@)l~P*m2CkkY|CZYA?}%;-+uSn`y5FI+ZbDshAL$?b#zCVq_626fZG5!tXO5weJxlb z{*hxaT+;0odOs$t=TaF&Sf|9l6Wwvwp5`HmELQ9#?Jz+T)6!W9d6!~Pbjo4TX$MB9 z9U5IzX$42uA_GeOGGn^o!iw6{TQtF33jQA{Z#;K} ze5$u%P<`X}HG%08y@X{Vn9K%5K3o}PfSPtl8%eTG#Eo9lB|{syh?ghX)#8>ip3MK@ zn49Sc1{3@F!IK~YIef^i=B{?4(y2+8J1$?5aP*Xp&cbN9bjy@r{4shmK*EAAalE3a zokv8^{eZ(Tq;*b-zZmmLk$Zk{>34&W{+IQI7%h;f2{_4~mCZn^#&ii<;q6S4^%0H@ zHt0wB6dWQi*f3gC_7xgGggYel1q_ghxcP{I$%?vYHlwEC)R&ntW=OP}@&0SIFQAh! z)FnNzJRmHGUkpA6z1EAvQ`FMYDN;&het?QvK0XCaEecUliy~Bh5yy>eM;sVIvGQ*o zN`h>FSx5!A(df;l(BdSGP?wrYDcGf^)(>~7sp)_huq)r7mDaIpCgFl3;Ci?wb9OnO zM9E-YULPBO<(o2`0V(N?@NtHB3B^0+GDQ2L`)7($}ho~ zPZXVE+WYYR2uNmsKVRCsmq7Zp8s0%>*B({a0N^7{0`VA<$`iw8WVT-NEds$ zz+x!F$<5J$I);R&OKeh}+-IF(fNEIhx(1YRBGYF`Ty_k0faeS#kdUHixi_T(v81JIRYcK z7>p&NFls8TIEq& zKaprg_aI&6#-p`?2>~?&jSuAjfYSFPIBY9XKFxAVDTrBbk?r^{BM z>XgdnN&a=Ef|2YPMyH9Csl5*RLRp^C3Mdx1Y@tnoox8FqV$k;13qdh zt&oqJN+al_rdA32Fg>}YXi_s5q1MM?w$hd&QkHyA!!FI2CAbQeO=O+ff~tKLc#KO9 zFf%=SGte%?HE){M#MJDNkSf~D!wE}k$gVL$i4%25V1dLf#PDCNyV|hkOOP<#T?eel zN16<&z#Rm{;2|~^!$UP541En<61hO^rt&jiPNI ztw`b}^N~-3#H(7c4@wJ2yk>vuF_3tr5o$tnaCd}P*$^}+%Do!4KlW4CmNd`8x z{Ii2qin+%Utg;drSTUD!luDtD8JSB4R@t|6b7M^4Z%fhbSyQPwK5Hsn*Jn+o=lrax z)ZCvnm2`mK=t<~;PsV5?3c8xu6bK-8c zj)94Cg2J{t5Ups77i9~f!H7T?`WIbSkhA5rqr*)GoJg`w7)66b!~iJgH8K>aXgSLP zJ>oueW9Aqk9n$Uckl{QU!3raI+Y`Gu4}1!!XvW8 z=~$Xjn=>M#RC0yQS#xQK?U9bv!#eq>E*4qA@JXL@gcDzkNI&Fd&9T8|(e9KvGvGjWJLw?WwkcWrQ?&P|WAUH1`OD@{&F)`QXzi9R~kGDV06#EA8=f8XbM`!pnEF6GIsBpO)?rzrqT|1)&S8i zQKh^)(S_e%jZ@wNH+*a8sl-eDo-z|5-?wfaqwxx ze-SGz!qmna*0`@tv0WO{9JNDK73?ftVJM(I&P_*P>xCDW9btzE;(*?a5*)TnSGpaV zI>|`kkULke$r-0aM<}!L_fx5{!f^?*!^{T+h?(SU)(1v#}7QWU?`Uk91s!6ugbiBf=0aX%^aEHWEk@(OR1#0rUfn4zQEN-rpDO z9|1c*oZ3((mM8IXQQpp(vAjboOltym^{7*r3vQACZeQnpu{y4O2BMlFwvq8l#1D4K zV5%QM*2JJhqccQrXc&QEToA2~N@!-HZdq12(-3sv2`(58wx?cjEIX)0b@RaEyqY>d z68lN3EWDw*66eBuNlqb&F!avQ9>(@wt)6p2D;t?zke|}T7~MxRUqhiE!lTyFG-KLO zpTJh0wa}ci5Ngfs*^0=kN0;EIPSCTiKu_M6ygaMp zshzpM`PHr#EhlF=PQkMCxmEC2(QcMjWJED@zK{`xAfiM4D_b@p2FJ<$Q|*v$5*Kba;gzPH0Ru;0FM>TU$vx7gJ z{C=@&ct~y!rc8sV5<)6jgm?8=4*b4v`aNe@S}@$*ZTsNG`rV#lD#_*Yt(@I<%H>M? zFK($)Ddfw!zvS#v!L77&&(Zms!KPb31;ZBofUwd#-|JkuC^)&bc&}rJZy6&D02l5g zd)D?L!Y46XtJ;Jj5JR%^u$k3Ag;Sf5c=A04+EH1ZW;nq$;#0oF46zMh|TX1 zntk)Cxet;@@ZNUR!6eau{&}xa7oqyR`;2pvWUq~Tjej>?Y!m4<8&xmZAF)%RpjKA zD|6JAvLra={X#KC@y5-4OO}Mfk#kgBU6Re5j}!Pnbp1>n3fT?!W3S_V+8fiZ zCSYlv-rTq#JsI_H__Z+s%@KP_-0|r0WBU@(AWHx2*(%ypv4n!SsfinD?rYMf3aiMV zQngw?T3H6oo}@oon;JKvmK5uUE2$+IQ{j)$rli}>*j~TeH?*l@N5oXs{!1Ze+ns9u z@o7`bfroh-6{%CselGu~Mm1Ly7aoZyPDi6EtSy<@mFgRgHeK$(6>dj zB>JKKDO5Wx4~!GsQ*rBtCW45K^87ly%WP5CQ4F+Y3?RORNiIbxI-p{)NZJ2%e?Zi>RPE>a*VoG`NHa2Vq46S5yeL$ zBUV(`iVvi&IXOZXcdBb?3Y3~kQ>4^XIzpwUZi^LB4uzjI5Vljv(nzXxT%{TTtvUb^ zZbbOjBmrr21MY1Jqe}(~aw5+j08vL|^R#lCL^re--&-Q7KZEnQt5t0%&Zx`(|lG}$I|o8zB5{_t5c!%qKB6EQzi+d2!8lNvg(_!=V2xMFRH zgt5D{Io!pS{d`)~9#M1Ok~UXpv*|u&@<*Z`^EB~%FtZtT6rYW zH63NBvbL~gF?f!yalWx6Lrg&3LECUV@H>54lBZ`kuq zt#uegW3GEHF5xjr;pp zW-0$GJ;1V~Ok$+qtm4tiJNDsnI?$X+ny&-#(Eb(tE3kZOr$0SB{(15z(_<-^vCNv9 zIb$I$uw2aBb}3iAzr4=#U~HAml`)vNhqv^(c_W#r-xYI4dKDYhLZecqW()Jbo16Dt zZj&i^FOf986aV#Ba17>9iq%Zj9UZ(iUAcN8S145LhlLfV%c)LxdyW8_{jip)$B?F$ z`kS1gt+~hQ=j_i}a{==sH#g7htn;XahzkxH}f}`Wr$xLaz6uoswUyg09q83u;oq~8|a;fVc^{yy})U8Sd)x0BhYT_&uNa#WI8 zr+E#qVN}r9$|dJEs$2EPAeVK*9_hDk_lm!)NtP0o68ni%J=?6+x!zs6;r;@vSAiDTaedi5;-lSAqiJpM7XDEMYcmQ+zo|SMi*sd`Wa99RPXB&KJ*Es z4uaaz9meE}t!O!cY_X~6%H=TEh}|p0luQh%n9;KbQI6?s2%4fV|Hym~^rQVlA(y86 zb8c`?jF0RRDM0(hRo$UCMSs-Xw`2~k6b{P8<+S~!=VJwPaH&p!AvOok{?xLp4)+#k z)Q4LFO~l!9cH^bx`I^B$>&Vk7)AKqFJ)}X}cI;BAiY}DO)`Q0h&EH|O@k8nBCX+6u zO(ty@iWOs}mJ=&hYMNIRJg;)W^68_IuurEZ`D-dg*7 zGSuHmu4i6FUBz{C17%z_b+4}9x}$5f?%rq}t6^42s@8Wj*;Z6giT9!K#0edR?mk>U zvKtd@BTO{N=};V;r%9^$pvu|77qVtr&D!pnRiu_)bG5^W8$*)Cc&UW@j|Y(4OR~C| zxENxGxl+J!2vc8@PYoZ}e7K2fK@1~kkB1STb~~!Z)$*`CB!i8wDd9|!+PQr~8p6A$ zYs2c9nVMr-7d`iT%&0r`nD!H_jD|xkWX4WS0w*^@Unp+62XB3cG1!KeM z@-Lcy*N&-Y)y~YH{%ThXmYtL07Iwb8O2kDzpFda%VlO`{Q^RS~Nh6FD^pm5R^}}c| zIr`ryPIC5aR4kD7?#UlFhjdYWbucXTeZq^?LjL~4%JZ;F;+#=CjTxH5%t76yMjSRu z2M3N*Xw3PjE#*Rz9~CHg$J3D_utY_p1>5kxEOb-GG@*2JY}+m(<@v?wIy`cBlgQ14 z+jCuJkp*rXX5Fyu`|-1fei%b&zu#q;HLcg&-Iw>Y)YFtH9V;OO!0G zO46+mPd=2wl<$HTVXmP6-5fCgXh`60?xA~O`qa7LoN_f8c_Psig6$dA7x#1aezExW zwcJUW8_Kc4_kWg&OO%Pzl{0RZAh}|O-g9~jN)TpBmrVphX1Eg!1*YiENMVj+$bVgz z6*OS}Xhg8)Ex~V+&S87#4@QQq_h{&O2d9Thf(|ryv%9$=6nfbn?NPuoUW^H?5q0x5 zn0cvT0&yQtU)J9>4|y`*zI$`_@m;fi_}~1u5ATl8ni*lBO)z_FIy%J-PZjTo$`tTR z3Bok$DG2s}-$m<-S0{QI{gbQOG~bb_{iUr@B^K4Ul@rq&hXao*8ej-4=N1<_|_$K#ZnA4UB$gU0{0&=I)!p5e#FN%%xGqG`gJ~m#-{9LLu;EB)Q>4{%{RvrW!=kJ4!8=iCB$k}C@ zlT{$U8CJU3*seCKxp~;Qzz+H9lGu3a=|_c)v6ZUUiw_Trjql`FUbQyjL*nbjop}I& z{aCo{)&tn1xeSy}ufj^YHC9rtvU1NlD>ao$rIqNbYt~w+skExC)Kn_< zR$>M#RA%!qXtBAvi5e^!G1xL!GiHiOrh`6;KTc>Tqw)z2@-=g28@1}(^@m)&gP>CK ziMZ0z2uc#K2tpj8zeE)#687ORuIXipoXMyCb1sC5L83Re-s1|_y4poXoBEAa9JXs# z75%=DH`8UnaV_9e{#p=Z3AwDRhA!Ne*rvHJiXe+K5oB>Lf-J0(u8=>fR*EYk$a%B! zAPBN}9|U<=xM;p41X*@U2uGg;?MKCd6u zj~i!b?zK<%TUULeRYY5-y?{Vjzcn6SWTK*6VKk7a{wr6B?zMck zPm{EbH?yQ$wXeM4HjRq@g0dw$$#pj*OmYv&x1@Bd-fL2!6w$=)(BtqJqd-7!q#JqY z)9?_6V$KT592d2lX;t{nkZ$tjfTO`WpWdZ#7u;5};8a4H8je1r!6CqKlqjMy6up}5L`BjGI95TBK23>8-Q_ zKTgLTc6QnGASIsI(MYN~Hh9o(yP=5;;#XQoP~frcAm4J_L)pb$EIiByiMrbT4k{%j zZe(b;*q*e)?~WQ3$FWY2|FcQ+bmfX4rk|xhvIrnlsKJ{tC(_)pO6t>I!9XBF12=-X zKHID~ChAD(@PC$lrW_H6z;UfaT{P9@bC3ItH;$3yE$_nb>%yTZDjOH4Vs%?jpBBQF z+QII=4A`i0to^LUO~lEm?&#LVBgEVp9g1Y_%0!|5Cws+4@gCp7jY{&+qQ0|Bb~GC7 z@9kY*Ut91CSzXU^+t&DM?|(+WUF`Mzu)R0F{%G!Z+x@7ov~13?xW!$mI*GPuH@2iY zRHA6A_Qi66P+d1FE|Sg_Qe88McLb7LwnCkAr|5qzqiqDki@m|PwWt5hJl{pDgg;K{ zz0*C}m-rg9D=?(`YuNV(^0S%*ytPd+RQ__DE5qN8E;agdhpyvYzK=~owS@na12_|b zi6J8bmUZH8*L63;p!>;Vn0AiZS3SCs-8h$;CJ5V!otOLt>_C^!xZBM{CaG(#ylUiu zF{aP~+R<+EkH;Tnd+hHjsd*vuR?8H;kRIA*Y-*>nJ8h`|d;ybJ*iF%yl+ za2zsKkS>QUC`#A;W$PFWTDaEvlE;4SaUC{YMvZs#!FUvdaFHV1ijLA_w`$)Vk*g#pl&yV@_xwu`Yi4DC|dxoR?|C5h>oQQUJ~}kw|mjQqD^$S7nBa)l5dS z=VOJOmxD^7QIB$7=6uSzNv83KS29g00_}d4Oc#9gA@fn3T*-DSR*81p!)K$+UHdsE zk;lSpzG61Yx>V-3BG+uYplu;?mP|H^&6beDe$ie~#WdXzt(r<9NUNq&4b!TrlmfMC z>h4f2$q7j}T&t$i4A`ovR719E$)Kn8gSKia&9E(w1;90D&r}>r zJf(YKQcg??iutV|!R4HJ<$Wi&Ura3=%ry7yg@YZ`?Siu+GMhIme|$Jt^QYgh+PP9C z@03;noMu=lhl3rKiuFRhaWDsZqEy$Cj=rg%}i$CLC-oNL>j`K42`^ zqMkf}@9xo*S6It;w{De%+u76kViK>4utjJNJJk=C3 z3u8nkz94>IAf*+JWU62COK}P5Wq|b!W@f{m1A7_N37}L{gdPod#EQx`Oi6S36R1Y*p1VVWl1S4RL$=v_1M1aCvkz(S6NW&y z&&UOcRy*9@UYq|{8r?+>E5HvR??jywQXTE=%Fz5CuN)WFkcrX$jQF>4CcLFiT?7O| zL`x%)~kv6xj;1;#a%E;O#G6u~iS z6D)woHwP`;JQ>NBi1QejO2;4;PW$Fe^Cabuq#7{e0;8Z!S)g7Nlnqs8x#W6J?i(pOD+`kzUy=d~46vGa> zGD0sVsW3Z_S#iDYBvPzh9TmzUg~9}HS*ot%;K0amEs1Bm9@6>N#E`uC=cZ)J9xXqu zs&uU6i_{VB^`wt%Al2)8eN)O%pd=?=_rRz zqk6OydDFc4NTpm>rI9NgMmox}pZZfr`Py`pLbXyYSjAkSU^x%3q|DtxbCi^?yB=3* z-FjTNl$2uGSzSr_6H7jNEXlO^mz-QLvCSh33)$sp_!ftUYBtS4eH;xZA9dVz&+Q9B zb3+&sK?ARmOd#>S2uF^(Y~p@)KKU+9vtr^;a9Sc_p2?>^Pj;WF<8wp#7zGMJivek6 zCZ@kUr+eqm?urW{kDDp>x3e zej>_A&r@u}7cFlDveq_sCMdxJDambOXm+Ijx6u@%vqLZ5cW$8IqC zXew6Llykz+J^`qCGNO^?k2tIG{5_RfIYwsH%$AFJa5;^N;Eqm{p}E6jIBq&6XFOmthpx>gSvi~nqrp1w1rYR7 z|Bz}2>rk{cq51T*R1ZZ@LTxT?@p$izGD%c=r{QfGCMf{ZQABfKdT~E*?>j4o58cL! zrnzqkrq?UYgXKcHm!FSS$`~9RPwlfgVE(a`|638vuV3$vV7r`91KUm5jZ>HxYg1FHLBg6!3l!GW+OW`YsLI14 zDml`_NPD)fje;Pf+%ZJ!$ZsRY1Vf0#?dFE(v-yyP8`~%Qoh~CK00@1Kbn#A8p`j1@ z^e7`t_?RNrd@e<-xnxtr5H*z+im0hHa70a|h$Q%nY$w&}rFdSZCJwQWzNg8l&c|Jo zJnd_cyt&abrfM-^V&W!LS&%H<;o28s+|*z?=yqUij+W(I79`^+E7^(9JpdV%53-u=3Hc z(ha4q=BuS@;h<5T{ZTmoZ+=u{_8m_@qEKq!tg7Q!FOD90DD|Dd@NQJNB6@qsc5~t7P70TPl{4z)@t<6gNJ@?)n`O+cmWk^h_0erU^e&0HD8!gVj^@ z?#Ki9xfWI-n0Q-WWVPX4zFtdkdi}eIoqNK8bd|@7r#($wEKO}JEqyFijjX29)5&ToC9SNc zmexZLM6`&fZ{D4OZyJ{%P1pV|6B_AiZ9JnStL6N*JRBg|SDdv#9o?c^cMIE7 z*VhCP01A~?MI+F8AY6)2GnFBf6U7QG+P|6~Ov0W7!qS@q&}Z{--k%+1D^b}^E}LlT zuV*-%4uo7_61d?2gOKd{tsyh7nW(BhO&ad&J?L13Y0)+=Hg=+Mn}5yLnWW-?SpowCTy}0$n-tq;bAu;`iJy1`7d~QoHEkaUiP17^F2uMkLsEqe zP2(fwB68>PY7+u$Uoj<{ABuCG4ioSfdK_rEulKP_7%-&$MZ1IU+~I}C@5!-v4B0!w zF+%NzqvsK)`;|5B``IB~=t29+BFr#lek!6$uw$oXelpE1GC$qj#TD&Qq-~BrTO)UT zF=9L0&h;hf=0z(UUEiF8U+sL=%ENwBwDQGO!szmi%5rsHmY<}A+2+((o0OHIPWl5mUW7FlQRd%$YyZjU^v z0rO9EO|9tqC6)S4=da`p{b_NUvN*}4JbrtGV$~m8*_WU^gi1>ATsXeC05f=kL-i97 z78ua!|M&P(-=nqY@HMd~Y3)3ZoN~{&?>wr~(F-i-0E6yX>jxrNXs_8ukDmMXjMp z_el@c<$3V(^l48Fog1VJW-sAN#OI&;!*+YX%8)Qs@dWaXBs4jVcWcniqxD)u^djpja0ko=2_2?R8yd~ zXHre2=uWDsbsS1>e#%H{LBbLiC+f5D4D)DEo^iI~} zBr+R1f@%D$S3I##eD-zFjGOfzN;yh%43*anJ#me(>SOlBS>tWusf<>GIGfrW2>8(sJX}RGN;Qno84^Q&Y2@IVVC{W`;xOP*WAl zl=w1ZU~2}7jgyhuL2sZ17Il1cBmRwX#bJ{Af%&Idxm2A;HI=ISNGJQvaUe-NYVOYpXq&lPc@aM_o=2*^*_~AiXNz%O4A2bQ;WP%K4wOXOm4a1j`?<#$p+RWEX%oodJRWRF>3GXD#$!-U;`g_xk41p z+YrZ}8|0V_{clfdMj@40O7>>-B`;fnALF@pbbTkV0m-d@J@iMKd7Zz)NK&db!Jn5g z6_2!gPZNwF4N)jglp)iQRdqYCQUQ(g_es2QAW283G0TWdogFD+g-m8GcEsv7guWoG zlwQ=Y~cjGGDsLG4}wYr`;-cI-LNnlQ&T`d8mut_=%Lx zkyLlu?nqf0csQ{A3 zE3I6%c&3@AbXVEpugIu&SU$3Is~gp3PQ{u=wR)lIlolD)W_)ykQ7wM@kr>rV#hmq` z@uyL3IiuQg_Q;eBZwtQgP_{Sc>$bf)X*fh@TiFU?5WkCToQ)j!#^!0G^VIQq$_PDe zjJ^n^i(P0rq>df%9;J4fDGlnJp`+~hj5irX@Uyx`u5Ok~=S{#vF&KKI5zUz?w+Wj= zpD_Lf&@-XNEjpvn!L=6f{^a=QYGq4DrlLFGw4m{lR^S2)%3@CR*So1n!4`f9yy1leJ8(PtfmKQq`5De zbLh;>Idtad99CAqH_d7>-&`^0P-f+3wYvw(Idtxmb9i`i{qiDLtTNlv{e#!cu+q&r ztUJYnBj>P?pYu^is>dx8wKnziqslqVI}U-I7cc(IIZO$=EeDOiTo~Lky-7d4v7CP_ zFmRpp#&SMoPb}^3%3T-{`rX|aF%6Wt8qQ1~X+8kfOdV~ek2e?Fh4K645hu-HI+(Mj z(n6gzH4AtC;<=ZoeJv#Jo!Iv+=4V>h12NQckw)fN6N+P(eK6Pspk-_OA$#mxfupc% z(p@=1|3dDLOc3>ehsv9UdHI-l*pwdK+~9pNa)&5g)EX(LY7mWTF?rzkbp zoGcYO8#vR%!i}RHx4g#<0W^CLUCqVLgmkp$gwZdQhV(?jByX74X%g8V)Mxg5v1W3i zPO-n@WF!amc&fkLr8yMr{d^@o%|V*`_6k21tL5T~G|O{mT78Qsh3r+R`O|BjzurHGf+S+h!a2)_X-tIb0Y<~w9Gw&1=Q?Vfn(&jj0qb6ptD~W zIU1TnTk8{AhtOd>Gtu}p2N5!o((DQ*gl!@~h&h-UO)|?tqK*kv9@G9cn*BIZ&kSwe z63+daO4YqzQ_~Lqno7^bUsLHi`D zA^@#JTb0An0DTz2M+sek`rzJ9;&urgMTI5A=w+}W&@PU{wL6m zvtAvTqEXfN)Pjg>whq$}2Ew)WaUPc9-4*gbQZgvg*JeU-4Wu;IMv{I%|&^kUz#0k%jrFIV4{y=j;I$b8Jt%?b^ zL^awueSh$`X5&n~YyQ3Y?zE|n-o1Gp{p0LS^w-1sSyR>Dy*vJU{nf5M{`FU{&=iNu zG9x!{jZpJ_<7iBZWK(SU7B1nIg-5IagcP-rdg2~(YPLhzCl1w;|+IZ z1ST!*T4p7H0zKjiOmVXc!I=_T%6A1~`-h$sC*$s1N1>-U){jQ9T^#09-p}Rs?R;8# zt!eHX3G-=G5Az2rDzo#ZV$Cq0YW{!}lzCx3vp+gd>zjJ|QH1%BvPZhri^IFZeE!tu z?&S$u!V{NPDA1hdA1V&0_*hiE)#8APY3q2o(qdxEyts&$$iYj|*q^4kf0n^tXH=zO z^slM(%>FfXKf}MyV7l>mT(_3`)5S0GmngwW56eI%Qad{wY|e>xCOSRPm2UJJMU{IX z()2;gYDZHh(vqST9U0rc@!2s(k9253r~)WivALm!@9_DR)Tym$1)Mq%Y{_hHJ$*+1 zcxGA8o^D0_glRfL?sA35CQ8p8Qd6rtM6i`DscS{*@RYBVEgD?dDw8N6$`F1#lvfES(~ko@66_)P)BH0={9(}L98XMI$Z<~d4}}~{UpI0rrHvdb zz|sqmBfAKfLXIi82U2wqq~s#d5Ft%BK~1IWC@@*@0tX@x6qkMMd4vuo#!?#9L)YKZ zh0}#<;;4Dnc&VBve{a5e^R_AFx;+|UHyE(F(9kn5i1s<^sRwhpm=iIqaO{teLoAF{ z9fE6ZG>@&XHw8y5Y2GLuYZZFTCuTqk7grr7Dpzi9$QbZbU{1EcafNMOICms9NfU@z zN?+=nN?P)G_XsNh12MgLy1=@j#NuhfQ1}_n(r&zcPnZ?}o^$l2GrK4a3biJy&8abL zA9QJO6Jh^g6YTjY%8pLaI$u+1+UIL3RSP}R)l;(3N2CR7Sn1^;r)H^_=GM2?LjW)t zO~SVnm8p+)IXSAnt^|&0XGN{`4*g({)>%p=~(f z6ZiX#@3AAIKjprAGmK{%@$l^=^5x>l@Oc};C} zVJR+lQZ5sfmWtVwiC$JWoWArfp((k*OvvQ`z5zUa34aMA(6~wtbAa7M>ZieY*!GTq zSqbX|1RP>1PLPqnj)~)7AQF@Ovc)=_^xHkg|I%HFtD?J;&cfeM*#yo|+H+aQ_1ag2 zBcG8T!fol7`2p54@DA-AQ4Yke9g&p*#=+{2e`K^1HVf7L{E|5j(@#Gxv{|U;%hrp(Ekv7t zg2{V=$tB?YAAmOJb>#t|X62iJnw7L2AJsc(t1F_F-s$)NbX*txu^Bf3Om5*ne5Njj zMgWe!`W|5C``CiZm`DIFqc=8+!I%w{J8%&M8y9;V1uz%(2Go|NcIrB8b{xVY_}abl z7(fiY*4XcMLQ6-Q5E9`a7YxVk0#Qs%%+2P;ewNYAF-)`jDjc@=nC^DZv}U=z&5ft) z|A#Ka#wi1weYHpH;qA$;+G~Fbv%?@5?dd7oo7soVCq#{phQ9Ynk{3R4%#SY7Vhi7l z%C=Q@sDGrUM)3Xj-_IW0ld7tZFLeEu>b_mvFXmMiRcf9@ZD$Gl&3<{x!Cu)fRQB_Z zyv?6X{zv*?XUa|EK?2E`Ew(*_n!> zKeK7~$Ncv2ZN2eR{YCTXKeo31vZMd8yY-K)RXUv5q&>Dw|G~TTb|=!>rng(3u}_Qh z_UVkbZ*Nq4@kT9V>|C@{7p*dWdwBZGY2(ew(c$qsS?F*Qf<+zA(ppE4z5VIM$9L~f zqQ{s7n{LvP$E}hswl}p&;$1{9zB)dLUX-n>o1fZBGWNxLNoI;hXh(bhagXqRr)jw< zcr-d~6izX%%;xut1o+dc_ZT&|P?=3eKdqw03d~kJ{(x|A)~*z@`4at6wYskRn|tL_ zCta&I`jp3Lf2C(EddA9^=#N!W_YaPoe0@bVcHVp>^gT10yHvbFu2OJ{&76HyaOQvN zW|-Q2HLtbe)#hROFQB*49Hmnt@kvVAJq=vH6F6femj6m znlz6V?1FEG7j9qI5#Lt{4Zfq-Hg8ueg`8!VN=2)*L|w`i3kKdU7dU+|=IDUc$D#wS z7WGz5&Ch@-i_0fEs7QtESRbx@tT$`N zbOy2RyWcc@JM*49wUcXlOx z8+_%l6!>1t246{~p7%^^yKM`IU7nisK0r=#0n!F@>BG4Q(3$Fi>p;4iN)6Q2)Gb(7 zzs#u19`bH`Qepg)CloYsV2ad%izz$YHk+~b@FhvS*fSs9i@J(}gdQgX>%>~*E==O- z2!=7-wR|1TdrT8OM6pQu0uL1wu^i&-_f_HrE23pZ2nt8qutO#qe?}rsOotM^jiUFL zK!Oq$eg_t$Ey4ZIMC~O?L2=Fjb_haDzEk^e=`dVe>Ng*o#cdk97D@y`(!;LO+_z+L zv&#qh<;XIZpN|zRZuP?>yBS&B=6uR7K0=F|Qz!#8)vE>PIf?l3XKUn+FGhOP-zV!c zva79?_!(R09+`!2@kBkSJ2WqZ8q_6FWi za%J|=2(CO-F9xTVW%6;R>+p7h1d<48Lv-CC?l!Jc+U1JcOxxj0wxdql6X^)>5Eo!% z;p(@?U8&mK=|rFAe(29;P{_LN2@yI2v&{_#R<|nwb28cK>8nI_UrD(V<`WU$QkxY zp_wJds0lj;>4l=f4bQs-g;)gmqbMQPLlw)>kf#A1o~ouZN3uKnMQ7hBrX|@;bKjC= zZyX#I>nlq3dGoP~FOB+;Um)3Me`din8rb>&o^Al|K1gxxmt=V3ohD z^qHF5Inw9rE@M|&zl_~6BHy}Qx?G~<1M2O?(CtYsfYxoU`TzTz@`-COCEgu(4E>}_aFo!fP_eQq7;==ICFOyyeM#vQI!Uv~AyaT?XOTT? zDLNHld+4|D>q~bq@cLmQQ`F_8d>90b{6Z7d56?;-l`LqBNK8(ziOx^eAMiLCSX(6c zK7QHj#R=`l#uUIbNRb%P8L6M>?|XxBtLwLUK87A|w*bXaHaARqLX?NBYnih+qb_{{ z@u%Al`jZ}Vvj-+|q0JI%8QD;PN!LzIHkjtVCD~9uI&3nlWaIv_VcvYKARC%?y-;5u z8{|_r^I&JlhIqfgU?zLzQHz99&L)k@F6SJp@MUGf)SS+d36G|x)oLfd9|DsYhd3xP*HQ=UeT9a zscu(RyV2&(${$|z<>cU}za9NTy(&2N{fEV_&0Cr8>w8}h>bB!J&AdJPqX@>$&-A|D z@$|!aUq6L?!X^FD`OfZaZ)@JR?Fx)(^?b4PFdnyDrK3k=j9O4s1xw3h#lYRwHfG zjJ0dALyx_IQhxyrHLBIDp}h6~1$rv)(nWDWO-|qUuIanXp1X}obAc3Y&}3JTXhToa znK2iSVJP?=@cg;T7IMh0`9c18{(N&oJ^AE*q$SvKhZo`QkNCmp;~*I9@ZYIFPyBPWL!(iD zRU}XTi@uoriTdyV#-3EQr=+%i{>Zl#Kc${Lp>F*e{V;3)Q$1DN|Hy6gH=f|7r|5at z>+jGX^67s+&Y1foek$$LfAi(E@RMooThaxcrd?STInA4o6?DOBzEI4~*9G;bO3Qfw zrv!fbUcUu!VHO?Fg2x|J%Pd!_)sp2@N)_57UsTJSUadJ$}q zXTMj0K=Fb=@HBBvO{I%$K(n6;wlNK*gKlanC43WgYe|IjvS%glB8iWh8fr9Q_~1Ws z%I%`2T1Jig%uI-bE^{5yOj|m13+zG>-Jx_sS3F?1o6w%`JGx(+?YJzJ$RepD!Wu=k+ptf^z}PszBo*!>Jc{<^fDD zkA}v5Et5;?Z0=63kOVn$$xqdKWr(;`nDq)UDQ%#XK2%B(ETs*XQUpx*g-kV-3TUdS z^7ddnS+;@8|I`P3c>`vGd#b@v|o+qqia^9|Sc}W>scO+FNLt1^> zg_;~YDhCki#`slcxF1QaTRK&P@yJZ~4o^Kbu|2v>)DmDe>H4re2kvXAY=@R7RNo2a1M5rny)efwCv8MHt(m4Qx#fZ0GimennWZoi9@N^Tid= zYoXq(+p7hpMN>Nuz3!Zw<>B-TFjtul-X2;7_@J~Wf9z@$`tq<-c5=2;{gK|g4dNVh zfUUGehJ1(k{?FO^RA^@QgUdJm{_U^sqjM;~p+symv+K>>BxxYo^Z8hEsv8DN9@3rO zQH@KtH-O1z)~3K#Smx|B+7AFZ%LFdRfTU){sYl}&hNt?PA^|THNROigFC9I~g&$3i_#wDG^`*LFso_h@214d8@(LJuo=^SUJOcI1^GZ2nFB8a**?$N5Ga+nAcL3Y%Xah(s6N%bSR{ZowIt`VPvZan_zK~rpM7bU*y~KcTNXL&Z z(GaW0nG-(hs7lQeY`l(7GI|HO5~E4*3UuOS*clG0a!)LQCyga{q^6_>#^-yXcIaaXyZ z7j72*q8{4RcH@1czxc!SzLRerpHE#&{QS`4VPF7m52?tYU%wy`tf!GC!+fh|8rM_8 zCCF&J+|EusRQ>#}`a$VwSgMbh8(D;Z%OR${r)%E;;~#6aQ4)s1zkiX{6^>-@$8~7C z0Vy}Q;m3WQ-6Jc*7d)nV$lURw!-1nc3x-v$HHVwQ*Y#?7PtspW860#pR^%Z?r%cv2OlrxaYae?;*!XevdBaP0 zAjwLo(MzWec~9oYZkKghi<8uk)x}s;YF*21=+PvNhHigkT8zpaxtkkwAJS3kLCfQb z^hfcpdv3=|yq+kCgn6+dQW{f^N(-sDU#2P-CpF=&c4?0fNqHIsqP@GKOF;$L+(64n z0>8dQ!P`)(a3LKD^Mt2y_d_v80b)s>7ikVvbK*%l^yWrzj`B=g5Wvz0niWu$J+x*a zoDU=iLsXpeVFEx8`)HH)6J3}eup79nK`ZD?SOSFZgc>^?_tjfdhxvG=~wc%#%= z{q*dY@1&YA?XmC@!Zq9TyPZs8`nV1oQVaMikkIOV-)B@QMc*0%@Dxf+m`HmhAB;e& zXsNAN&9m)O^|Jo=raFD!c&X~D@#g5L+5AqOy!r4Qs2FeSyZoQGl0x1kWT+bTqo#U& zTz`FRsn>2_s%c$GPT8Um4TrgJifMU$qUOG(5RH22FrQz|-=pW_X57R|AsUTRnT9bp zL_>dS$t3Lu579_l+kA4LwjVUEqna<x%azhGF$^a{_>Z66KC`)Xkw>yo?A z3+rIhRpzFRCRRpM(lKgkj+RkVtndC8{^zEyK|fha+o0#>>KpXCyETrQ`nOTM)6zR? zDkaUMrWWWPT+=6jrJC?ohZKeVU3F-(#G_(P^r5(tQ^!B|NzA=?b$kGseDP7{=A)pp zkK+LWn~&Jd9c{apGM1QFHu`HUs_Ra+Tq&Ztx#5o>U{Uu~%!Emke+0Iqd@F&1osI`4 z3~@&Y`3%6Xe~yL(k?v-q8kAB`cTZQzWKiFuVNG>4vVY7;#fvD9tEimil=ky$Q65(l zA+@GC?Qd^3zkbnPY81-$Qlfq7=~zXwA5@Ar36F1oDL4J4If8xQ_DE41x*dP~Ae1W= zyPC7@oOS3pg+k?d&kx;EVDhy1JW5!shdiLxDS9 zjs;!dEG&lF_&{RN6AF=j!bqcj3%uu$Vx6Ezdk|X`DkL2sUtU9_(UsoO-(}efY=Abp zY$m?$?1h(Z2hl4~xDW3RrL<={nR>f8=(f&vO0enpxaElqZO0OnB<} z3(*W{BZ;;#O-xtQizRtrirMEk@kZnK6ub1r>-*_XhYrw~RpZjbe1cKmv*{VC`yj@He2tBzIv)rp24~whHxxMZ1l}Hj%)KXtWn}#!N!|`367RvHi2J~ zss5r)AlNTtYY&oyG4MSWCxjic)VkFsd^uzn5<-NOU8*89B?Kv9%Nt#L9$jbXMS`Z! z)21E%al1YG*t_VBcEqH_uF##3&x(3F)qnasO*)P9WApT^e(>t}^ySCKyXN8UuKMmN z|NQiQqtQG))s9oNWEzs+*AL#jgCKiCzx;Ua5`x{?N_;{0Luo1~c&b_inzqEds(p+{ z^;f6O$j4mM3+uAKFGKhO}C*Hc( zs2&g#mS{Xqxo9~h+Wij=3FqGcb0Fc@&G%bPhwt}t9D~2St?MS_oh88Eu}Er;u4us- zaw2#~2ZL}uCH5{E`&+`)brzYc09w%6w86@7_=<6(BhixXxz2J(ejyXJ&z>VOaM8>r z%q-mCx#Ydx>fLR78q?q+_*3kO@+80RGHj11MZ!CWpODL+HHANyeK?p)AEtbQQ25bS%KGaH@qettzU z%6b5`5*?zbxdmeMR)6>#7;hPER=%!boH{?#rZb=9%#l5G`AE6CDf z$*JDMAhOJyObuq_J9O{bIX73y5hQRbR;!g9*B!tAwUV!J{sQ43949h_wVul z-W=nbdL1dechZ&J9$|q;h|~@nBYe#x6!CnD)kY!}ulDTKzh9E#x%j6+!Eg|m~W-3exKbF6??brpUY~}OSLf)D+ zSu^F*V_^cm)!}=Ir*#Q|Z(1z!xoSyr$ui7YwcXQx_n(;gmustNNx+3~ChGWg{jF42 z84q42mnx}9edMeFvj z*p|Sic+$VcS{z0Pv4;Vw9Lg;Voa8t!rf|g z{kE7Nl+F%UEyQlH%Vs=~iHqp)J+H^X-0tuSzemELiPPEKh@4B>b_CYsE^-dG_3>3% zDA@GsynoZk-cgg=SjNqbV2IqYbIJLj*0xCNoA5WcB!T2uvwPr?zGhC3;)YCbc*ziQ zG(n^+EJ_%L7)1OqP6K(CaEj5TnNbpa>v6K=vuNQ-ZTBzw49sXd4M%O7o-1Tx(Bs>) z2Sc0Sm@QJRiA*Q&@XZZZNB{K4y%uT*oSPL0CEz;f()dHR{`@|sRiaJOq6O~p@B0B) zbC8`R`>R2>E*)#abHfX|fjcAh*OvL~^Q(?0#pQSpVuInRoJ5|}GhdMn`Bao-ei(3T^8o-sE5lwP6a5RT_{kB_93-C|Tp zzw|j;S%v)|7(|KErXLwS~*@+9^+o8YQ2=zhYrcy3lrSkU`2rP4ai{AhfBE~hauRkDq-I#MnyVYKz7mPq^| zx80_Lju$yk2d(q<7fl{-p)8K0f1WtmQ4$r+EC09`pS#VCZ5myFJIXUNDutxk)4#XR zJ-ThTl^krOdGQS?Cs?j62#~l*zUo9v%FzSuZQ1?wP}T&t(PzyIG(cW&7A$54rIGjw zK-R_77QwX|?+}n8x$*lMiC!apk#6wqabNaNtheJe=-#-* zo!0lF^BtdV^V{hoek|D&Zsc_j_%bS3?RHdqujgLqxzZZ1l;O)NzMDDbQB1vXrTdTt z9@o_4dN8K>Xh^rKmen#Z@%H4m!5A=*Wwny)7C%uZrs}C zK}0S5TMu0R{CB*G?;&NQt$h+?Bqc`NL4K5gq#&&_0E<9$zqe&-(i(C@O#Z440{GrY z!y+xQ6+91vesE5=$Lxo5Og`jul)Om5QCKPAAXI_)=*p7e>N*v7G;683W4wJQsJ{ z7;bwl?tbS1eF9~>P;zJwz1 zOg=U=o-OYjvRp^X5`vO;85hc5t+VFOXWBs@jadiq27#rH{LfIB@A8;C1%N4_q!q;t zJ8r3!=v+k2eM91=Y#-%{5I09ldCZrYj#UckHX7v;iyNmr|4Vm^n;hM7R*tqEUv8bp zTU4oHmkNby$tqXMR%zY4-Ju~zA%AnHZO%RZXi9Lbc8{-Hd*fEfU$Jb9;%E{TVC#r& z9Lzx=S^36Tsdt;D>Rqv#_1xdM+PW^DceBDNS9_;c`2zc-x2%yOw0;Y>E1j1g)H{=! zaOsAZ0^6m2J6$jfZ!Xp&&=HxxGUgGj^1)tGlwnccK2V)7e z>y5i3V0^L5THJ?Is6lQQS_Y&~55@4+epRXOa6>Y~eob zi%;OYal4v^0SrZ1gsSziNfDDzoee$SyP|W&sRFt!PX{AnN$RJKI7 zhrz5zp?eO>2jO6V@r9Sl^rJ`1Pu~_d0^5wQ`HhScGmrij?U1&M;c(ih7+?8S>^bfS z?K|u~Z+j>hKx|?1k&|GAmqfAGn`Nu{^YQ7~=@xAQW`6X0J+I?yMVB|D@d_wZFQzXH zwV07M-?`T`W&2YT8M<5{1;S>k;4o7tN_cD*zdav!@jSQM@dz$mh&i5`jV=c|$YWy- z#I4l@iW?$QP_r>1qLf~ECDbe8oq=VO4+pYEdRiEu_gVE8w<{eUji1GwAbOji z?=;^7Z^&(D<=Ie1@lU40L0|O}YQVKOJZ}R`!OzQ)uJXClGeSCNuTJq+qJ93|KfV{k zD;n(iA)iT>(r=sZUe`~WCud@4;fGdY`)?`uKPG_h$#*nTgSr)NzWo0iL zvd;Hs4Zc0;7U~T`VK%wo)!;WIm@+GumvnaMvGtVAdzboN)1X)861gw&G0{5nYj-);F_e>9o0 zhV;DW;qPPm(&QSFLX*iSfhr2$?iZZ2fU0S30Z^UQr!~S-tZ;<8a~vXGPVJ2Qu&MS5 zpytX}pEZA1O?VjJ>tMfuq9!rUs*^7A(P`!S! z7^Hc?yQHsfQr(#~nFImh-3nZQVQ_0>7;T?X68qXo8jO2#Sw|4u>$@!tf`Fabx}e!e zxB@o2iy2#AwA(6s2&SlyjW;Jp$1gsU}Qh=3pak0ZL263!@Q3IY{z|_R%2pLVs&`>(S17Bn;Q#y5&gir z+_&@jeN+Pz?IA_pCn98b%%mg&c18DYSvGimQrUCYe)wamV7dQ zFq`HFP*eQCE)81X@xV&sa@K`XReh8&l4UgTrRxGNckL^WYN~E-)E)r>p{-YYbQq*| zPY_Z|1%=dZOMT4Foj~Ni@7?p26&}a$?#3i|EZ+FF`8&UM7cQCU<9Z!vL{d~a(i02& zh1_b0gc{1AmDu-dn)`-)Ty~L=!&1;>>FHR($5lEytlN=~YtEPQ)ulozW=QGfJYBP; z4T34QX3ojhd;l>k%wlQ%c}BR!((gW~TdrIxm8({n_M%-#)h{=9spsI6$5M1-MZes- z>p^Vqe`??yGbJznZ1*n{~Q$J5m5dB679k z21a=J2)H;0Vuk%s=tQH=^GHA_-|49TWq$V8aMbYw>$0XvL2iyK4n6TUH_VT-=4~C9 zoY)JJq@-yiXqh9YFS~4Q>KyLyk=_KqkGXW2I`^(=Z}?Ia#0&Sd7kG0c&S{Ov;}^+w zT7+U18HVY|^{BaTNUrA!m3)1v*p;QHV->asyHqL9m+SgV`Q>6)?5`nq#c}LvInS{x z_Szg)^MvH|yv-4kUpISqb$#~klRyH|``&d%QWfL|k{S{8n>|Chsp$k!t3wwd;`=R% z3Xyw{lY?5Ewsqo-p#Bf8E=WM}WhdkXC{ZC&(da)Hz>r|PglGNGYta%v(|6n_y{jW3 zUR)x)g>H3JdXp&#(en8T@kWsN?MbaHW< zDKfL+C>SCEl7EA^1`}McfUJ_wen~lb)Y{FhDETBNx0JBYn&!SC$=9eK)~icNzNM#Q z6^*t`!XT1-^Szn1Wu zNYY6;-ti!0)rJ|V0|Xx61W7$C%7=Yetn6a+7{LQnF&j2v`gIapdk4j!M=&8fK; zN#?p*KXSRZx|UfcnG~x-_WFph^R*0gTY=}8nMpGo}-FWt{%)e zA3AkIUw+K&^L)@6PTao43z>vxrwmhnF2C=rNH!;hxyn%4s`d5R#ENzY}-Go_dg<^b^KZRrAp~X z<@K}gPk&UyPf*`1XIH)O+E>9Ib-~V+9geQ4x7*$z&YsxEJE(gC(#RQ1bob-Li#W7E zSRD*F9l6=}FWCAJHJk%KU{3Dv=(jpcgf5`L#ULxpbQ%78kM8~j5lm6&l8IdUQ5U~? zdfpALfoAWNO|zxIV9W`^mmqNICyCTtQ?RGU z3{Z3w-+#!03*(Rl0#j9R$yzN!XhC~AGYZPU)<8};q`*h|kLJ4ww+JBfU%8|&3b1>g zVcdZ_34AWf4Gf`yHs}cj90ZV1=s}x8HTX_MsHQv1^fn%KRlR-l)>QRTI}tm%n-ooW zY~Z%9=q&9*Sc(;PFoZ81ENgvFGkq8}ixXX>=#k7*lH@0$$wmDZ?juge;V>|*(~(fc zkpBbh>kNAKF{Oq%hf`ya*yze2INb|toOl(gBq3BpFgMkxvk!$A57E&o84LNG@B|LcW}`PBVAj z=1H^1Le09WPO~m4t1wwdv}M;YsI^S?BFdcV?WC=FY;;641dXWY(~o!P$KZO~r3>;1 zK?I^$Yh{IPH?U?V^0S#PGooVc`JGOe*~xTD_vVHqiPFD5A~fb>ujdZD&eMJ{RR_bk zuq??VX4*KH8LsG^2^*HC2^KWP8nm<)NPZ-v+Zd&~{*@PNO+~syCZP_&F;*~GD%+YO zZF7S@NJ`*`0ILVX+c61Tcr^QyDDgbz_`dq&cU-!?FDF)KeC0`<_ZwGr0rA10yQ}=;@X_7<9EYcaMKhuQ18&7V6+|1!Q+P zdAjv9`#1adpWg0^T|o^r1YPq-?r|M0XdR;JN!qsv^&$nrDw1rGdNo&Rq|D< zTqsvayVgPpdtjbOnk7vh3(?f9rM;hAS)X$X*%Y%^b473g5?#8OI6EIv3HZb%xWgfi z;704u-ToxoSUuAL?AO=VG%a1a!p-FD{g0lqy?1Y4pY91Cw>M!v6eb8A^F=?r>?(pR zDr*7~BzlhaV8!~(I#WyMa~^|d0Mvx=@jn#3$tl9XY zI6xPLuU8KX`CPtSm?P`U`?@Sff=-Ti(p1I=Ky9@au)q}HJI^Oc+l2|8L3mEfJ`FLN^~(5OR{b(9c!{~ zEFFuoZfv$y*-S!8wq@N~!oG~SRrea0lbQZ z=Co_iY1wG;BqUn{SnlwB4)s@!VP9QMVc3fgqyNX=ySBG&UEQLeqvz3oSk>FJEj1(r zfOmD9Hg$`Y_*Tn~lcw+X3y~0oh$I+-l+5gN`rFSObFKw~7f=)lrLx{y)z*tn_kGkjy1Zy!wdgu({O!Ye_uX&5-F)l3 zcyaCze(3+&s(k>4bvqX)D`D70lRJfB)13{jb5N{hhg_w(ugKYOST0l=YeQE|M>PXy zLxtdnz0}#T;7j)epLcm2z68{nwZo%Uu&d(m$T(Lwb@9@`<6~j|TG!*TIpNo0+=dvq zCAcj!A9_}}M`ee4R66ECQK|7Wb*||oks&I@WJpr_w}@bu`bc9Pe8v&rh{UDCfXpGu z6Vg1bT;8nfN#(cc2;b?{FXT=6D2}FE)eP)@z|^iZOH582B9F?{kP@h2#g;U7^6-0X zJ-dv*8~c2cAk9P6cc)|V4>|loliWve0YyyEDH9z`;_6X`oto{@?JLgvaKhwC&*iAY z6ZTpH_8{|=nnA-sAlu^F_xqDzgT!aKa*5D8i2TR4?FpY!+j{PqR3}-^N!M%R{mw4H z#P*h>5RKYxf}ju;UZItAkTpryh@Ss?#lJGw*9^8O6%O1@3bD-GlG5A~Y%$lNH6cbw z2s1gX-Ps=?rq3^op)+c8T1MRMaL`t}{eIsqS^aJy zU$HJmm-*(E)f-!!fZvlkd9L=DQBYkrGBLD8Zkv~2; z&mSEg74qjV8fuq@wom_}7$kA)Hv#4XF`J5qZbf9*cUUH&$XnznCV(#lV zKkVsgo?D(&i(TRHs9JW8ZTE1I#f~(72js1JyNr1}I$wDndB(2bIsV0QE7-u6V>ZF! zH{Kf69h4L2Cpyj%PKn*1P%ZDx_q}{ntThguVtG-xyn9fr9JmO~x6$?QUZLw5fV7n% z<2?|Xjt>3aL&D4pOC4cW)b|5xXYq->4^Uia%9E*?HRU1NwOeSF!>C#MwPsyLm(Le1 z6*Wq0gJC(P>3CIA1Ec+tW!GrF>9^{pWYsKtPn~qVX0-3piMe0(wXAua~8Qe{*f)dgKBWH*EQyw-{}McgP|`Go_Msr5ZWz(btpy|zcP*9?upzisUagx zZ4%I!FgG0=9HW6|*pK3#>m-6_iu!#)lFz^4T*Ej`K{^|5Z;c{J0 zd&G>ZaT;r0d2P0fJX*5Vvg?*AMaP?x7zvLF&f>`)Tk$)%) z|I|{a39v7lX^i^6x!CIIV`41)6GMb4J#e4~m)D&`on+k*q&; z3+2_S%cf07IRjbWs2*1;%Vquim$LTHuWVsu`G2ROIG48I(@0=7o9aF6#ow&j<^uzR zvdy`URjcrD$&OR|A`KXP**t92|AzwxMFIx%+dcsgGPJyk2)NTF?6!_>AyIkT%mZD5O#Zu+0yy$^m{kKAndWT-AL6* zrAr#Cc&nLc_QUzfJ3&>T$fjf0ID3h}1$s)V!)m5fUOeHT13&BP)Hz5rhWRct@hZ)I zHPksLhlf>XZQ+W^NDY4b1E_OK1smys>rv;FxcjB$)Hy}lB?Yzw|FP|^Mx8_Ly5e;H znyGU(bQ-S`es>=?@t4cn<2)AjwvBjuoXt7hrqQnv@LZ82gGjf!$|%;2gz}2fZjkEN zB$iQxp2HFeBi#5iAIBvqaxLV*MQ3b>8xyHun)`|r z^CP$H+N)RVRP)o31@CCJdSVx1#eBstrLpuBzXX<^O4+fIrdur8P&_l#To>%vr3B)R z^H@md*3?{`&FSVi9f`~I@cr0fu24T1fP2x2ff`WEfcbVJSZKYDbX3UAV;X3_)FnEZ z?u4PoTJ1MVUe_sRtO!c0L61dL(}UfZv=kHLLGwXv`@^OMyL!8g8;#o`nHTx;<(O<+ za|VGgD4xolcrJGm)5s%w1@Xdk>@;I-k|h^|@Y>hJMx#FUni<`_COEh+1HaRX_&v}e zwaomJfF$`tXZ~7debj?c^joki2S|~V2S%J%#w<%x6@*$v>!j@Z9g@P6mPKRc*rnL= zZSvAC!q!+sCTYuHm!Nm1i-YLdM_p9IR?9w#39%ASkKgSN$9FOqB*j^ciG(u~XQsKY zhrz&cPm8OuXwS=-l-Zb$UxIc=UCcY>e7Tf&9LsA(oy3UldVt60l&x~fa-4P84T_b@ zp+g#Tt+r?kE|0-kiQOPYR<1~Hu@Akf935^JYi-T*v2g5dOjqD!z_vG4DUxL>I0GC4K{>TpHLa^G zWPe@VOZtnA`o0K2-pU9CP(vBv0B&hhB*5cZ3k&d@8>0ap!~#4Jw|*}~z;A7a2?qUc zG^sIJWUj1HpSJUXx+l+86c_l4gW-iGD{1+I7Jn064*M1FmoMR6TVyy z##SfjjoN-sqPNZXL$}6U?N*+* z{Af5BHHWYuE*8x9$>oXJDS~JsRmGw_W6U27hx?9IOw0dq9t%C;f~B~R6yBVh@I{~? zIq+M>v^&#muyJMIE{)@g3ss3N5iGCliv1J6D3!Y+@783OaN#KW_X#a#FM7i9P_%e$ zF8;{-$2d(bG$7$m{3Rxw_1dWDEbS$Ny{2+g4kTK>6I@7pd2`v)Pa^!pio(&LiO`_7 z4=d!p39p~L!)fMp!}eM*#rmc&mFkaB=SnV@K>M;A6%QNzD5lOEs7uNrssNjDcrbw*fJQ zHSclRY8sLc`A%PrK!S`AI8H)mnqoRV`N60c8`{x`6mg!^<5JANjF0QJ)B6b>f2KDu z=!XGDcqc`D>_9F6jh!9m`SY0!E=EI58Oar{(Ln%rn!$1G@oo=%e;D0uyLJl)Hj|xY zBq~jFUy<#)adcQ(EvRf>Zl+Af8n$cuwA?7B+pZUXDMcrbYZ$I-xlT`B$SQiA^inq9Dhn#83E>FJC+3#tQ*yqZf!@)SdnWb={ z)}y9nztm!k;(%|kxDA5lFt5X>@=@@+Z_!CQ>&1dk{f~L-lYZ(Go_cbytbw9)p#JCG z%d=N!?>{xppD^Qbv%`XU_c9nr)R_o%SR?eO_U>wO_k>i|WMculdPz5C*Y>~%8Zz!- zcy{kPn1d~K~`t+QZPajcQjiYm>&k;mIBuSoc+3(Dvj~O z>$9I~Rg25AXgMr#*&6AEO3&1B<9&mnVK7YCQ6OM%IV#ToZA43ZLE)fGsJQss~lBtyfGPbi6s;SGBCRD$0_BAIH_O-=Awdm#+*ybjZb!|-A~f zdlh|M>d5st#5!4unp29dgC56NN69Y66CyHbpG6kxeN+4D3?bL>0!R!Qn@BIkw5PRU z4)RXYjc6iRmNp-H9fWD0k!C>3BiKdtKEyQ1=1!>C9aGMFiexb#4a%i`vsI^h(VbQ1V z@cNrd3tWlFd>Na9JJS-2d3b9ny2_&fNnlAfT$KIU-n#ISZYJB3r-W@I@HNmSM#v43 zX!tIZqM)Ch&*4cbDvtpr4F52p>g_jOAtmWY~?wNsy^oW(nkh z3M=+OX-(Vt9P)^p`-+gqVd1b*SjiwThX>N8W1S+}hb5<$jyx8BDFt~%LGzmV6@4}K zhtIMx3)~Ni6bh9>!Fv1mqqnt+<5=NnXxoMRDF63p;30uZ)vZ-ah`b<_l4+_~`l(98 zDUXFJY+anPIY$`b6h0U+PH{O^%sD8e=a7Ha$;WA%Tm41v)0Hjtk_7aGFh#P!u}27w zITci8F1YHWH|X*H{UKn-0AFRyo!(Ih$&^HA$)keee7)!~nDxuazA_NI1d3@w;!E7T zb{53B+DZg8zXn$@3<@{h%`dcd>9+nEHPVN5PEu*`fQRgs4=fWhixqc-33i+BC(1P_y zJiF_4y_+Cv<$M0^>?mxfUXnO+=Iamt5Fz4YOd^B0}AhY6OyqbyKW$^upM zj-804n;S86*g1>p0@Wb?ZO1AWtF$7f{NUncOTz*`2Bci&&q=N7VMBzycq#SGNQ>Ke z<^33(rwRSX!hANfs`r!po06WHy%3ia;t^176|kX}WFBI*U`U=y!|t`@ zZ*?av6jA(#0)A;_>Rtnu}VuL)Bc!fXf&T|b+4xhY0$bioO*Oe3Q@+5%anN*Mh-3|gnaJlq`f8vaL+FdA3DRb8I+2VuQ!0#A! zvN?xiEV(^hXh1A=I!Q1t!yNKZ=;1EECN1sz!{$DGX}95^rTZzH$Q&kux=4E*PUafh z;!n?}OJe5fFc|T1=Rew1*Y}5fM_T@fZY^X-gE$0%H9IifuknvZsX6qY zO4)L&)e3F0LN!xsOX}*T0k1EdvCr9*v2PZ0xpt*sD)r#J#5R}`XG=i$%*}4?i4}&C znv_%A1gn9v{r+gJt}?-E@EY9*vEli9VK&geODo8m&`gKoq>;{A>Z31}j>NaYaS2g? za;#p@4@VIVkM7j%EpfeBN}n-BdxD)P@MGx6&IDZn<|h-hh!Jm$7tzSth!Jo;!3DuA z4S10U6e&7fS3~ZiW#J9o3NQ{FN#dO0S9Nd)W?8>KqI0SD8fXEv(abDuTs27IN^v7j z;1OqR^wh2iW;K{8+SBu&0npVx&CQ|i_STH0GeG$kP2!Z3_V~`9<j@KJ2I~4@-3mn!VggY(NgE^sN>+{3Bd#PYkSK$R8h~Ut0^QV#8uNET;{fwXF`eT z7q_?c0?$nPJutnPX651JgYXFJ%_EGH$-fmkD^7*l#f$uBI4Px!J#8MQ#Fv@Hrkt7A zsgs(WR2)M!w zp=gloHl0kxpd7=NdRqF9HpyHw%e1-nBS$0~{YToG)qqHB#&RZh|7+SIvopCt-kuYC zx`eaEE5;=}26TU)BCzhW$ED8rTUcF78sg8rxyqT0uz~+1k~=9Tlr~Ez&V6m`*Qfp_ z7$AgUdrO~uZicTq{ANPnYU%LzgtuTQOUHz8L&IZ7rwioOqIsDQ^F0BSIJ2kILgr2f zf#_0Y_{7}eL19gLRBDKHsq&PIO`7|Pbg83;Q);ZFOYwB56{cey$MtE|K4o31WUu_C z$Er(J+!|C6`qgqO53EalvzprT%{pCsP(7`Jn0U*vOSa`^8xWtmZ_>21FPuKr*_1wY z-ZEY*Yb}jWVyvZcJyn`hyS7A2tELCJ*K2Sd{R2OT{q(E>wCYcK-yqUI)KvR53(7YrngdK9!0qV|ka_tR>rN+1~r zy|p_CkO4IZ79nDXzVO^J{NS!8zz1k(={5T5Qzl~P@%M8^L zh-NawkZ5c%j+8u&@ahVjezX>&uAfBEF*1^!NT0ssmgW&sOy2C>}P?JZ%hmnw9kuj?wVvFkPuNtZ5o4%zew>vF@o)*ai&`3NwJVpHqX)D0<$z>}Nc2u2?@xqf1d z7qr6|Q}fy2`8f2Wpfy72SK0(sOS(ya=Qo ztueeiuChx9PH{8CyOWF?H_a^pi;H`;CU6|wHV56SX34Q8I!(UvxXd$Y>sa35) zbq&y1tJY3x1R9I>N(T4^(~?RXz(Yw4faH$rR`Z2g-f;*gaGlu6SblL_i~iawAPu05 zkxXP94Qct5B)AZg@6q^l1fk>F1*b?bwM75^>e++9UpS-!56O{Yt8*m4rSo0* z64)tEFEb++rbc$A38AP#w>-@O{5{HEsJD8@EZIDZiGb}15%B52_YcpH6$;BHsDKX~ z%SjLcyLQcX)+7Qp_Y*LidWn;ApOSPsl3KNBfK9m|{{_st?qgxrUA)lu!ietX{JLkx zTpIaIRO?M>To9T9VZ_VD=3X5NuOtLsj3UQx)ay6zm6R6U`RBe~@pvB-o zb+0)HK`6^|PhJPI;e3g)7%RLkwM0TX2uFs&cc2*%Ku}8(c!zdD)88>xiUseYPj`CV98ZjXNltEC<4(ZR&VxRi-t+T!559kVb9)*`f%)^8Z zH`S#L$yz;dOKWE?&fmADxv$6!nn}{vHBB6T;at7$=3KqA zZfi$U2Y_gEvzvBe5Xnw(N>!L_L}y)PAw279WARy6O9ki;5Sk>RNMi12x;lRQli$19 zF?I><7dKtB6sPyo?nhc2PMT03{dlg~`{9c?Ga9>*v@D2AoVCYpkZ1^EBALh@sj}nk zElty}Rc>(O^~N+|mbPYOChak;S;+gkx}U^<(d}C5Wf)#ZvTVACEnU*ce7>j7-rf{V zE54?EJmZfs#p6!UE8}lJ(frUR{TQ-W@d{pjY_-VL-0R$M5J3=G7AgtyE=Z&S>+I5e zz7=p}M?UD^lw-n7Y*(5774&?Q%}DyVEp)qU(i?H^kdv@6$IbmwdVbVo{nd7pO6{T_ z#(KU>KQ6CBYFsRqPbw?v`72M$gXsCor{r!uzid}ZZlMI7*Y^ERr{pgjQiX?9O4IX? ztL0LOFoI1Op}0WL7e2#85Sd#*gQdd1yPmJF)qjna|5#Xzv(WN4=P^Fh2J9;d)?w-d zu=e0qj+bc?_%SEnjG&bx=ZUi;IvHTa#fn1UuKtaL?*N|W; zLp;Pm<`ck1kYRx2e}30&U(-g9W77#c9k{vk9TQT~WB1_}Gcn`xU=*hkBp5yC0cypDvDPmHYH3A5OV1(u|kw zQrRk1+@j?cGOaD2zN%?b@Uc(`udCc|&K*3<3*d0H1H;&5%KeJ+Qk=)x$jrJ*N=<1w zL&;fJnaR$&%1V0HRaWw|t}>FK!bQj-9rR?-5ST4%unlm9#*oSS78mVpZ4iQ6CkLPfKfyTue*x5A{(m zo|08`*Q#4Ax^{{F{9D-S+KYA9Di5iUuDc!|A6q5TLkqUMKzE&Q&}N&l^Y5y=o}a%| z->O-QZEC`IbI5xU&Ax8iqppv7~2n_JC@sC+D|3Ut~F z?690P_n9P#qVw6o0B-J&h5>~|GOv&ts&gQi%i%HVLfMukx;saI(IH77>4V_%#rT}{DGEE zH{WlL#L502>&Abq8`IVenf9!`1cef$X=a#3n)`~p1a9eQ#cF&jPsbWwf)nTTq`K5g zfG;_#l`lxql2>yTY-asWGic1q=Cmgt*7{!oC9zolOPR6)r>|BTB>2Kvf!*v`fyMd{ z4WP8d`k$e(fw=%OFgK6@xWnL3S6P`H>MCh;sH+Uk4t2Gu;o%(;O)aU9Xb#=Erf`G&L$-vh&DEf2V4wV$Qh9=qwyC@)O zsD~_d7JEkiOL$l`=>#3IxL?Q#<=*`Hg9&2IaRr5gf6CACI_(*xNiJxKtK7%s9HpZ) zKRe6X-^rnwo#aP$?rm);+HePChFqk%uh8jIs+3QwYa^J2)ACrIE=9XoV5du`7zTwj zYYT>y?sTa+wPUNo$irUI+ER4>jh!ybAmysCZYB8j$6I9{3rBMnR+;Rnnu{d~P89kD zl0LgLLO|`z{$xiz6=t+)En@=S)`NWXZ#_%cSI-Fc)$fFT^~_*jeG2x~vxI&1&0wFg zTh!GI{EPpho*f9RX9NQ4S%Sd&T_A8WMHzuX&6kk|2jyEEK|;;EVPN6H7T1M_XlNE3 zPNwMLJa}}hU6e}52veHo{^%qWXHmw*Lb3@?LUGo>l;x9Sr?_4cO4_tMXcEfeDOm+m z)@)8facuiHyGPM>g;D&ZW!yOrU!FU@^LPLRe?rRuJI-7lPu zby6j2cEMfZ1X=i{6pi(c8|Dx2g`~=aIi)%E$-D2tv-3CkYPCeNv$XW?V{F#S_h})x zNc$?zl7d~L4M*BK;fQj!JP4_)nsz^b;dGJirgV|1&JqBLC7N?a7x+`~GKtP{pG%yJ zC3{SSM_8gwlXJ!I=O$lf8ae2?Y&KBwk^2XJvqQi8pMS($bpGV0K7QP@G^%T;s0-eI zQbQuvmr1qADLU3#xk72*9ykI9tkqum%!kP}ZgdM6nG8lN(B2J3j=&1qVo&z{ZJ<%{|>X-XL+ecyI3qoD3zk;6-VF272%LHTcDBGYGr!LW*cu03)aIJP<1=k+` zVes^{+}-0m|93|wxu0JHB?3|zI~^vl915S|4s_L)L^XI3b2xHVgxBS#pa`zS#YYOa zQH8mfX~9{OF=*uH&`Oe_O`7|o)uX~9U0`8=BdbS+wM_1ZwbkV1%G2_o>e1pUxm!J| z*iH#{a{OEV^~!Gg3y0+JknA+|=%nhNo?1oHHtf~ZBNQdE4@#wfSM?}a3PHk?{slTw z;VYuzD{M~1_klTqr&eaV+QXR|jCx|gDe=>%8?uv=N$8M8ijf6$y%9-^N;p&xMBg?+BR(tHA#(m`b&+6^Q{Nu(kwGSUtC zQ(wLD1_5IQ^vANa+>a@UgP{%{93Cyfa+ zV9J;5mGt37BIV(g;B?79s|2S%`Je%TwW8zNqT?1a=K4!rsWf@}g>(HCvf=u}E{}EG z3ey1qF)z_^HG>B-O8?2bS;u~YY6A#%1NWKl5kF&mARzLsnPs8 z4u=5>^N?8ahjZh)X!q&c#aiW}9a+E9Wi*ESvG*(fmX6F2AWZ#It^$1&NTYOO|LekO zN*QFK>>j#R+gW3bHjfeUY9$`yImVb|y`OuS+3*q%3ozLEUiOE2VW}U-UbK_4V}8p2 zls8hzJg{^QG2;d$too59n{ff+(Tty?ekb%=Vgo$p)@FAoYd%|1@BEfAZ7&{kJb0XRjxk`iJv;gMUWHTJITkAMZ`hs4S`n&NZ9-EF@J{YdWz8LHHs#Z+JqM%f;m3waxjW${f4lG z#8ovr1mA{?52Ro?qG%vAx>A*}&)l%Qb0nuQQ2+*Y$4Q2SY-#lrwH@L&ogpe>4rwuw zv?kG1@eb^J&1(L_P9<_IugLLd5%IgxPcmmF#yCuQahggeHuSlKs#9a&X zKj$>&%xI+SC6`6gquJ$3P`IFU=TSE|3|nKmMZ6oj@A!wJ!b$!2+uPfjQ?wuU{ocMZ zi_4{oa(V&O(7UE7@^tZG8V;hvfRz}@UHX0teh8McA5f%eIHDy$RV(H}ruiTo<##W< z!N7|cEu3jIFsUnyX*KzTAwmSVx5C>V$G(kvLtaZ@AoxRkPV3VFkpgHq#;Fsw_jMeh zc4LT8N8pG9W3Lv55@4o})gB#biP}Sf9qc89L^1kA6B0_MpA{$>^bj+pUY;^)xn$pq zFhl#F`wRwkzJavFp>k4}kGBk$cycPQ4tXL&PGaOz#q{9lHqQ=)l!64)mvqVPa5J@&r0$9|bo7POCKfiOO`MC=w+l6GJz;zwTTiMSApn&pr)F>P1m8er z*s89wLRYM#XN9loY88Zq8T<}G-nm41#9J>I8qd0v_T$O~U0Q*whzn{S?CBZ?Izn6O z6>uTMX2>uFB_qR7b0Khz0G$WGF%x>!lQMhTTg-VuCyE0KylaUCX!#wVyCOBgd7`_c z;V`bXy)oiIBWq%gIJ7-O+;J>5E`@9XYbmT4x1nNu0P_&704h||-}xSaJzeI2qgS5s zeq}IL=jXlW7fNU|_>ULQo62x%OA$;{e-yiySBcGEW>cDK3=`BS)wnHMQ`$Rc(YzH zvx%83oFLvHjFHU{E=KLCh>+;|CTMw`=l!d()f!#XK5@RU3Q>Lcr zH&e#ooE#p5c&b>u{}^mNhAHFG0uX1ks{tV>51g&$*c$*B`P3y|`qVwJWaX!kPZuRs z5w*1+1??UJvbsUj>$IQt+x8pI;qa4R75S&7C`Nj%^jxF8`7*?=3_;dJ2m&g$s~Z!Eq|( zlI2vYg{tKko2xZgR zp*IjiNh`Qy?PjQ?_ozm^z4gyHB@T+DPVUR8h)~2OPAEwJx%S!?i*bzI}$#qP&Iw1R2{-yeUByUGea2CYjXk915xBfo($+{=py*Ns_gFyd#(}g(wahRHdc3heZ3|^cd=?&#iow!)k_7+N2p=1`n$16)?<#JfzWuhqi)S&y} z5W6JzcNMF)A-Mp7bRXk_M_$?=*u1U zKDK+d=uR2RZsGeqX4}G@FWsVl4o~3MeEZ#7b$<5U>&E*J?@svV+gtH{`CV`H_B*-! zDr)z;*B|y@2EEbeJ!Z5$t{xx$t?_Nc|LKF%__gra?Veq{YQAe+hktK;Un}%p{a9-J z?WgkZCpT{kzg6k1eScjjy*P3IcGT{TTEA~^eRy^Jefj12uRqsnXXU^D+V>B?9k;%% zKHc7W`orJK`?{Maua6gYGJSI8Z#9N%wVTgJblo?-b}0DmbP#Ci0>62|SsTAmLr^8B z0#~KECHQT&KWk#Q7Qtg{YFxYXKl;USNg@_X5U-^*z+2(C;FMPaZ!1rQ8N}|TS)5Kt z7aLNGP5R=bOvOFq zKYD{6%c8lj9-wnv%T6RSXIszir4iqF`AdUa?&yZWa>w2cnBcFxZXfs8qCJ6~uma>; zVvb9(6fOapQ+_`QZ>Im!9x9Zr?|v+n-176eX_}2wS_hEb7o5_1uLMuk1e|^@_75+A zIy?XEFg)&7qrpz%4h^CS4h(wGnrCg9cMfVQoO(1I?mJe=a)UIi`BNSOFjpLY*>GZ560@|SLG`Xni+0amATrlp&+cYS>d^tA3 z60EyK`O>iG^XRxnYtXxan$#u{hgP0MVx03}VZ5gQT!7w1x+Ve<4wHbh$2o+J|XqFdT^v1U!$B3&}$kc11fOJnwBH7s?o zr(rx)K9xVVzL}Ls-@HCQd;iUcUcge4P69TC{58Gt(SM>#J`)rOxdJfF`)U#-6*t@= zKGs#9&PTmB=b|{B{Cv`QN6JOx=hMco#iQo+-x^2l>%ZO9{6bWA_HW;OeD|txL369x2Q2}?f?B^{9${`Isee;1*fk+j*gDLk8ZzjT>m<;Ag*7ZL3;B>wlmG)x%&3U||Mrzw0CKY@A;CkC z6*8@>tgvZaWrI%Zv#5Rpd|FpoAylv>XNFOq?7X4DT$3)dqcdf?L95k~3JcZ~Jo2;e zT>Yr4v^7!n#_-tCCSs!m@ZO({-6-TidE-kbzTpFs{v<0vI-;*dA^$6>8?om#=RzB}9$zif(1)-jo8 z14bSTWAVCRBy*NxT8L#3#l%-MUP**SF1eE@@FY8z{8as>JDlI)$=Wz5y@D;zS&R!qB`APYm$d{l$ zNW&>fmu!`~bUy9^mPDHG*TV#eD2uyjsaFt#6H;4whyx8LUqV1#s4Pncq~}kltYFrm z^%9HFz?W6RPbUa#!nRBzQ9j4@oIv=T%pJX*5Ff{w5!NIVVaY^N9TQ5(l(iBg`R5NT zeGhnko+gQI58jiYyL!4m?DpwTERGqDT=<4ZSp$sZ)UjyK$;8TofB`LzL!gfWj19K8 z%;Lq9q@zC{xpAHL*DRCs)0CHJTG@~t4z78R5}Gu1M80Ih{0oABZ;-a#qWP^ef(lJMrfhe`4nuiSAyz<0rNY&0R0-O`e@pT8lKJGa7CAJV-q7 zOuh7mNIaKA1eDvAADDyhLy>?UL=(vY*^vSI+gn#0Qcf3ETuzOHue;XD!3xELQi0UT z>;S1W_mu=IoD`3$m9-HmrlRt2!3yr+hmUThR4y$HR&Z_pzWpx_R+#R_J%bgN!Q)3_ z+AD#-MB!nR$XOh(2_!`nVGb< zw_b-i$-Ob8DufbD)Ge1@L4ge<)kIQ|g#RpUchxN;egUqCt|e4stik;dTM`ft>DfTH z0Szl0m};ups0)r+t{=jYtdfJKEu+xj2)6{^*+fe~DeU`5gro0`HPWY6THT-*2?y=OORbQO-fPPOQEwAE zKxE9aCb5p1A`o=~2rk%@)4@n1O&ClFV(WQ_Fu*q=TrIBQJ^3=P~f*b&L`KJx`= zavt8z*mOowj1ondZ)r#}BV&kT!YGnt58J&Y9LX#RehzzV@MmNy#5#sk2ZXkg7Ixuv zyq*X+erMKzkK8nQm-d|@LBJW}$G5j=86!^HXU2j_)HX0Xlzld|5<1UGZ#rQcJO?_k z8}DF35VUQ|HS_p6&{$#sm@64OYTz|x&N=Oo_TR+Gp!*jm-_!heMv;;Y0Y(JRL=;SI zVz&BQq~hW@BsE$tP(3+~0aG~hCt0U!b7>s9snH!yeUsHO0T;Z5TJR_EUKyI>At%SE z(87zPBHCinm&k2Hcgzk2&r=_K)zMnm|GI68Kc?Z{g%;Y7jFM%teI#gt`1 z&!Fhb9vu{%WE~YE55}3CUnR&9%YWO;d~^;7dV81eS~Y$ zT2Vtew30;e(%e@>*l<)Um#QlTqOUw1YY;Zn4v%V+;tOeCx|?iLvY`h}XI0n2N=bd$ z(ixPhPQ|e;w^p``q>pDwXOKQ;X^{U5FKtlRytKiQScVyGqmv#WQw$$DVVaHXqkg1u zHc1~`(6A8S`T!F{Cf2vWI@67GJ+<3#zyez=xqdDdv&^VZAGioO!5Zn?1cN*NQ zlp(gTGk4b66Kp&b?c=}`fmpg9miivZ#0YS71ihe5x$CuQd++H7+0g%OVS7uT`$)(o z!5Yx=##{pMX9$0~a10G+L_nkIM>_aKN8{>KIW2JZ2hDv$p4JX|V_}UrXW>zK*psro^$VahU)TRc z2u#8Dy6Wxu`~No8sAg(0=kwZHk=G390**jNwY}ADHdVf@@^4&~|A;t}{73q~#$|cV zRUOhGqQnndezW6g8xZZXk8F6Nj(P)J>iLftVm=yONPa)w)I-E4F1%L$l1?3a1wp8+ z^5OOt*Wm`QJCnOxA^(!+DKmd)1t_nT@3uc>T{^IF$Q)<<3(9)q zH{<4D+#gyJY7_J>L%e8yuO}t(x5(6O5p+%br0;bXyd&J;iN|heLo+u|sFY>xcY+K2 zOaHe#mG5vb(i%BH2!xp!^*;o;KEXRU{)95rPY$-X^j6$&-b4lv?t@t!le53D&E`SH zJ>Yl*Ao>=Pn6&2(c=vzg(NFYf=abB*e31iw+w@!G8W2oMsNP2n?y(S#qXZ*| z!l0$nKCl}N2ZYT{MHKayKH*dNi;wsr_QBCD#ih0tb0%>B3(Ws5Nn$IicDelQNgXOX$ni3jx|hS z<#MrXFENG5mr8evhp(!ZFO}I;r7~x3o&dqnT?VC z$Hwdfa|oPEV#tcKg*ajsfk}bn_DJ(21+|S%{5~}LShAd=KuAuz6JsGHtfF4$7Umz6 zAEm*-H=8t=T!p>o&PACEO~izaZds97bt>x@_Cmyzo24UzsX3>m!s_&l3&x(B# zhq?}PDoyBAdqser63hl{hnSg!QSZ73&iUOJCokV9J)wJQ*LA9vTSBZ$%_`e$LZKP3 zC&OI+m08{G&^|3Ss38}Bom{s`$Xv2Hx(Cr5f+!Oj+Y5-G>2*(4%{~S_f?^S?XZrW* z^?Oyq_Dd>gg^Q$y=D2QYO$}}JC_5u3Ie`$1=OdOu$C>{tEE$ib=tp8{?W#~GP56B%C>Io>(9MIkjNANsQ|1QQuA0!@=P;8y|$`UHlK@sND1jz^59M5|i z##Rrrb$|%EqEk?(C-0A5s9jn&Rdp-s-RV))b*=NWzn@SC%Y}-g4&NOA{L6p;Z@aGk zuVd2%{Qt2Y6!n5WM}2VkKLXge7cdk3-t9I%wmy9RxlwL}7tP;qiihpd=kvdR_gm}d zsC@GB^;y6DYw-3%>*trBqZd%wD(cM->Xds3IoI)roUBn*=$7;xo#S36d#Jm{-Lm+< z^}Tx!Ox~Leu6!7DKK?xV@cnm<;?Mr;pHIKPICI)BO+?SYj}YubJAliQ?MAeV^nGt; z{U-2lwIaMnV*KFVQRsk+SJGNY^?or;y^$Gcs;kUkQ(a{Ooa!nY==A=|>!IaI0x6g8)ES71!&EpPA};P> zVjM{BKbg=Kz}U9u6xd@oPrP@`<@dyk0Nl6_S`CULQjS8uNj0 z<{pQgATjnIgMUb^RFp!~k@X0+*E0v-01x^f1AIqpk6y>(8m&m%yU}615l3a4ASakp zpwk3gyhJLehC-3{k6s)||BoJLrJ@VgR6s{GRpc}&Tj^{U4@zrBgU(;^rnzNt9MR_W zO})`z+_QcSd$g3TYdR}hUH_%*T9ukrsaS5Iu!bCUDrMJQQ;wz#eX8GaLZ8~58Nx4! zF`+rO;t-SBF{X6+uyI&EaVvJ?&Tshg$T2u%I zEsk}yZLUaU)D6p02l>6&av`bFEHKA$e$DV|z?oZbMX{v2i&boG0d9x!1CR#PE&qmE z;)BgmqkgofgZ6f=gC?wybWiUj>QHOZ5|Uu*S?YzvO(M^HdrL}x>b##$*w%H=#o4~g z=VtGKGjN#GiWaan7SAlW`U%-ejc!6~yO3$0I05_{OP=ioPCXRnM;xkUzi<3 z`|#b_kFVaG+Xb7mSMM(@QDVSV-@STIFYo$2+4%X(#hY$XNh3S>eR}5|>3q6`mxpv{rcTI5)xV&rUU`t8yqBl;@%^OnyO(FL&fb4& zoJ$54pKKi_wm6ZVe#p%71!>~6AZcmh#Ye-jKeasf6V;nC?3;2* z+6K=W$*7Omf6)$2IG~|YU^lFm(z-(Ag+KI+bk9lA>jY&J?-;1fNFjt;8O3cP7o1ji zz**Y6?jEaMl5U7Giqko<`*zRhTcl6l_S*Zr%&E+IQTT`jUvDG=@a~}|CdJgU%@4NEXZ={SSk>A&1l z|LcFne-&>BYVX_qzpS>I)ZAYB3xO5&jo>h)R|~)VRR;PEesAGFpSs>>^;Av%+#C(m z(}cOhfGoLk>0Mm6imAHdYma_rb@?s^zt(WnLX7{YOJ~~hnsP5LH>Z<8F3?S1%MJiZ zb6=4;y>Q|-R*QODc{)!h36m8 z((c#}X(?4>X)k7*oI1ar)#aAs*#t4PbA%s6gibfkchr1Y; zD+r53*|?30N+gf3Y3}P_Ty`2K_G(#+E6>D+jLVhc$>|9gmrv8bITIS0qUcUPydf<@ ziojjR>>LZanq4S3)^`o3P^&JbRbIzXy%Ev!8XtTp>gD1WPQ6^roO;7D4FWJUb7iM%fL%}G!D)&SN%b7 zgS38WIp~q#@LF*2!uKqJS&`5sjxB31U2wDCEI04;nemrrVydsJ42|`5m13?J%DR7* z$)2g;K131(pCn8cU z6qgJEEmR=HSNbKPi~|Fi;YZ*v4hK>c3?v7GuF4NC)jx$B$--P{E(AEap zWX|U@YqS&bXNhrg4*sIB{&?nsv)JC6egHopK!JygZc3P`Bt8JiU1*LBq4e^F;6q0q z@I0EZDl;O`R#}!>h5P|;LxNV7T{|dM4xBZqfzLs5NUcoxa7}Z6H2Fhe5z|2-mHc6y zfP$k^@np4tg0yK#r3p?Ue{kGV-YHa5*&pcdE&BUZ`iI4nayR`$)vlB(uwE2w8+W1W zFC0{b2NnAmW)mWgOSMwXs@PRDvw;3#9?KQD!NR{Q{X-(f>aP=$eY(-x51!S&7?nL1 z!rUzQA2O%SbqYn%>zflEB%N9(N&+j zm+p$mpFr@$S8YDeFf|&&S``c|rOpVnsg5t9pTRJ$TCw}~-O1_MPhg(h(`;JcqoNM& zGTi0nPWB|1Dd|v%Cd{?_7`cId2Q(BMpes=Z;Jh~vfJwz9;GgE8aDgF znN5qEGMmofu~NBgcoX>07|T?ZtEoZ(TgID^9g3@~>~LINWrF1DDkCgcSNBJAb(IaC ztE-FwZx`B%7SJbjx2Q?FiR@C z=|GB3Z}3_(alFa>@W7dGx`5Srrj2cC7naUFyztNcS5nu@fK4ImHwE?L;!#MZkkVJe z9IGpSWd>fk;8!;AwXQY>uywU5gsrO#L2O-R2V-?{=DUGxU1bGj>uL_ne)KzpQN`n% zEVoBnce3_5wlTM4Gq(m|KhBDiXQ-vXx8{dQuWF0+ldk4sW*%uy-I5|Dd`OynFXSdE zR+>pyd>>N#BA74)>tYOWG0P&6h{77lyn}JMo0Lq(y!s-jTOOi`%MC<>Efla<0i%3&rd zLtSN~G}KkBHgG_);aGyG(G^`Jr8%d`JL2FC`4nAg9CYjt_%=m(5{_6gJI~B}%*fG` zI`blABIBQ{G5jkX6sNCmz1#MmT7LtExQPSnZC++MhrKY^Xpz<|o991T^B@M?&qOiLQZrwyn%5^sWMk!A znh_O!BlX-I5Tm58tf0$_i`De|s_4?zw4%PAk}fSRP}T3Nrb|O<%KFNRdWJr|`kYs9 z=0Ihpv1g{TXQs2qO8dkqtifVT#K_DmvZ|Asbs_h9hE^Ciu?_@hfIqS@(I+)Z*o== z*TkAeg0$#V2Q)KwR#`}Kb6-((YNc^fUf+$ra5~nAPA#028q1qf<8uI8C(~ zWa}^fW~uV4?&!AGO~I8m5TAh|0kC5Ke3GeiDk?%mJtOU2OwI$@uOkTmQh{MJ=oS{ zj2$N4A)Kk97yC!fjG>)bib8gCF!B@oGe7N3I+u&drnIR#y!OU95Fhy(cA7%QbhlDd zw1u^8{S(IfiMA#Fa;GcJ9sEC6I~e>9rh)&!FYv?{# zd4lZd>F#0&(j3RM()8_-m;5$?Z~zg#xJ(E|tKJzuQ-I@!W=JYa%X%92A}9e9?+tS9 zBlej@YQXbqN_qSzr1U=_rT+;jT_L3O9ga;-ixSXI&ROCgE<~?TJSf^3DumqJSEvwH z3vPJ>g>ccdJYt2gSg46Ycs&}XhUn3tn8bXsLRhg2jy7?xn%IU}a{i+b{vitCCT@z= z+zEfY!ue>bu4SNbZeD(EnQD1|D`TTMWQI(Jz4C5O3VB{I7bhU9C$&an?Sf#(U@&HA zb1@QiZ+i>en|uXzK|Tx3ktU0##8OZ9vJiXePUlh?A;p=AI%)1Jaypmo@=0-R5w~zU z)^R$!CntraPUi(*dc02OG_&Z#IGxkJ_ONc{TA@-#-|U*@RO|dqwNhy5q*9eT>C94nDL5M5ND@gE4y^y*ayyN*a;LhYowlJHBLk7ay- z^W(Tn47yI0v@!eCr3E=3(HGs?RLN@kqFoVPi5fef6wOF`xNH=w`<28nrdUDf3+rBx zz$#GB%hq>?ZlUa!pU;l=kFWTMIu#<_=MKj1cl;Cey`{b*m9XdCda&y+mL`v-Xw&Q{ z?k7_=FWq#GAR@YVq;ip|x`ey-OnF_!@ex6n?#>AI{|j*Hli=w4C`!PO2*vA2|Lv_d zkkK6apZjn&@OA6!U8V_BG0%xnHwwES?P|P&Rq+w@L zuN&;Pbc8Uf-|bKO0+DN)IFpJO>>KPUJT_+0WZaCrMRqJ}2XD6tOZ5EYP8rD@ zBJ+8;fhx+*hQk+9!uc$0V%_cb0=*c{2 zOOyEMv@ba;sj9Pbb~W8}XTi4HS%|*wS>$mQnZBUb<0`K`q%P{x*Q*ZeqD7Ro%2F3i zU)nVE@P*g8Ds5Wligi&w7DgAPIYbBx0CP?)x-usUX*N<#>uMRuwYthcvYAFUlx=l2 zE8UEd3Ud&dQkPt6YvPU-?>8eT#MS74@D?JwX-N26u1>IH@hn)RNnDslV&Zx{#kDf! zDJwG1>{uqw1i+@-r!z`xBp*2n>HPTvlA3zbzWr8Do0Vd?O-jSsL6PgkWF!qub6=4( zJgGW%V@=&vrXve!SgSgPWzulrm%bQjSaC~E(faNvYian+YJ%&V`*h`DC2X-+FqUYy z=suW)o!TyG685pwELvB>Zd#`(Rl?Q|+|peV*1Regtu(b z%0%{{p?k?6@ilS?j-?}aq)=oG5~$&>R^pRZ3DZAZhiF27g{{q`ISzwP2PST$ctpO0 zS#se?v@`1K(5YE#F+_;+n~vjD^UjfvJ%#g*HdS zp$=oBwW1#!?5Ptv_=EhlPoN|8BUBHH5JQG|+Y5t=qQ2Mk`57s2m|RdxH6~!Gr+djZ z41gpV-Nh&ldt#Ji{xbeVl#}o%iAR;f+d?#+SSGH{7*}v?!_LI}7LV{C{dg!QjG5j5 ziT=c)%@&!-XBj`5=G%0ML^4M-i8M$Zx^)RAe|cQDVP3c9sxP8qW;E(c*>UU@x?}ww zZXX)p_LeMw6f^arXvF*f${)`)yb%1nzZ$9UX%3aEobrL~YOi(S-r$EC$|*Y2XbcTZ zn)`~tPos3wD6C`(;OS8F(^1X<{FF|sr4t4}wereeS^$1@opfEdzO`YA>xTpaC8z8Z z${Mi{@XHbiEZDPmxP8Cy0y(9u3*@-8IJjJH^oGJz=4~fWGrM#%(Qf{W2vfZU5u?)? zwNPtVbwW1H(?&s|U!Tq>s#W_@-){z7wU~pgIROBO6&aJX8-`b!n$v zj#=0Bq{21H9nPGHph9~c#XpI-5e*k$AJ?xp-kzBVXIhY(KspzBmn81JvGyznB+ZY| zQK^Sb4WUReNz@)c7%TVBKfFF~oSvwm*N!s`yiqG)Rh%Y(BnIEo-+W7$L@s<>GUspJ zy`NnAmU^Q%LT8-F4H3c83+4Mkx8L!*+P92a%<=1yNfDuKD&A_ueRuIp-w*qMF$Aw< zHv9{S+DMO|O&;hAA)puUP8!FMtH#(dkxB9}B#<;hZ^?IVeI>=BjW7#Xwpl!0gnfU) zhSimu@#o0=Ao7Pp4iA7U+T&nJweuJ8zwaCz?8JKjo8yuTSKs=viAFbq%eCLkWq0Lt^n|&>MYbnVsWi5dNtp z_#Th&HEk+Akc0U+10B9?Qd-PNZaH9pesCG|THG$gL1gl1XtPfu)CC1fa>_JUn>F77 zneVXu38@M^OJQ4bpd#fDn-&dSP%FLe#pR*plwi}xc{Mt)oZA2B$196qSm)HcPOrXzb|1QlM3auT$7H)DMnn` z+vAWz{)V1AABgCiXLMae1pfS%`T3{jmEXMn6ae_$(9rdiK|t$Ox4KJU+GA;6mMDYrKEudjP_b4^ad# z$*~&@?xfN;r-@VRxI!foNk8Va@0VLO>sZPg7Yot0{`w{~hC)q&}>w0qb zC2yEWFC+ej>&znzhIPsje5e@SAb=m3+BdU2!jSdqzdSj5&tpCLsqyOU^$E=??h@u2 zph+beaB}jRCD9lwCpGHPFQ6s8cygP=@jL1dSzOWNy- z`9MOU;Kz?%oE-fibKe6S0noj#b7OYQ;!MJjc7$m`)|+PP{fUVUo`f#ReUbbI2^)G) zpU?k&MPd?XVQj9La8Nhtl%fY&$=A@YcPFnKui{VXK(c}V+bBR5rD-sLdEfMan|OD6 zRCQg_5QZE}1$u>8^8?!iQia+bn9JLURE7!`JL{zrlDGvdNBibe-yeKRc;cUu{EOx% zfMuV;O~-p-@`91X9LnHvM1Tm0KpzHD9Pl#_8%@5CjIx%w;ikT)f2u{DPLG}XbYAIx z(=%NGDGbNVG;$)YFEM!1k6piQndAq3szojWvf_^3-Q$k-*n1)JiFbS+(jz$$-jF3P z&qur2+s*ZPB}Uq1ftOvkmgaU9u(HpXt|0!DFv~1 z5I46(qg~vqbtBSd2e*4U?8krj#i>;-$F5k2MJtroDWGMSYey@ovpg4SelAkgS?+%? z99;M$uXput7I(c^oJ%X3A2>!}y}7&$n!bAVzM)8?Cp|t^99*}MY~t+rzm%OX+pb&t zK#KEA`uE)au~3?w0M3+{ymq%npVSpgJOd>Y>XsT}hA)^XvmFy=wqvyIL)tLvG-$VF z?>wLEnES=feITg81L%kSLLwsQnbC6>^`8^kb^nu*Z9hPFMrfN=SeSvdM5F(06w;B- zv$VtgGNaAo)3#%~PN`J03e{?>up!-`?I@@k(l;;&sQr(?mR{uCaMwUZfPYg(xGDQe12mOp266eOgnXv_O6&S@K2O=@@9UvkiMxn@x(*Nk~~O9f{&)9JjdNtvXnp+PUsyTQ$6vHYvmBDG=E zPY!~G$S+R0WVyu(WRB%J&VQ>bzY{*~5{UfO?^($S)&pENCpJIzVrO!cE6|6!* z{gkIu~eZzw@W0Ir)6t>0iNS zp{0J9O?nddADQ{ENWrkwhQ}oNxC=duzs~e>l%K&X3N{)=gF| zKbDddix%U4QZ(~|6h*rtF-_2nv|Kc1tmzGxGrLhm!?Dg(Wg<-E4}p%RpG$ad6FXPV zqihEGIY=WwA}ESz?D?C7iuFca0?aIYqdXjdqu#)e!p;qB0Z}ya#k(obLutD3VK{|8 z2f^w``g&r!t|awy`-moH%a$MMICw9XPk5LOH%!3fM{J%kKFx?!-HQ$zVRuR8zT&uY z8V(aW4*cF8`pULF`V1&8;FiqAW~tm$!>I{Yt0Pl zLXXJn%ANpW<+$4)hTSNq?ex3`3N{=M8%py_=BC#{n2_jDgl)Q_ghVoCLv2y-=+M$n z_Y(7$*IkRm(7NmKJtBXQw*9C%2rk4o%+U>oVKp|C?wK4lPuNVra)mTAZMByu0Oo)4 z#P&s^Foes64hBtpPK-XcA&xp^BxiDK_rPx>Y&cvxFm&_!7%w*+bn2LR=CuC@)Uk0a_)A;Mibv9Nz`k&Wq1~o3v70_8j=uzga?quUl^VsQ2&^H z7l@^2YJ?mKYE0FmK5t!i739Q=HX=bn?-NA8IUdJx5p+HG>E5QLq!HwFmW|Ft(7Tfk z|8??6!TO4H1nMvQwQv=L>IsI^rG;rdfmQQKG-|_~`($!xap{?N=~RV-mb}^aWx-;5 zoSt3YK(C?jN5j$OW%3AtL;%LmPH-(}j*S4gEnBn|v1YtG{u-}>_~YPn^W`Nv)41X3 z6@2x^>-eIR^kWPKU&X7&+jth}QD9l5)o@pF$C}l-^)E=+A_?3dT)~3YBI(u%`?xxV zNC_gPGfsL{;5^bdv$%cYSX@IUE6hkjQ$(U1n%RJcN)ms>S9pt0Cu?bvu!-(UlM^2s zQlr8l4k;WE==3hDeKabMwqni@H<_)i-R=Wjf4;$vYtor>4c8oq7N)_O+K5r zH$;@M1C*cB_cuSp>=zg_ybKd(2tx(7!otNq-?-3bTzM`1B=29rCHygwjff|k?Vy!3 zrpZ4vLAbP!NAO)m>KB=zBac92r}N)SQi`W_?qU_B*O}?u(%jdSQe3JX6>Dp&*9+%k zqm*L1a(qzNZSYDJ;JZGkqXG`m46eGTSiDh&)!5A83!FG$)V#)fhQE-a| zXO-~lJ1m+HnU6ddL4HSH9xi{m{8)-CY?QxT-n7&LM@jQ0#um2SoNQx+Iv2#?{`QsZ zVlLskg4yGxpY1Z0B$+}>hM3vFPtnI=e=8VX?5zhgxs zoQgx2kZzjj?Jb@yOV1Ay-y<4eQdc4#qo;oK4{c(g?5>( z$*PJ)C)23q)CEry&yR&6f6b`na@MT)POVbb;+l_89JMUMx_Dq$aOKSH( z6O?XZZsZidjV+GMB#m?f6%z!B^ zY;411O=v?iCTm5TSu(ka`x-MRC}VEM#3k^=B&7xtmx)a?B=};9Cna)5=@r9ducsD|L|)X%%ctl|c?Mnz*z1@*4VF6j%())HJkuecLVayO zP)N#ciJy-tg+zUSi0m^U?64vOUix0am_B+V@&sh{i72GOE0_u%sH zbRx~g?sdJ^RWSQ=QiVtdD!Wz@@e1}Ds!*X~yQ}d)EK*Pw&dStixOBo5++4ep-~Iwx zeVTbIX4PLf=mlNYF*(QfQMu|A>_xq@xmV>y5itDlozIH8wDY|zsCr?kAIBc_p@mEQ zQ~syCLEGkm#jAz6F}l&Jt!Jf}t)n(>q|8m3WZ#m?X}x^?lT}2dux`h7%O!`^s&XM^ z#x_(6mqom?p7{Rcr}z7BUpCHOFACCGtbaWk!uYxEaz8z6Q?mFuJ*-f*lV;P2?LSrh zdI*rcc5ng9{fy!Zq*6;3AqK+kkU^9Ig%B93rhm>xL!^K*b58fZrxvRzFEv5+SR0sMP?o zNv9L+5V+GHa^)+o;dBx7_7j`fGTUGm=JK16#M}@y783cXqAyvjQT*4RxPj=3eoOyX zi(1O(O=z0g%6Rf9?2m;wdiVcl@6Eg0wvzPWzpZoh9d6V!Ey*Dv0^I4gY-+KsL|a>O zoQ!`tJ`f3th)9AVNXg2)GoSsfr|Mn+q)3S(p|s;^cgL1U;ORBo#7g82u|K0+DkhS)t$jdq@(rNbNn~3Y7AcfhrPPux6~(<{X?z&2Ys@p zetp5%CpeeAMmtbzM*tdWM1`9me?ctk8b9`hL`ECZAG*xN>9Ss`wQPGzksQERQ~!~R zi;GLjnUY3btWQp-*u8@Cn(Zr;yY=5-jsu0 z(f5O!FPB+2%dbA>{nZuI9J*v%VwlxrI0zP{OF_8bNd9H@;Ku7T0&(2}6ni&5G9u(-_3@wviSP>~dOkGMS9o&|;FCM_Xr ztX~^Ca5~|;20r%%@Jyvk(#>#$l2R(^rpRBZowlNA) zU#3@{Y;n`#5QQ z*%Tgkq;aRf7tM1pwqfioj;R7vdH%Ve75V(xyvsL9@g|gNOMwv&k4D&5 zso)W|RT_wdZ8Z&(a1L7{TQR$S$Q1A}t#ViJjTozY!joXK8VOB8Cefux3IQw6t%@1$ zHIF?6;Q_k>T(RtM5bp+Gl+x49h*wXfN5;koWlsw!Em=aYF=?z0h>y~Tot~UVQ-YD~ z1a4C(Qbfs^e4ZOXyTDWqH$l{NhQ}vpEF^Dn5Hh_DA_gQIaj11BXg2H&Q4Mdv1aw!> z-vIrGP10{LKHfm>-HS-68#4Ki(Hq%4fPOO=RHl;vUqGP0r+^&~g2zN&*$&j4rsTQ- zR5Tt;o~Rw2wb`f{)7lbGn&zB|a#clI7}NYSi;O9Mub#v~C+5&nr)EVmdkNH6FEKX# z(Ew)l4BV1UjX6?U2O0#I0~diY&@Tm5s>3g20dXZvaRDb>uZa(lecB^!q7{p?D!XSr zn>Y8U^~3g7tVXiw2PP+h5(v!~g>`Qe$*E*=H?sKljA%7Zj6fXgv!+q59i0?FHm z1-I>dDc5K<{v8AgQSocZFSL9R=(0J6CWYR-U1gifI}hd%%bMb$$8DIP!-8VVaVP7I z4!p=({#aKVZj`&Ui|lEeF;Jj)?GJ(;tjhuqa21G-B!r?#3J=3>8UZH_AL?XALgf<) ztYUy>S&Z#x*pkAk<$?6hd;yqnv`5tM)E>X(3q#lX#prTfBCCR-jtxXlNnoIf3PvH_ zx73jfzO#{hprsKwI=X~}jtr-8g~z!y8t_eS=^;CdgWFuD&Z5cBb^s9=8zmqI4A+kq z$62%`SzzOaaV`e}BEl0AT8Qxy<~w2Sq7%CVCP07^LIWNW7y!n?HROym2V5=&DUqNP zErz72^YJC99MRcVI-ZnTMi+3X)4KzG6??_Q1QngZRKV~i?FS@<^Hkk{FtU&F;^~wG zRANewC*NeMx^{U6=%rpW_`@^hW*B&2R+T_B&<9K#BznC@mFXM$Ebl1J%#JZUAOH$ifBW}V0twU-?z%m=2qm!y<+y)Ri-DtUVJ%dIvG}7mX zXIp3IBp}fbOdxp?0(;RFucdMaI##yt~=3(E3KS`)Wa(oa}FVb5>11eucb zuIy-XI4mY?izuGgbbuGQaH0w6H)o>t#v`hE?ZVE_dDar^0dPeSs?wCpNQ{PUYxqU; zc=iG<;g5~j3+g&AU+mw)KQP6{CQ=_ajz{Ve= zM|&YEgbw8v$~jR}V%kTybBUnjc2^LOv0;uR6jwG2GMVuJnP6P?T)a?J&vROAL+H}J z_$T5Of#JLH+$&aPI$%1N6x6rtGOl1a$I94n`O64f1#{^DYGWAXn&Y+THUz3dn{hK^ z2CsXihm9RoJ&$pRSkBZ{tm09R4>W#c`dIQN#9+w{GSojyd#HUGlU^?IIS#muM6ZC6 z>v~`ykh@2WF40=lJfX6YPI{Upk#SzPHKdz~`DqbF=rZE2YXbAfHx)ZFnR1oJhYI&Y zvl}86P?FYBqt}svdp+Ur+2Al}J*aX$$U$c=VP-Y;hQir#sO>*Jk z_QLrs%~q&0u5vTlO)yQ+`ofCUrUK>^z746$jABP8X#*}FYz$VSvNwb!Zyilt2L+=* zcf+Xt*vB$K>&*Z@Pm70c>{%SGoT`=|7!T#2HPD;vG=~{z1J%@CCko%JsW(EN7&B7f z_(YF*(KOwmTT{vQB*cdVPSYQRV0+-*<8tOfsY|IkdwOL8*tln6SUHP(Bx5y$7qR;m zTn26jHz$M{5(H`Fd9L)1wqUk^92RPSvGCxIuNo?J3lq4-i(~6QdlBk%gHExmh!L`I z*oZ^F>7AT-?S)D{r};$LzD1LniP9uWmPD#Cg0^nHn4E{iA`i#77tA6Sc3cfQpg#Ga zK)v?l))iniU!yvcLv%~txZ3*0nUUkAuwe)zG4#{{y1{8Q8C1t=C?EPb$g%2fkRO>n zyjlVxd069kb^hV+l=|qlgMj{h-f`*VdXCAxljmwolG}k z8zbKl;458QSJ;dp-OL&n&O&%Rbr~@;LH->`0#l)pm=P>}(4#pC)LVbh31iS=U+2(v z%EdU<`nW}^T+0;~rnR_cM_tI)IGmP9^Nf%%>2Gx-q~|@ttxf6 znk$XZb8HtHvsu|Fun-YTieYFX8T{w%{vZX%-6~o%*gxC$3-#fJ;g?l@zEXNY z;#Ky;*$XweuGtn1$y={+6>K595H*)+ujXd=%Vw6mV=c;; z3v!>#hz(1aktDMD+cqUhA5%9cNwMFONl7n;f18!QRkM3SWx>1fkx=N9i1pZpVxGB@%e~UP&o2LE52#pGK6AvCC=9e zYDnX|qeo~vZDDkyuuVA=m|#r~7`cpZ@#5-=3_ATrx8mTIGjv1;L8JuAAwo^q=o>J6 z2oMRh!cEQNvbut^iZ-SJoa@j?NtyX?%i3SUvL@4&qIGRdqWZUm?VGi*$vl5cRyOI8 zSlUdq!MNhDwv_q}AHD9AQqviFjdo}#_~(XSm1|B>r)f{e*d@jNIq<6s7c3uB!B?}t zNzoeSJm;rNvPnKtb??p7{EY*n>j@9%4D z)tyny)$;UzXDYU8(k3_d>;l-Txvg!FkA0XAf>p#ndB-Z0i_J!*a52lYH4gT_*W9>_ z1?}1WMgQLdf_*KC?3V|EEt}H*ZYJA;;e>j?tUa;AR##?ivGJhgSti3&%tBJoGPWU| z(tb$;k(82#l}Jij)>NcRS=wNv%Spj(q)VD&JW5H?-BxL-y4z}bWtU4~iGG^4doJQ? zoY7D?Y0$=)^@Td>MX@yp=95s?HQ06h#;5}#>=;_30wzU8AL$)B#$kgNrj*;{E1*rn z9${y#<{U$y4ac3phZ3y;w$^h?+_5-FqiuIRs$8#L$jo#i-+1^TE<=Ca$IfpIC3`1F zhKv=cEo7`RA$cpin^#xdYwAC2Bhdh{em`X9P+2vk2?Y(GGiZo}#8L8th8G8+wA?P1 z%mx;8G+B|seiItgmGT?NG>wr4cY&KYRXCg`7#gJtl&f7@1)S_H%EB!JlCUghc&&Xe zZPwZg&aDe9Gdj6s=pyUF<%r}jI-8}ZM`QCf#?6#qauTWp8P~f*iVzZvX@q442-;5<|i2{{PtZ2P% zXcp-n&3bT7V9(Nd$CO4?`tSx!?7S>>j_!6K)Lf)$BEznsW@73HeK_}UI#I**wiRoh zNgIo$j8h4=EWNAQTna{th#AfA!ZyMO2eiK)W($4NkzL}GaW8!ir%LV;*+2oUh%Sd8 znN^{cWv0YH1{B$jIuNMKA!_pC_#BVjeDCw_aFsAFrad2zZ{Z?^XbDN zq=QWTh+$K2M?T#jYtT6kF#_*&xAt_8*Wx4zN#5p!HN>__Qv?}RI@XY=xNnHJIae<2 zENJ|gH5?1@HW#ba+{BJj zu3AoMdD3P(CT*TM+p|>l>Q|zwFG|{6O<6(D=kj@@pkx0;q|HSpZ7$UcrF%%5mj;tC zp>isC5@rocBT5MhqcGZ9cT=^oS)U{v?FFD29vST+HH$+kSQOR}mxZHDP{A&OND)@B zr9mk|*{~!og_p>3HijudPiWN1Xb8d#Gaf)zKo31WKRg1-E+$k&cpRymNRVL~N^?b! zC`xlIAP*m)jT>?A#?0SZt@QNl$MwV{*-N5i#4xcmP{!m;SsE+DRD!9%Wl&NIh#B3W zr9m^ijF!U9@EFgI$~w?A$5U`NG;xNpJz@wqOH7oQz^pB;IAVPXV@J#l9M_F2lc$sE zf(4`!GD$s+JeDh#*y>9Erpd?eY5j<9iRP$d>_VEu9E;>=564MY*f2yn7UY$8$+lh+ z%sMK2w8s-!tKspir`F-W8mh#nwHi&_=eGA{V-2%H3g_CVeBsys*IQfXw6wAi@Or#8 zG-YDMU#}Vd%`vO#_@kj(UEz<}R76K`!h+DiQFhM0eIXe%j&3^CXggmFf-CBmq0gbs z!jxBIheE<;rSU$Y93~pzq;V6W^1A&Y^FcGaf7A)QvFLy?_@NdJTd@=k+p>5J-h&eX zPj5BXxo)_IyJ&mWP`l01v)U$Kt$e02Vt{pL9*yYRfz-ABdfifQfbBJm^(jC; z_)bm(^`Uu(mIi*HYyYVM6reGZd0W$%pyXkqMC)ala5`2CJwIIpC#$(P$Q*6A)mAIZp*H=ilzBbBM%#G zmV=HwT>k$l{75*)QlqOnMKkt zjND@yiNXa_F^_Dk^b921YIzfh^ez=Mi4>KNsYI`iG>j$NDjjplwwhuvS$Ozn8%<8G zTZnyxunR#U~NHjiVC!s~wlM54+%RF^qEcCFW4(PH`hp#=F>>Ujy zx}X7XL?w;!a5G+qu1QaP<+qae(BQAeGbU0!KnV&aP2w62eSPfXY9L2=BK;JdpMqB4|?cVYS%Du$m9&&;10K5OjM=~vj@M|Ss$l-5CZg|rAqxJ=3;B%q#dDzd_!ZTO>3v_sH1i*wSyAYrY?o)d`-oeujvp5E9IBQ z*qV?s&2kcaP1IDUv>;#8Q=2LmHy_SpoP4Y?^G|1kD%)R)!EbW)Jx1Dbs?ahE=dCp9r@+z9MGb7|vI6 zV=RbUE;~!#71LcT4#cdC8e8yVVZfT24wf#pb9YQ@euqQl-z`}&3!cuhulrdNu4cv+0G-lK^LYg7LEm|r~jyS{kp+0>i z-U*&+1_oIxX1zMIA!00EG0r~7 zR4rTape#TX3#~+6VGSz!4LNN&rMT*Pi0SjguC`P#a~J$oRCIKnv*{Y1lP+}&sBE_S z!_dQlRKBs|vaKL0F-#(4uKXMSb>Lp#)iLv6#wb?Oy1h+t_uSsewk&vmU%5T6=~|{k z8xy~Ova3bQE&?=y>{W#YuIzF>|7O83qFG}w)`Pg|G#ZxM3_F$|HtCb`4%p?gmCtkg zH{?J#KRmm(rLhgak7?iAKg;g!?B=s)@9Jv9E>*U`mH?~rCck$am^ee23&mjV8Sk^# z)MGO_q%)P)Ql+v}v5VzmZn_YGzC*3HIhic?{n>`syY@Ok-^;cpSK~}n3yLZMaKlAC zLK2xD7I8;ux^r`?jd~wmA$P%Y-G<(*vC$-OCLZF{ertriM7Af58n{7$Lhl$=Ceb102Qj?m)* zpy-BU$zV(u5b|G%fRG&5$B78}B}IfgnO93W4sCkps~1^L)&9w{tY1aHOyxe^R;k>m z+iKA}mD8%GcCBu!lrGk7mDbhDY12=c1qK8f2A(ZJ!U3FTL#j3*zG2>s_sJv{Ws^cS z4(1x513U~JEWoU|Az-XGX+B8^Wb40BoQkJ57=;|T%=Y^Waj2rU)s+c9w0DUbX?5z1 zv%H=*xc}x(SJ`b9x?uXYqVc}qS=-3J?~<4 zskY1t2j&9^f=eUHOQW$mxY=(2W4TrR04{xMF7j1 zX}ZNJU1=A0Z^Pd^+`*VZ*|@ZUu%{u#Weh|Q-9T9cQK*eD7z}PVB>}RKF~s`%^^SY?cn~$@?^`K7i-;OxQi@u}VWj_IIST{f!d_A9`++6GFey zVR>i|*jqbkVnm!+vdRt0BMR z650YzpB+cyyV3!EI_UxL)E{sl7?OzuNeQlmKAlB;`k9j$+#)hbfT8I{y1K&8;Y>>C z19$ySM}gJ}N-Ag9>|)JcP$xJ7NlvbmfG3;cz8Sw$Zm;gl2ae~#P;(5%pXzt2l6)9%1RrIuST?fbMlP>Q?f z4jgaI!U^9;o)vd#qZxK`)jXZJ^q*6;Zki3-g0U&=d+rtFRDGkAtZLq>UoV%k+{LKFA9o&>8E`SmYb;MBbF+HE`kGTNU*E9+U7w(Bp(UYy8TZVsa&m*5s(i z$t}Rfxv5Skqk}FO?A(S(z#I(BA7L25*t8N#JiTsH9mvs}*Z-xz#LIambi-ZAUGk(`Zw637Xh)|2BO;i~$imuV7PHu4h< zb)1E%$uL0kVFQz+$7i&gP^yFu`5JLEbVps&!ZkZaVpO!@7oiHGk41r9!ncB5$1mda zY7FRHA3F>pVq*!FrE2)Rqk=j9z}z+ZvC!-ZHVF?J1;cAgeG->7jp+U<$2zXA;Cni?ZTd(O2>Fi`nzNwExHxF3Nq*LX}?{*PdfdlFNn_b5#lr~wTJcy&Q zkPef>f=5xtD?T`Aj(~}w>0tbS3o!`BXOUs%8hs&=wCvG22G(tL<=xWE!{Bi#iO!<_ z!>muD`6(?>=%6Qb6a930%=`9S?%s=th_8ozz;_TEZTp@;acGC#>L5E>9Js-==vfwP z0Ib}e4oRlxGcr@4Fh^6m-lcnSi-}|oA@eKWb#ni>14zYI(t#!XcdW2u(!wEFf;;Rf zaC>lkqe--!#;b?YKSU&sJLFtWnuJKdlH7%3I~R%Pejg#j(6Aje<)8rdF5kdypN#3GZxNPthYriQoTh~XGNUcQO8(dFw2 zP(iYwsf$kF4x>frb4RGUjfi$J$gM*e${? zBpC2HOpDPNO%uZtQylVIZ!+gRjU_K{7L@s%qWR8>4_lK5;Bi?} zyjzUXYK=n{HBtPU@KSb#_UjBRo)OtM4hxuvhIn2Ws@QK8C2W-FGA_qZ!gHk}f@p7< z&i)>7l>Q(v)Bv0$f^mD0({3cV%Vn`Dt6yOTNxDu)^vE6^+qAuYu+)w>pejATC*ty* z7Zg8#O4l>5cwYEj4k#D0{x%(LwC1v`NYEk)hgt%Z^Gc$`^!dvrvMZFf3SF0f=rykg zg2l+Bu)&<3@*S==_V=Oi_ouWc`Nm@z3Mgo=%3|LRhL;1cAXF@Lt$dm{?% zrU%J9qiaDdV;3&saO6ddms)rd(V?lO4h#UtW+9e#D8d{h<4p+}71lP@qJ5c|sIj}A z51lC2#5=ds0;!i8-N?jqP$cSVj`IcE#zxpulRfv z(Vee*nL{cPGNEL1<}?7h_fd{zA$ViQE=dSM=h`z`A{7!vyk89Ty6xf=3Ioh03mq2x zx{~CX&96E;rs0O;LDb$($ieI@#^LH>HPO&^`$Ha7NKmrK!Ro+#b-Wi5tO_X$i7NSc zmM}ZcN6{P7d0FpbqxFT9gS){~*$(`{D>`$(dyL9H)7T72ez)AP5x=|_hw|5i z(v>$HiJtyYkT7g%itd(Ye6==3e@ zE)Gy*Z;wB0u~SO4)xFBz&Ohtlzdicrm+y<=uG1aeemOaMU+?vPtp89gmv-9i;%_DQ zC#P`udujNl*XSPn7#yBnU+%Vk`DIjGT{*kRy(=C0AOG1o`R7i_FPC@!IDO|8UXn)q z`_18zZsz{+-qc3U9$fkMUK+KeQQWEJa>jac8WA=Z=3>+@N^nAsm`g+L!&nS>;g1u^O*9Vj84t2=HTGT zH5}K=6&^vSHiu>V3DfaCd#t12*}RSZzeUtm`byjZ%M-PgQg#a1wq2dZrZ(nfnPcyc zxzf|JsBWutY^vKTJ*(=rO3ALetUPE0JqFI#9vSazJzUhN&crQ;o} zP?5<-kBT%6V8!}C>@4g8VM|a;?Y^%c;jLFp1vg>)8HzUh`^X>Q=#2@~A}Z;zA=N3O zB%_z~j&)}eJxLd4tb&5YuT4^{$Br7-8{(!X^NYbo7@EB2lC7+zQ^Mo8gRvvf7Jps` zZNZXGpd-Y25hb^ZX^4cVP&^0hkLe=p^~&p|oBQdQX@Gnr59{&Z58%!)cF+s0UETO% zOzuVL%*AMjaYC*?S_V4zg0^yKl;aK1BG5Z^q9|G)q7%Bp_&lX>HfOM|B2OkbzPU2b zK&!ix4V+Hw!3=AoMjPhwPclNg?f`s%zs`j;accQ8*jrN*s-?JZ5owaE7Wd{0q@E^D zPaT&@q)CY`i5WX{s}0S?>isQ z8u|Y^4Yt~U7WtH()Z2Kw?C`3y)Hl5jG2 zAS!XjRvzqTB>f&}uSYb>v{>{o3p4=O81@A6Y_j)o;2{V}Lc7F(p0}!)Ab40Sd7B*_ zfONo`9Tm+@NQ|AYeWyG`+Dl+uMeCvQtn1{U5s&cqYOS_Le+yfv`7QE_8zZjHc>_>G z(Cv?g5)?Iv90ulywLs+@(G}82)a^}m9>}f`_C4xUO~gZO`YbHAtML78pFv4)whC_BV zM5-T`p?$h^gks?{lz|CY1t8LceZ6L)*OYb=;{tfxu4yswfVh9XQXr5@+#4@ z%wiLVpmI6GJ4vAhMG^36C6QZ_hMFBj;(f(+7XfQ}0Xy|s6K9@dfy~eg=m8KZsNPb} zCLdn+zk=2w1q~WZGmaNp0lVhV(D!d%(pH3{zZo4Q`KZ`+o;NzCs4tScX3Xx;VqelOy-4FHM)(7|Ce0Alzd0M?X`K5I37hA)V zi*9|tcwPFr0smNd;vLTAADcF~WB#%Gl=wVUvqEZ~F;m>PsKoEQsgx=UhKtC!*bgZ2 z#azjrM~Sy<`9h7x{ZCcmCn}wy#NYVdpvfuFxd=_ioSGv4Cn%}^Y!$XXw0=Jtd>>YR zZrmQe&261OLP;%E3pvZNtED`R^%Lr-4jWVox%+k0$~T~+R#Mhc?Lsl3qfVRd6m?W4 znPI3*=f=GaV?yQ&x5olt_ZAm_mXXWMUJo>>u>|8{XR$C+$rudX63lg%BQAEc05dl; zCL4y((2FBR?9Fs|fUDeN?tZf?Im6;}D)PDN@($G*5ClHubs*T@j3T5P@=O3v2+1GN zq5*jJMN&a}lRnVfEL&x1oQ6P2WdKMMe^QKlB0csx@DT){G1eqp_ehA9_L34ZhOlEe zwI<;Y<5ok(GrThBkv?%^(9t#1$a|s+UrRe<(36H+8&Y;1$w37f#F8F$9%k+$OP;Jl zu%g-uf}!NDV5wKw_-UyR$HF{BWerKy88En;g~kJ&H^-!mq3Bi$MM-$&z(GO8;@nw8UwUDw%5_AhNj&$izV@%hF+HhjJNy~g)7q>2JiRmK@ z`>i%Jnd;7;P$K|V|-f!(ceE-Tm zg7CY0(8$dt{7xI(M}or&wPLkaETts^o8tZ)aM%TDptsY}VY9zV!o!^60`RcC{rukE zf}*gD&%Ywe=IjA^7$0{0;b#cG<@Y~jA#puR5EqU2d3pi^bh(fscC5JL6r35@F{v(J zm@EqSO8P?gpZy-Xhydh?Zi4TNi*O!gcnmcR^oVH4q2DSLY8n*B}Bs446F5l%)aql4TA! zs7uc*)+p_krPMo#8@ljV+=K(>Hp_qO@F(3@rYZ3nep77yI#7cHM|u%EUe~2L)(#Uu z`9t}VF4-RPG0_^ZT zKtrk%a~YaBBp=dg*(C-1QT^aBY5i5#?!af#IQUF->PE(^aT3gNtMRoDCyF@v*bc9a zug0y$LzEF?^Jg~c4@jV=a!w_YQdg|J*@R^}lgQbwheY$)nq;Rs)BwR02MaSII7XOk7Oj9QA4njgzRU7^UG>uzhwjwBnuCYNj6=S@9NCUA;c-VdOk=(rCTdA8Nz3B+cTFH-`2_>1F{ z$}=72G+LPmgH(GnoUD|@W-2!hEZ9KxhtusPnej0!mxFQ9gGt&h$7dNf3` z#*l#R%Mf|L*liZ-oKO>^tfPNuSfeS|#YpN!GrP26(Wgd-fo`S+dfiJqFFj5X7zKg5 zCVwvX+8=5{QHiV>+(ye+k1lVpGtOctr}BZ;dHE>qvvx>7(ehOnKIzZm0pX4?hpZ>L z2(c>oFmfZIBf@X8=%I|=f@vlYa;zP*%$z8L#36}+#6r1rtzC+{*|-`d=uKZ2hPP7C zvN~oK5n^nm35OF2?zDBJ!7PZ9DA)qo`6c`<>_=ic&!KE28uvhOwIm?Y@0*G2d|n5a z5vD2Bgj7+&VY+q(LmdO}b&=e&hwNjqU-$Y>v_nYCzn&xgEfL=0$*F3!r`dZQX4s|TD6K(n&OUUbSiR>88X zB;Knt>O`HIoF~~DIV18FwZC@`(bzF%9KwD}MIP3V-@f#EpFf;s)Zy;u-Sb~g_vLpq zTuXp#$mvVaW6Xsiea9t4UGaSd8r4S0nw9LwvGSIkWxnhED}Z>zCAruyLd8)$Xv6KI5-{-pTq_BM#D}xmFgyEl`JWbTL^Js zdSUcQR32T+xy$K2&ROM>bXc8sXxZ~^KXH~GaSC4&Gok{AZrHQ4Q}d(K@oqU;9*iT2 zxvrlb(|}9zX3n3vq1(AB<(XW|vl>{nN|#xiDmxRi=2_6VBE|90y#@Wm+1cSH9YH;e zfGY+5K`pdr^p~cjhDj-8D@sTpBm)2LD6&M#n3x36g*Nr+u3ah|{=8Mp=SiQloueJ~ zzJ8X#I_WKq~yn`6E!tA{{BYiOR zTF7U7#-zMJdQ~nJ)^$z`ty8s=j7Eh$dqzXH-}UL6Gde$Ln@`Ovm9Qt-4ypRJTs0+o zQdHbGlvk<*M$^UM(6fePfxJ>qwVcmKd8KC6G(mm%wda+Z&2Iaz&MWooVft0|P#>N$ zH&vyWD-`l*SlKE#sph7dxvFN^cPhEB#J;m&ZmP=EOZomkcXLzmj*RfVC1$Au3-x`u zsZykhO_3}%#q157-)bJpSjR$UY3mC*9+NyllZ10j>p=;8CXmkZBnQF?r3GoAfNq+c zFymhV>lML4R;FVo#H*W z;nbRD+=dSUfgZXQ=eq6YgWQbWdh@;Vw3m!yg9X1-Emtf%-3*8`XKU6i^R?v6UG$b& zK3ndLTLx&ojG92JCGMHj;2qm4Er7?ingQ{U8^a(sfWo3Gg(U2$zQ8veHzfiLpb8 zQkIyBy=eUdLrxVldDX`Rwn=V#o#TlgG1uLE7goLkLr`iLmK665U0B7Pl2csx!kRi9 z3tU*0+-_;_-U|ygmFK#!W_hINRN}_{3pp^_RCvy|OFiQq{`mpYZxi%>AtE?=PMJ zxUy^l;5#hjIfS(uVAZrKUP?BadD{XIbWhCYQ#wLXemef8Z8fKdiTUUTjjfJ(XE6gIfe(u@@4g$8AJR8HJA#i?5_Nb2)z0eSRMSL5LZ2 zJh`H;(>a3Tm2UXDPUozBpyTFx_>5Lp*qt6b{9DZ9s1cZv(9_Z_zF|^9@d%=kaOi?J z9*ZaPq|UcI_xv#4=`vt|61?DmMg~-xp`W^>zhoO4cWEAru{#H6C)rA+Sk2nSM+3sQ zp0BQ`XwtLl%^rJu8Rab$9lJsQR)EGEjZN`Gjom)xxl?bzi8BM{-^B3I&3QRs&n0e5 zk8S8^GrW<*K%^ih%~in#V6!ITka-;Yw^7(+7_?$W8){t8@EQ4SLNv8!+lMb^!vu_~ zwKM(N+Ga+e9kge(Mc;fC;ctNm82IdH&gj6%s5R!94xRQx;~v%E>rh8sX1K*5ac=r8 z{OfTq`=blm=9AHVL;%4=M@^7*j)x6xEBFK*v&e?fz|K~wmOvd z$js}nLeSosa8?s&%Kj8L(Z>Or15eA)r>S92w)R&hyJAI6dAG05PTrhEc>C^Ly*oMF+ounA_Nk5aox^>p z@AwzJR}bq4N2Dg!kLqvt`PCCD>ODWJ+xhX`K7T}&*6IJd=LaXp7^mHnrrD1u#g<1bw(`&)^_W9@2w}sCRe`KPT*XXvg+H1*A^f87H6CeL0ybZTP z_KjNY?a41jy=fo%0e_0mTO<*+)b7#Q+s|jG^R^B|3iN6 z4f*+d75aa8L2Cp5d`31{WpAp#tN-1Y^7-bSQn{`~sgqTGpRC-J$gX77=!Tc==3sM5 zsfYW=)61Mu0#(D$qa-NyEfy7|<#cO&hP=4p_YD0nqpP5GH$S(U84iITDh{xD4o>Wh z(r@A)D)PT$4k%r*)Qf*!-1*Ljw28m(ex=*1o;T#pXSiixDgTrhOWj3k;pXPAFVK-^ zThAfwiNt`|Y36#T15HODH+4u(2=ow36s@MY9TisSGygOF<_(5ixY|}Z_4mJX@6yEdy6+7){!Wb~jXa|lh}szI{H8o?dPx1X=)cdLA>w6zkJNk~ z&rN0-e{uIgnl640g^!g)#ar~>XL^Vq_`3|{v&84-U0d4gqir&o>N|X%r4{_&a6r?* z>EsGZYZ81L=$}!HhH>%uzd3k+cK&Yf{l=z@T2!TZ7iH6Iy_oV{vO6?;^pqaVXP9gL zYP=4N?&b^gKz18{f0nR&JbZR?`1$N)_s9M7$!CTnY{?_LN1yq$J-b^>FRl@LDY>Tj z;pZE?9cX&^nZ5dXeFLx3R^ndBXzCs$Mv-x$9oBka3NyKdna_c-P7#EV$=C2UmVZ1b zxLZqw3x60h1Wp}m(}nR~q3!>}jT*2Sh#N}ZZJ2$n*DN~uKNDvF5FKM@z>Ezqhr{z$ zQ+EEdXMisAJa+uy@Q|BA zt4PU#gvSsan*4P3CVUaEaOuYX+^4)r&=y-}F9vS2^%}<9OHw_oVc)+Kv<0H4JOb)d zveKI3?y=HNZ`BeBpT|14(Hi!nUq0E@ie=}@RuM#Rr3Fm1<-KZQ!A#G3EM^i8a{GbK zLI_se&Vuvnrqb!0lRZmPPjYLm6R^T~>gc?#uJcyW$|L;}XDWA`ygJzXo62QNImfAf zuyeUX`tL0FQ@*&FENHy57_$5Nn0%AFn+!+e-sVdtx%1569TZ}00Ba@(@R~6Z@6h5< zZ~V(kStqaK)e+lrlV9BH=bU1suJbX<>`1VBT?EbB2+If~hS)%7bS}zVoyfJDIW`K;CVyc^nqzmEhqu94?`&?H~#G%zlH%^QjUX7!QN3sN>s zXMp1NBKM=X)3hg!Z3D-2ogf_g(i;jCzNh4h*ABU9%aiK~7}m)=OK34-rlY9vkZ&o( z8-NfDOUjDUJu=GlhEt&7oPaIL0t1^T5e5gatis2NF1Fb^8TwQ50j9=_I zrp+kx!t3Zz^aJH~0VwVX)tdBBfcFW+aAtJ{r&tsop2nY-Gs;?B85>6&Sacod;2EqH z3S$H6;Lkk+N>!AkxD9OF&17lFZ(KQbdw!!PRCd!=XH6i-`9g^o=LRg}pOKo!`-~^4 z!0|a?z8*Y85z9H;EKDW0pSZ>B}EP_3#?o{{h!5&CW`MIzM4~cg3 zVGzcJmboG056f!P5C!)<{-of!a-rflj2_5Yh3b=`2gYV^mb6;q%nKL?ItDlzUMby^f>pFzdruW4Q0l4#S zISU^=w&Hwmd#-^Latt)*v)sTr1;=aTbj&Er5?WHu9SnTyP@IDQyQN#XWl4ow7Dn9S zN}nq*bN%lYij~i|raWZD2s?J^b4uciYtHws{rAl9B@RpN=jW_^2TZWVjAcQjLkP-T zciQB&@H%}182~*OgCcI%2NEYe>JK#z4uMxLO}61U#Fo&?glYDo(y!)K!sR&mW>ZAsU(xzfY zeG?qV&+VM-{UT(K`04EY{fAx9_^FmRMDi(cfr$0dFraYmZ^0}z25u;C+8=N>1vDc`ZD1~Yzjy6+=pI8~nS3Y`z&y#$%p|9(2Vaec zQzs&m8H(wYH;qY#tzQNutAsNSyo-_F8EOibj9N2PS{I|sH6_{p+)IeH)Sy4SL15A9 zy#=W}$RmX17VNOvH-se4NClq|pyqHBI!?`I=Dn1JDO23H;wG$=?Y;V9f%tkX@_#8e z;fp&iGF!Tldz}Bz4+9MXUeW`2pGdc$tT^2jDE*wn75FGoZ!S6S(bDEUlIxQ=DsAu? zpwn-9d!bMLdQ%S2UB2!DM<;gNMmAtM1Y(SI0A?TTJa71!D`lF$7r?;rU zxS?2|ni9RG?=|zFk!CrrTAci}r-nBsNdx$w)X})cG2T_Y*_6Rw!?ji{i46av#`l~i(a#azL%3;E(?F|-m%nDG!#Eh|>|2Sr=( zO{NH-G_Y7{DZQHN;(vq$?G$VIe0mbJ6!)!2(EF82sj#pFjj{M^0qv$r(9(@>UxKE7 z+7C<6_lU1)3)l0=u+tsBv!8z=5!)$R^j9@MpNRdn+;!=^_sW?N|HdBC)Cp`rro+=bMJ|#&W;_6V!dqAAu7V4o?Bzj2Q!vxn4 zOQ%c7^qAG4&jn5a-!3548?|72Otw!7_UdtR{YzxHLIC~tUFm*WpJS06uRKD&SNDkb zSUgd2^9cFsUKt-+*@5VkTGHPZtuGk|)DL{-GZ)?k?k9gP{1_3-k~mbZy-6p_4Tf6J z!K4<4W^l#@qlalSCo_zljx0TNyK4cg(Ugi3yz?5f&AScxD0u&&kD3%}yd=Fy(Q%zp z!W5vpmfRh@FEmvFmL=^CY{!Va*qkY}adC{S4;hOp0b0o_qlG)R3{~VSOVTd0OJh>c zng#VBiLweV(DZbx>K($`zc$pvcB;AD;_B73;aC9bQO@t|-ouTkYx)bpJ!eb*Pk{7L z^VIvaV30z!khh#li4NMrlVL%|R>3UA=4(mHx+oT8*@UdKP#^40oUT-)X2cw~U~VrX z?~L0pwWN}o33J?jx&255D`^20_SuwZomvP4Jm~Wsuq8{*2Vnn-0 zzQp8G2Gc=Zzt$w3u&r2dF}kWK1qFQsG{SRw21oOk!SXOqfvz>rHjh zF?^u94WQq$Bt3%&PKfGj-LmF+3)VE2sz}L;@I88WGyTvPor;NbVu5x8AL-CKzjqOQ z!S9UNQFiS)@^g8epzqCrXLV}%H1VvaxNk_vsXMvy4uqWYB0|p8;aEh-v7OvKF)h<; znj++IU}0&{u#L63xg_oL(~1Vge7VG;0XzUtC>taOZEuTz@bK`-R@6kD(ty^dKPHK zge8bCVsPBw0GS4UwE*Bf;lG!pR=Ct?N;|iFQa;PFl`k}ayb0K$>N0H`tJ2J@pMX^+ zppeg$nvRC#+*s%*po3$D0l>#Ze0toehz+rX;?A3DApDnctZqhy!ih$F3AqYjdv zQ$fw3=f&_xZi^&3o4_(kGCE}WiWr;a#^zG!+T<(-;~-01OeB|FB4zh9pe3-V85RJz z5Dj;X;Ex9Ps0{3pCV+Z1!8gg|kry&qbm*C&@3rS^3@7FL4kT~;$L9yT_4EDB3|FRu zrD>sITY$0N3^S**l-l-uBx1`R?G?cktawh>eh>QDfHQB0Ye}3XzF0G9%5Plh6IMXC zPc4P*SA%CYnal3&U=*se`q`mW_R%wjL^ec95Gm|{V}wKe=zGuYblEDwIRl$nHmtM3 zhGyszGy7a{zH%zk?zp7~Bxz>|vN7r}n3$SgE8Ls|S$RJ4ubBx7b%`aTwZak7%} z9|y9WLk^h&p?pAac@igX0XduOd~rS}ZuM))VzjUmx4LW|qbZRu)d!up`4T&EX9d8d zB_nOCw4|hMbyrSeXVuGGH5FlrOIluF(#0$D)%;r64=$l9rdBbU_cwP7*y1IGADqZQkqLqI9VeyI|e58m46PiTM(7{^~qkOf^zcf~SO+&5%xsh0B2o4KNg?@DGfhGPM1 z%NsktUyiITvub+SrDTK?=h4=dWnD-!g@MNkH_zrN^hu3C)nc_M{vr6VQZ)k2oYPrC z(_cI(S2dNST)ZD52T_^Rgg`5r?jTzHNZlK?Errb!jD|XfmBSAYIK#N7(F({CNx}iA zyTXOTfP&iQ4BPiddW}!x3WT{rxmI)*JB>dA2$idhAyB2bZ|F2G*6W4&^kg0mHOFu) z!sU?9?^XCTF6Cy|WIuQs9|o6~lKSoPQb-t)m*s1j{0ySg)H`FI=v@QHadk4JL zBo`NR|GAmLnT})K`IW_6{^r8N{vo|7aGP7r;HHNkq9}8EO?5rDC;3yP3aq^W*0{uk znRi%!8A9s<#MHguka(8->G<)7jIWexSW)&{YSfEcO)8B)TyIL=c?J@E84g?(_T7fo zR_Js&J3HJuKRiPPLt0fG8R`*VH)rQ`xYL&pjW9j(;F#hWI%;Y2Y~inX6z!aSXBann z0UCCHy$@8$wdGJ-JZBTtBho#AhC2C~P^DqN~uD$R$$&;`K@LJ9&S{XI;5xlyNqy)N zoYZC>dj4te>wLj3YDJ}*Zua1rb2RI|{#xj|7rn2SCF^EIh1Ykas8l#S2GY)K^KDAk z9HIqMy6Cpmf>#}tMm1Ri(DS+%-aupY=xNx)JhrnT8Kb)3^zu5ZO6Qi*SqB2)Vwym? zNi?V*Vvyg3=~SmM9LpPccR;9vR3qIbvJ0<&uGZ~Kh*#*6=kG)u@+-@^n z%8@_WBEI%tpBT)fu*&LHVH`nuLYhw#y(s;Y?=+KL``obXEb= zknDfxvG@hWQv!d3(Lm=|ZDNV^M2STnOSjn^P`@`mwneNOjCzth1pY}Erx()2#|h&Q zYd2_-=oG%%R0jx9M)2+3dRJF)=W+K@t>0IuiFzs7XI-Y_!#x$S$cDVgI5!YS+BXRD z)s?P0Y_v19aRuVO9>y{tyZXCg=04~NzTW%dLaZX)t>e=C2aU!EnSXnpNPAi2g6aBL z#Vz0KG&6C)aOlz6aE((qpfL`0G#$-iYt&&Wvrj!o6he>3((T-aJ}1l_x(qXvWtqrX z_98<4V+z$8&F&gkxrX^&|lU6iH)WoUTum8S+`X>#KC|VSnaiz5!Lc3zPa?-#g7nRN&FqkuwY%XKpU?!h_58J znwUTg5l;nXw9^@Z1rdwu1w?Zb2@nS{&~mtGdmIPgn^;91h?&gT4VjxujbaGe9IY#A399}Pj)<|JlWLg8`|<&j9^bbQrB*01jRz<-H~Opy26MH zj4yU3T|G^WF!1$zp#+NDv;!Irj_8lz73PRVw&Fx}D3N!UHTjZK}u zxY2%Lj-q5iJnS?Gthh8lTGvqp*B(O5_F zkZchijL_3D?hL-6P0_;DN*l?fA2Qyv8v}glLZj2iAwymS_kr|B0i8^=QZWzayaInc zdX$w@l#h5{bJFTyTuB{;Ywred8OFJ27>0>)-n1|V9IDfkD1BP67jB2nD?x6^Dbe(< zeH`mtjtP&(E40`BrpMD2vCis;%_)mb&Z*By@x}RxGh0UzMYBa+xx_+{5yZg8Ptm4G zKBhVDX*5<>RCdpLHYvY7J3c!&fA*p0vrIYUEbhDVHN7D#Ty*A+r01$_ZAGfF+r9mR z>U>SdGv}TXjBi3cb|5z(qA?xyI4bTQ^>|vF7Do8q_S`|!0v^`FdJBPXytI0x)NS1N zhBO21i6pHAt1vJ(tv{$!U( zBPo@51X3vr9!n_v5WRJ0|Zw`G9*_=jb;nL z3_X49S)-@dN!RdQRab}J4ILV@mS(zGwW~$>~3nBlsCG|L!JauaRLcWkRZ8L#WMNHgm+}0+o`jHs5GLz=4NatQyso$Pg zt4~v{zoXZ$u6zo6exJ3*?Gz=lq(}ygbZSD`_G6{8bsefL5y&xemb=(D*(9M@Fno%M zwzQ0!5q2W4L6bHl&8{2te3G6RbPlDLt3~^v`6j}gMc?ZR^$V@6gYg*X7+|gI;f8Lf zAwO=XB|vVcBtdRZ6(PT8d)N_Am@GKS;mVpFN{9qQs2SNVsS(JPs?Hgv7K&*oD+p|8 zR#4Ws0BxV*tgxhOc1eSJArAvH7u6C(QTH4CIk0qY&kwsu=FJ+iJZUTW+=HmL`#)s-vnmSp6nOo8XZiUHGnCNfZkya`d~XYLRFzGP#4qA$`@nOli#7_;u5}@ZFvj3@*nde7lw}Ab(9t622+!9tr<$iZozbNZXA;sdwZ~P9Vrq%7i&($6G3Zo7hA%PknQKJB)X;=aM#Zg0=oUHG<}Ix0_j+le`-SaB-*xbLRl zXlPY+&KqsYvwx8r4bTv~TrB3QuMw!5I7c!{e$L|aY?BuEYpn3GM`$3Ua;F|fv#!j) zNu5!%VA1i1R#Urcj0)~HB~};y6llkAfOwoiZgmBY78c=0ecIZiA@FT#qjl5VobTQ( z=IwvYz5BIrl`nhmF3VWHy7EaYLAXM22a^t15BUngt-K8Ytbm<9)<-c3Pg`?UTqO+d0q!D*T`+Clje>3 z;gIfsW~Xg(1h3Wvg=CMHv023aF!VYtZRIhpN>|>(KVpHP+tBM{N_q{L7~ucLjV9YE z@^r!*2=2Mwwbx_TK)E)}AN3{}+_(c6l?=;Y{N*Dp)VO0~(-eEo{gx)cP43ag#1@VA zrsf+oX|CC>6>nA%Nc4uK8Gj=VRI%LaAF{fN4La*Pkfx$&yu|o!LP>|(&jp(>cYWLo zf3`FLPXzt`y-#qUbtt@q0#SIuKnIp~P`)ff*Xsu2er~kA#+8ZpWA1gDPq*X$f#z=X zydlPe0j(is4$kXLY-uwX41qI*LnJ}e$cSdpXat*&7mN}h)`#VqVs7CaEz{&0Del{o zYjzD& zIU(898x5aMcRFRM<{q69Y$x{yFyk^wtOz$VoLR+OzThB;BVXUozuDj2e|~M$!>m+JkrI zl5ACo4uH(0z+gOS3^{5=GMNO;8wVF>;{e!8uExk^Ko=?G7!J)4eiUYAW;CoYwZ!2l zp4VhS=+c9f(f0xU6J{cOB@P_ECJnQ(RBp26y;2gIFLXAckzSg@A=7OZm3CGP+d&h} z=KvRoE?yKO9TJ~uflHNzV@L!;!25o`o! zNUl^3O^rW{>_Qq5ob9?KnheB|$FeZws9~UUJ<=~xjt^w(zNEp;YhW3ieSOy-Xp5w) zXyK?fB;mmbBG-Wy?~IQU6V+0_;=Z9!-M#%%es4iJREA?_*1$)E>Xyn*t}r`P zcY00BN=j7qG+AlBpx$`}$(aFCvjj=Dd@V0Vj!S(1BB|}!QZ+wm{BkK*tmF(4RPM@lCNJ5 zJRozx^Qp54h9;7u>9PL~l?tjR(0CccKoG9L)sqUCFkzh8Z-rNCLMM3=rL=u{&zR!%)NRO4HHxXbIhJ zY5{!I6Hp$Ndqp!N{G}3-qKP#PSdP~uo1L}25NXIfMHP9;Nb=dzj$}3rjmwhhq_G$JwdAxmVhmQqx1y&fFd^h&_`&ulqb+%W;N1wG(h2IcF}1+?brR zL^)3^wHSIxl3Hx%38j|A3Y;#r=r8`VQVX`$lSwTF`#z~9Ehm^s_M3(qY+K#y2-{W< zyTb6yL(VV>Qcu$%wyjcfiT&bnKC5faWISlf^YE>b6lBtbUNGW#0C^K$;U=;vlc2JW z0QDv{X3a2VN24|BuQBH9LSVJYXqwHj*j|xGQPk=R3TEyZfM`j6iDuKdU^AMSTax=~ z!kQcO%Sb3JijjDvUAEMMwTNTnM;~*pqwD=kVAa2@B?XM%`reR5MyFhS?GA?*{@sYY ze63JiIAiEEVMB_$&(k-xRSOCpVWTnVwi`Lyiu<=Q?ZZzF6301Z%gH%b$zDL{DA-PY zQCHuLVHxX6q64;*b(~7pc8Xa$U$Jx=!03TbbY(bT;&F-=egErNA`R-){ez>ka_d=(QeA(&*H#pZHl$NFoC0`3^_4MWTI00pO z0&9s3nd)lWo)Q$ZL~zfxN-w-;TcsM{v#sU|@e$Evu`nO~-rRvcaXkx!`sl3b9IM7| zKap&jo|`ek58JswQ6fDYkBnTGhpD@8Ou3;?+7p;L*tdkBwJ|r$_%W)sl==-Hz3%&8 zyv~rOO=B#k=YwdEL|~|b{ig`n$AbAF+9Nvjt7ECDq_}U$Ibvu0>cViSJHt_! z$#VAy>qK>LuMqoU=B()nXjA84r+aLwh-J>`|pZt#zPOptUK<#aaAI(K;}5 z1{ zLY=m)(jZUUR&&y)z3X-@#;9|$bNC%Z*kOZ7qT9L=mfA~v2n>)Io|w2yEFj|qWBtfc zL}Fl6y6!P5F$H5U-J$rE*eib+g}~FdVxh3q+YtmH<`ZD6iAfSp2h8yq6C#ZIp(Gfy zEOi`ks_>@Qaz~w^jOD;(TL)K%g-$|{_`6nDU_BoA)Ddm`;3EACIB2Y>Z#tX$1fg#; zFe%xY(2c{*4o9olvsHgT|V?2FHyO_{w{GG}Q~!O!G>X5E`iQzDfgW4Zr9Cweb(|N?Z7^Cl=$YvK zc%@KD%wr^LT^<7>0ynQ1L>dwq&SDO~EXTRFh4489aLnq8$#yz$Zfoa3BNYa*iwX}b~_2F(!PhfqRGotbF z7KjwN@*Zn^c($zBJ|!zfxfCHU3x%h&QdA#6UOKtIcIp^v|I#jk85o9K&9-NT>1?Ys zIos%-ONX^>TTSO}+Z_!g*$c#YNjwheY^|=dOY@^^78gev9o^NO??&`!{Io_ZKiP%I zwXtS^0em$E11*TiQ(8Ltx0~g!>1H`I>FZ7N5yN)tfo(pq_=OojuajXAh@`V2Y0o!V zz~;=`I-58ni7(spfGv{Ji(PZ7HG9F7b9bc|Q{0~ow#ZMz7361vEfyg>E>!p46c&_X zW(>;{gDs{H$~<6;yi>Ic_$5c$e)DQ7*dot;D$WF3>=a6lmCqG&#hf!2*rJF6^Ob*t zEhgp4KOgJxwdC?i1M84>MjtyT^ub9o6Lq3nu8t;{Ow2mYMi4hz^H}PYTEXPZCMi@F zY}QmuJIcQMxq~k%xtaUTsq%g+=L+-gH)@3a*WGXTXunxknm9i%%5(~7#|#NDBzO8R44`Eu#u{uG^Rfkv4+^uL9Cp>lM{MH_GYuFvAXiH9k_64(^7V|n+h>OwN14v-Q-9l zz2aFW(G3@oggso6ylh>&9wFv*ZmGP6i{xokG5~P8|Dz)Iu2Zc;9f};>;!RV4h#Pm88 z&$Eo-I%g!H=WyuoO=8W8D^3VKHXZo6n&YG-^hCvdLqbonTFT8A;xqSfEaE$>6yD@! z3q4ZPWG3txHjYIFpC5vr8>+J|-Lsc2!s!0-8$Nmc8>qXj=G9|-X2~er97o|IuBcch z-OF;SrE*pT?4>bFru$lG{TG$# zmZ$SS>B+V-&N3Q9x~+1h8uCxwS^7zylGM&`6n6Iv7q3dJ_k-!!_{IP^bvvw8f8@Ye zT&(bP39=N)T|ywj2Ef9yF(A-nVtucnVTEo_OFIajU0q=So#e%lSl*b-_>!c8KGOtV zGBi%VPup>Q$S*_(&dXPR7-TDzVl`{O`sb@w5WIRh`|J1nFZ1=!`k~d84<92yUg#ko zDnLiQCJiCXP|CwjqZ>o~QM9B0PGcONmbX0#n~6~-RKO7l&}w(!hkY7;cAoY7uJ#-P z(Ifyh0gdwc|Ka0$$b=;ZlI645!|Ap>Bhb-guP#~(AsDYvDH_h8Dl0n!R1XmhXtMBwK$01uGLVmHks51;05+76I zmUo<5adGyLJC{vV+&#DaeQjHm#CPcCO4;zbVU2ZkW5(y7N{;0e^FrlYTHxZh^E;Ko zTp>GikI-0uZb&s!8>X3sqD5E2ubZ5xa%zu~zM_o-z}VT%aeYMB8nWNzNlidxVb*bI zTRC)sIpr(|?3SF;IN@(xQ0#QFsi{RLpR?Yc9Q{JYlnd7McDzQzx08iDa&U4yI633D zni!VL0c{dmI$al|W0R(sv#3@1Ulp^wy_A)ur<2D}Vk5?~h8ES|Y_%a=rkk5l`p{ve zj`X3q4+mM){8$@S39u8+n#v8AyLM{-@^azkVzd6ygL~p}8HC;lW&!+DbjSf6FYv<& zNh2}0dAn*k&9a*>xwiE{TVhG$?6%WygFjUZ1*C#jtcu!rd;a71B6ZM-0S`k~bG~%^ z-snsAvNzgXFo|bO?Yp4w>}yGtaNig|PP$n>X~H`{Bl#G+kT7)24v8%-cL241D2a*~ zgJWPXgIjG38H{=y`2l7$Vf+_W%}DPtw~mpm_`f*i$7c`$$Qzs4gL@BEB&$cdZ!avRCB6o+JLh!=bXg6E7(|$9c=yOQ#XY6nBIvr7KcI2t)Czx= z`Jz(X&zBZ6U(6bng!y8kPY+o!3Y9nY>TD~<^a^KKF=k(Y^I0<b{OEUpln8gnp692Qe@{4YPNvoi(=J{259#0n->5~&otqe4Bq9-7P8 zT1<7%*Yc(GPzh0Sf4=HooT0i;`Fu=uFD`KD<@R^#`GqgNsU!3V)%|Xdo?dmg>5?y7 z?~YF1RdYFOdMlPz+-)A3^1niH4_d6a|JU9^ihH?Mpi{E^km64LE7$D8(<<)w%P!NE zcROFU3s#}wdW{RO@Q_R}E}YDBlcynxJe#YgzqDcDYvFQF&#;gRyZe2z+-xa4l2*=wRalu!} zj%o}C5-@(@8E#W*tq*zxAw^G2kw3I7D^lQ(fHED9Y4N9LIwy4`^vN=B#-Ippm++o} zr(h5us*D3N4BrZ*Sj`FI(&P?@oroWT#-r6!df787#656xT5(aS)s*tItKZ{5P+cvTN~#pE(BCXo^4Lj>k%#J6&JwQf z$`Zc90|t}jyW$V9fISkwbkX$=;>$X~V;@BloSo>>opM?ZJnxACfs$G0ykIn+DhH`{u!pwrAY0~*{`2!-WKkuLWfmqOyIg*e&AU!r+~4bAb}mR&PG?Ov z09;B%8llC^T%&Yh&^claq5<7mGJF<(pK_8R=$<5&nOF`6S)dr%-^2MLiWE=0V_1bB zFlf*GK*lS1tsDW z+0P$X8;~azBIe1dKd_(X&dmIQU7R-G9KBq$f4aa*fJ)HJ0n1Jn`RBZhj2~nu`O_%S z;+7v)o+;?_XSVjZf|K%*2snQ7wsqEe+j@11{% zbaWU`g>pGm)`Y{P$rPqwA@fQUdSv_c+e_koLVhA1d`Sgh=u5(KxPg|z8Qei3rWiky z4q64^cr0}QTpm2602#)QQ^wOy;>$33g>qSPo*hM?G>V{Qtg*b-!R zas~P!L_+%o9W-5MioIjUuTOtL1Vm_qGMw0r|ps_$YZ^D3~F~n^HH4H{v|Xr&)EADI{O}{{ z5HCQ8r9Zt_fS0`FOFLdr4nAl8lCKYOw_RBvoEV0!N_sDP65?_NWCPQIJm7Gfa+}^=K5xCcJU?k(whr?& zPEm)+9xss>^%zogO+_y+o0spf5)!<f0W1@xZiOCYDF^BmmXKo%Ay zQaTtR?)%XMCJBWDJ;h^#IKkL))^4^h_&DFf$vvVj2P>UYUtzOdFX*BRdxmk=t_-K* zvIKXRr2j#j1Q(u&i6k9NI!*6PAxuWx7umg`Yd?^f5mOpw0OO1a<6d}~x4a=rE2>YslvodqEw}p7Z)yqoMAfRMXlY244Xs*CuXdC-6;^j9 zb9ZSBs~-i%U}&LQ>)|_NE3F@&sKsNwsMgeoeqv5=8H1xj<0f*<>*JD zd>E-R@s-nReC48bw@OXLQCEtJDn^d`D^72VQn~I+4TPxOwVm>{YXyXrEIgxBl6k?= zYn)Ow{{)yFj=~9SQBsnEbkXpaf70y32e480?J;!Jw@c-JA;ZLx(4 z6laE3-;v-Eket7=HRiYG9Ht-8)@~Z<*%` zn1ML6|DfbansU?S%-#5JGLK^fg3v&D(k!T7~ z8_(Z7<8q?Ha?f79JGwx(8d=Bf)9UH*Pt8Z?&vjL8e$+o}qw}km-M7tK`$hA|lkQP} zbUY{(FRt$@SD&39dzI?d`0b$49rN z=9B%sC(mw*w&-T-)oElW<0qGYyU}{D8}*W&9en1^eS6k>qgupB|CBH~a#qq??~AH_ zVyv~^;{&xsA%R<4@Dt$WZSim{#M>GRKiJ&nmvuE%w3;UJ6SZv1sK>8x9Pa(fOOB|! zmK=$LoTS(0gBv+mSR@lfAi1Qs#RS|ZzY{KKu$0KaFZ#(X)C#{^c=@4EqEdu4=BfMu zT&KteLDk<8GGpm?^kL7(WIXoka=`&_p$z869>1Ch$FXFSB?meh1bFpOgrQXUTl4`C zG&d1*f_2Fyx(a3*Y*#qbV6lMa7o?05ML0TmRmU3NK3NK{_>Xs@z&(a-l06R-GeuR6 z79}})n2DRuxDAstgkK>g!e@b*aVQ0!!yVDR<4|1|tfVmG1QH%}UnbHF%KVqG-X!xm z5inBLq?7qCk~W)sg^N*Gh0rA*cFbG`CmDKx0(roceKpcjd(ND&N6ogsVm~}vXH#c zi>zdCgKdz$+%ji-^=_68XEtXdz&w3HmLRL}$T&Upd~ypE*}hQP-g(ps%Qa6t?R@~< zgMFRncXc_JIkG?XELlVYFXg!qE=YETGfPnUPc9{AvXB>-ojCsXPZt(4ay}5(5)nh9XI^v*on&1!I8gnk{XLZU1+P#!_uC_RcAgiZ;g-AZ&;l_up&~u@ zXPKqw5^p>*At~&vb$Rlfh9I21RH`aavxhvRRD#Bpo$yH~M(|$s^33d0Wu^fSlHA`L z+E)cHKGcu%9GFuyUZSP({AKfvUuIpH;9Tk9CiCU~9$!uAMewbNyjYEAp9|F?iT30L zV2P&I;e=Bw8Nm|K+}fm#ugsIzY@JokK)#*mTf-4=a{qcyo?0qaYI;#GRU`VT8r0A7 z*Wt-qFZZ{HI$0ncCfixxxj)5cc-t$U|MBAJRdb|%eDv#&zdW~YCnp+jWEJ+0ab$A_z7sekyzkL1t=8r!vho8(}U!0e!P5nh{Qf`f2 z{B+X)wcG{~IDfWyUAvrXTKmIK)9JB#Y|ksb=?(fOf4sOFCGt&1O>W3HDRN++4y|ig zuo;=dLYjew_0`psPRdm*ESp=S8ANm`ZK^HxudQs7g zA{G77ifO9;R9Yu`E1l&sZh8`q%9}X{03_A|hv8@yTp2}`1Bt&Is{<+85&y9~s6p}c zNB3mhEg}Me&G{fFaM_c~i}tO0)NWtg>a1%|;KX^-I(=R^ZMO@jt#;w$_(U(XpEvP@ zcAu!_Dux8v{l$}4uMg9xQaC^8dQ)+JPNL?=AN}ftY&QuXJgBJ}Nj(Hb< zF8o}O=v!V9*UN3Q%_T_p6imi>r$pI!8H@%0&b^*kar9pE^Ys8T8=LzfJ?aoui2mXHZcrOVdOmq%|dn&+>gQnsU2oBL9VAYx&+(`>S@ zNuX9Z?;FCPsr4QHa=Nk^uFPrQMGQ6yQLm8GkJFQJ9vIvkSV~A&H0DfkbMw+dhEmB> zdFv&n7YpZ-GL?Q~isFv#iK4LI_xG;5T~;`K-hAKcoVk#ZYXiSq)5ODv4;j6IT*Fbx(mdmO>CxPs@|<t z?k*6f!z+vRlSo%nj${fA*L;NgUwJ4JVa}-lINA>=3Iyw*Q$WGdTk9HBDmTw%-e%`L zqO)opIgT>J!qnWi=e%!LtJO+MYkAD9q<7v|&Q7Y;c;|ie17pnIiO~rn@BlCfKdXFB%R|Q;k|tsg{d1rBeA);Q--16NmM_@)8AVCM62!ee;aL zx+DNJ^5LEC+k&&hd<~Wy^$8|dQFfd1EV(I?thPM=#mnxHSuF}XpUFP-$mX}dhxwlf zxpIe$3KLK+HYdX2DVBL|mbkPF~^T(6((zxHMR@66@ zlbf^ArLEU~sr794ym@tZ(*LPidJ8+Cd^J8k*DJrgx_*B3u9YCmDfp!4kD-LUift`uCK=3V$lGdDI>oQirOeW z8|R?%m-}vDyz029t9n`8qN9JRe!WE#sRSpv zUi_<^KKG>(z|KyeT}uM2Qec#I$;s>Q|M)nu%(V0d+f7ezur!+b@tjKE^c;@QhV~V8 zfgpze8y$pHb^Fk#^$Cw(oy5cdH_au=f^ z65!1of~Gu7N0dmw0HLX<6pVF`ai{T&OUb$g7Ybv{HS(MF=TnL>>XAbPCw}U{RrMn! z6Gs|Ao$v>|1|?f@O0g)PRM1nk&MC##`J}LsNT-zR4)Ix^7X=5-FZc~TQxPB5**VS= zznlFxX&;>{WCH8hyXeLecNQnk+h`~3A8-1$Ajf|xh}*rU0=J$1Q5#2#}PFIzv1 zZztmkWi@xy)S7Pde#G>VUh~QLU zqN|Od0d+LV`jU~)5sBqu{B}~83R+SLKj8-e1u^VjsT4qCGNt=HXGb`ymYHAB>Cd7NxA|Ln-d(^f3=E=O-q3j0B z4re#mCZvrEeDdba*+uhN`^oo(^N1&Z9}IT!qw9J41JNK0*601bJ98m(7l?=&dypK< z^DKrSn&+E?Uz`_@ruiLFXunVBhglf)L-x#?i6lWUH)eltWRK@FVl|FL2A6Y-BLf2y z?p3XBq$J9KhVs}-Fn^yn_w6Brs-~7s5`~*5o|W{-prmWZCmu3L_`n!su-VJ_XR~Fk z^WCjvoUXh0%S!B0t!(hz)0#ExCb7>TWY<@GkSUF&VEk=S}Mh6GEv8sW6w-Pzb+ z*|{4VEGvg&gJtJ(l+PCot2AMFJCdGKTUcSqy}?vN5|w25;q?>XK)JSNGa^$LJgayy z4brP4;5Z3|l~LDH7I%(3C8@b@PgvFTQaM>}RN`4lFRaSONkENF_`ujYOcEmZ?r}9H zqgF1KOI(|)WlNS6J8^Nc;l2=qr{tTl`Z3 z%iq+hcb8S|?H|wIxRakQ$L}Wky9@%$Ln|GP?bS+Isc7(5DP>@}c3)V_cMdGqcI7W$ z%}=jsesFWt8fVMA6=5~PEwH9q&-SLKfUzkfb5mvprziV+!VQ$2)hRo>ljMxb$okY^ z>+MgHoN0gW1L;>b^$Q;AI%88%qQPS@2M(~)Y-)a@1ZS-ABRqH~gDH%-bql-i2UcLb zYB^|0w{Cwn;RH|gU|b4lW6Vjk-l8$%ITZ>L#ti^A_Oz=oQOyak3~VdJYdApzdqnJ9 zmWAs(u=tq8^zHA#S3}3Sp=3QcT2`ynBzdX6=8SUHKEWdMp!i<6A z%ByywjDe`h4P^`@0-V)47Ou$*a8h&M4&cUjaBPt&`R|z)6|Ue=6Wi z=*Wx5rTJ|zX*`a~8j-TikNk7UV(nk7!C?NV+Aq$Y_y71O9@%;3+evukabu3{nhAt)d=t`My&Cl0$Jm#{iFi9>0oXM=w=3WGXlFH#=V%Q zjNmRbc`h5w1oJ|JN|+=!Gw8b!{K|(0|7KeNVeghq;BbRw28bJM6%zYUe_(N<3h42z zN56~4eDdf#U)$wdb81^4cd;ne)VZFTBR5Z%(Av{T_%QbO!sEcE{}bfIps~!-n&9;K zs0ZL zmEO)@y`Q~(^XQ{KxEK;kYyZQGWZAq?lN(z>GK|RK+$&~i0_M$qdvOP)N>!_+M&^;T zvO_hgQb}*EjXQ{V;8zqRmkECMs!wH#pED1qC{@d%%tKN8g7FlA4H1Wf?@Q5ywDAeguKLK*ih(SJ%BSP zAG7C$SO-=jfy7$rx2oS(mSnULar->$_2}oz&4R4qC zo$eeNiibCD(0*gmQAN>0C_nHJMX5!`nJK*#Vz`u9$1O6L0d96@d_0u!1>Nq?hr9{0 zIYDQz9&w-}i{MjH@#i%GJ+)p)#bMu^-k9#wtFbvz1}g%wRI8WD*$G76+!}$n)SVq< zp{iU>CbrqdLFKN7b1U#WXq14qQR%2vlyW77Bvf_1T27RZop>5nMxX^pfbq8GFgOMk zOW7H^g0m#} zGT>pt^|ifS=|-@pDD?xwiCBxkwK;QS-r8=jFt+YiwoOSjs)b^er5XX!N*4VQghU|L zoH2}66=fw{Qw_dBF*H-MQJNMBBF^ajV;9yfNIxs;g?tC-rqH~Gud&fTKsPXtN(%Jq zXH|X7-aTeyUt>1N-N(!t7v8lWv*~S}6kJRU?cO*?$Qq#vW~62p>#`Rv%<=UchEvCT z=5C&0jB$iu-mad=Cfu=&OD};#Z=*e8JA+A_^}+`sbGJan4jwbXS<@dncc}GkG;jP| zcyCYP_IB4CkMT7?z6G(dg)D$w-+XDlx7%?L=e|%}FKyE8(9P~V*V*~E;Qx)SJzNd^ ze?z*s8jURcTa8ABse_G1W>H*?MrJ`=G;LWKfEpXTSPf~@@oYQas4OZ>Xy=-SPC-ebaCG*Q|B6!b-Hl#Z-_?`U+W-2%W0@qOGfpqmJ(dYOj2lg zZ=S!tZ2bs0ZUAr1O>mR|s-fd$=s4gg4^l@>2hog{INN9`ie|L^?Dbs{D@vy-5vNt_ zT9G|_d5OG7*7(*MIuonVkJ@uIJ`Ykv>;Mkz4&K_`8`hp#paIVgA}{0(MD{WjxQYXw z#kvk+up|S6!j6`g&@Y9(p?)ErSi-qv$Wgo6)m6FoAC;L%)PV$42Zx)*My_q__Iba%gya?PXzGm;sO0lSDRrpI)ivY{c zM}*}F0Lf>EvF93vAsX!67uNaZjk_J%+m&&CHOfk_tvW%d;IS);HUnk=341C3jkskd z*oi0ytPg-Lnbf_UNz{_ zjA{aNW*~l-sFy6W@+hy(;aS9jF{n$A$J;=Xk8$cSKNCK<9$GFu^kiUJRN;DmPdS=; zff%pFDlz%8@&E^Z^sHNA_8V&6uo2c>^gDU?j;(-VzcGJm%)h_)=YanGz54_2Hw9m*7?P14 zxc8d-_JXgprg4(28ermCNgsSw)SxTz!B^seiAi5XuKK-Yd?^{oBr2S!o;fq~5T)?6 z#R16s!h@eSL}^$0`~AHaM5>9$lf{WO1|}ds3Zmy?(k4D_l(_B;j$FRK6xMPlR@WwJ zbw`DC5i|AdDPByx9Ej(HO!Zj{;G~v31bAdm6p#bA7U!fykP67zBaR#(eBgYMBz>V3 zK-j~Dl7Rn81%+G3+~kaoLfPCmcobH(QZeOG7&9w79EC--X{)KK}bLdbm!5NC>B(NkK z1?-iIHA7borKt6_vfiye#WEAiT_)qCJQyd|M>Fy&mU}m*a%8sEI}D!WKfP;SoVW;bIj`^|XI|C?xCi6CZ*%T% z&i%cJ4>5>7xM97*PFUsf#BpwHi%wCH1LpV9ErbhG$uBy*3}%PUODs_pb*o;fv;W2~ zwx~}P*G`JsUU5w&sO=SXt5Js05I5zH!>M55E_zvW_R(5>;C@q3+NO{?ElTXudI zm9EslwHKCoi&kKtM)tptrj9ei^G6HU&Z^K$h~$vpiw1Q0FxcybDH^n05M#@a(NjD| zK0!{`3Yw|2M|!Es3;?eswZ>}25+FMBv-ntJm2wFw;o#F2Zm|t$fd#Zt zxTFW?+EXs(cwy#%3aXat28Ksuq=I_Qtx-W&d$daiT3DKc+G&o=-oRe|95kddpixZW z3P##&Q~0E&)=m@Uq9vY{wBhS3gN{|2imlRVsjBML7>%xo&W?yYu(*p$QF&koFZhbG z=L&njFx`WgH4Y|&$qS;FD;Qyvv~|38OZ8{_l(L{^6xMuF>z8qfWhGUS?PzML0&QzX zDQ3QwbKTQ-<_jdI>Gy?te|6hzr&eW6!4F4LNhFdlzi;`att>SvDh0f)7RD0mVH82I zyDHXT87X7bpWSr>Y-I=KfZv{>M%G{nbTWGC4ocZyGU)p^^+Cm~5%uwws#$a!36-Zfg`*XL)WOb%77P(-~~tt&SPNZd;)hg;bQohP3L0@3!CuTnjr9gVC^;Br8Z3H!HCYxa|Wnnzf2v^#U>yJ}`k| zlq@JCJ6$a!MJyW)>PrTdlnqU-RfT7*l5KhH*oBYN>h4SJhLk}i`mPlle7((!qc0Hw z82W7E^tUgoy|H_yzU}Y*f)%}72T>z+Ug3{s&&~ zlQtDgsxz)Xb#GmXN<0vE6!G}{EwYv(D7fY1Ft@hL-yaoTfu7y(1KMDPDgGYa{Q?Nu zh%Z@XXQ&;+KDU%pn+B`s>O|Qwu3qJxEx4M)u_itlodYV#rMq$pOr~H3FO&e zJTGfnv1$~HO4%qEzqIi@S&zSjh5f!1CD_%%zH3Q>7+ZQYvf?zVS-_=i?cGT+6RG8G zhn-_-R#*qY2d6-rE_^td&)9r|(QqT=G@n^N&>wCYu%|r*qYwc;(!Z%GZY(&5yRb+n zPwwC*6;bz-e}dh>)J4n};kkv*O|bqY(#u(F44Myu3QRQ-iGV+(Gdhg!o(|1%-!i9k z1czj5rCvg1B`cXKn_DAO!@WrH+TQgKhV0M?pGPYNd_%u!+ho|Iu>*BTl-66|)A{y~s zh$Ag@qc;odI}ss|-gybKSjX8O6fe;&TV>`?W zY}k=FpZzMubh*^Ymg-5FglA3n9hkv8 zvWpMxF|Ry*bZbtJpoJq*r^986B9jM6I+T$Lp`>AQ6rHWSfH_rKuCwmYajhILr+g?R zf1MqpEt(Xxdt12gt=2A zs>5!v+&MBgaF2?AX5rotBs8q`c`}6u?eF389WoZUVPcK%SSV+8am%y_)D#( z17hP`4e&yDohf>sV2-X~w@tCuICSdHoRX`BPlrh*+wDHS4wCa^Cf)^Z@yNscQiXY& zb*Gjf{DsSc_wl0UH~5j_4%i{zdFQT}W`7%>(`$>3%i`Y_y0k<;e_&x&0hcBK211UV z9Ar!ownNtp3_xGA(coXN>SVJ8>=Rfc?H*nubE;ZK zILd48+jHQkl}almsEV7F^bVZUX6v}_PEwOVV$aQAt-n7=q`*JerK4Zc zJ5((i73fa2T+|dz%eFjB?5>Ifh4+PwKCO3XcMAH~WBx{Z-k}P4htNQoXUht3h}9{n z0fYN{vNK|2XU1R+-eO`?2J^U!QW!J@cTt2$p-pT_fn}fy;KQ7(u^< zjbjr6h_QieRljY$5n_cdef{NMR=k$9VvWNBP0yCmBX%w0#Or+_LQgARcjrKl5wB>t z%=3-ui^**4E?C=kXUhwhNZD9l8Z5E><<#MEgGkhQYNO7G770{7iK!1h4fhnTL=Dt- zM|lAkuxaT3o90V@ggl}hby9|L5csCfgc_UY6?5R5In{@OVGXSA4YT|Ed*1B8Ruzh{ zs92YonW(`w8j5_H#Ow?_4JJ)I{!IDPEmj=u<1j851<800k8sg(iUA_|H(=li$$ppH zA2>|mljqX3yEA9XtptLxORyv4S$962(w6tEbTZ}}#7{6EArWZMD|J0H>ws+T+XD@y za}u-VU~X$Nc<>PJ}huqDT1G!MWB zPX~8X1rY2X&e%*-@D=26Mc1SpE-lO95Ml@_>YA+6R)zHe`K=n2it+SjVIp6hvf3$O z4JZ|B6-`I2x~9P08>|81A%uP@GI11L~ouIRVQ(#K@erJvej-j{)6m`z& z&Ow!*!TIRRV~!IM%FJEAHZL8SxIcY)jct4n?GviO8^;8h2qPf<@g21YG7wstc=+VQ zz?lG;5+vJGB%4-*kAyQsQ_o4RlPKzO@G$BT8d(B$JhM#!#m~8MIj$Qm7{wYbUuwG& zs)#{(1f7E3ACt!rz_9MnhWlJ@`9LSl#p`F#o=n=g8`xb=h1~E#B7u%KWN?9rLq2Tt z7G5P%Okoy;p-DPELfiBxDbHRIz*Or+t*&Qg^^nc|$vk_b&nm__xLD5d242cB;NV=; zhmsDiR)&)-P2Jto>RBtT@grt(LL>RZf%{iT)IRszVL{WXYw7LadoMsUYCftUr4{3d zerT(8Ql3B4p7qtLRw`o^j+ovrv!1RelB=kdQbogp7(GL)Rd*qh+krbSZYW+E_x1I4 z^DrAp9Xel+{&|UhLJ&|4%61guV+8NU0?ar%>Nj&HVgBLDY z5>HBJ(3b9?hVzqXRxa{0SjUD_% z=i<3AUC78vk9HkDMg>MQ2l(c71ln_Dgw^+C%R}QEVgv6Ic`rwhu)~&!Dy$i?Saf>U z1e@Yd_+DmT6aMx3j%zx$xaCkx5T!U5vSMcCPvE<1!k94+C#qK}^(r#cCgH{REz{;c@m(c_!JATcDyoWQ46%MlP1h2} z{QLz7?~CKRUFP^pBpKb8DgwJE8SP$8K%%+8fw6|Li+do`N?W=;V}VP#HLq{T@N!Os zI#WO@-P8LHuWZ7Jd7$WLK6fD<@h8VWCdLmQ^Y=vtvhsn3RKxO(O8D#ADYO1 z&#)rdR2B*oFQ8zck)ub?XF(G+c|1f)eok1xA>w?%fZdznA|=5~q=zO1pfnwW>b0!W zn7!uK0;p)WQCbY8F8d!?&i}Y6K5m`9Lo`xV)LL1oXo{|;$z-mbRrPey!f_L`I-1S+ zDPq7>N4Ubv30ES_WEY=~fBsO^)KY0SO7@YIkCQk*fs7;eS*lj-%9ctyVfNDMbXZS zGz&OjVU}DQ^=sUorf@{yF*&u&9=mcdK?9c>&rq@0!sg|QAb~ORb#Vudq~j93=6SL< z9^Ny~vPEB7eRDpXb%-6q8s9FbXXFFV8KP9hAdUS!;YPv06E7?V-`N=t-T1#FaToel zc)SXUZ*=L8Vb(ze!Gw|nX@8GI3=NA9#d{JpM3PB{Eb(}y*>oYwlA`n=cM%m{8}~?r zj4-mm?DZ(73BTm+fg}WRLR5OL;)mYAUO+PovXg~+p2xOO&MAR36!yX%#YC61q2nY_ zs;sEhF*8$5Cs5iL>jJI#S)Q`FZ!fx}*{mFw5^3a#XC-}fNx5}gu6WTUaStpe3Q1i) zkpHUil3mXJO>rlQ8~UO#4n|eSE?{4ja;6~;v1=bE*X|1|bjlD1V^=olxDW>benxd& zv_h7`ufbx)pNK!mNCY-mmO`+>G8KblvCm8pHdsIu;{0KBCXT>`*O61`e((D6nN&R5 zTTPMZ8d^WU{Hhov#)!&rTbv(P6lKN2*RH#?`0`#~X*B0EhljCXZ>SDz&Km%h;pS5f zM?a{PI70Fv@L4^iQ8np7I8*d25>qrp?2XW~QTfJbQ`B*n3>7#eTvHd6VJxKR&MjX2 z94^3+5}XlO3EnlX={q}r=GNejs6M6DW4l8OYn-8Vd<2`=ouKJJgoMO@_nTI%DCHX36O~dW1qJ~b zq9)_!N<2fMap49kX!dR2D1TIXwmFeoOB}L}YRZ@(8PWRn>~BUQsGF!!p#$l@inMs34QI zF`?PT8Yc=h-YwENQK0c|amIBr6|A+DPH>a^ZrcN22c!&J@I- z(`T1rdwmzg(e1@>GC)(hfV`}zRFx{rJ-htz5y#YU#@KqbEJ8H^e~#y$3)kcMVakvn z-@ufBXDAA(-xsFesMmHo%)TqF z2R_S&Lh;WszkRnD=$)gWc_UJNNf`$%8WF+Jp-5;x%pIbkpN~d7r04sQr^ktKB2?L> zSB(4e3mUFRAJgk#uCX8hYqu|WzMzTubNhR=h`5G%o~0-<@Iv>&Js512!{MPCf;yt> z4m$wZu-i}e_ZF6WNLhemnd`{6(;Y^*iOICbyjNv>5|)PMM1-U)*|Rxc1irIG?^WpJ zWPpXRXH?bz7SI`ATT`wE(^ur6S;p9U))5*77G;Ii-7wo@!X4HwF}!T{rTLBYdWzzH^CbI#Z9`$HmQc|QvSCh zCtv~ixvtSag(b2Y3Uq^&(e+rC$eLOM#Yw4D5MWnQ(?lBgg`;=}nn+_;e&QAUSXRs2 zW<^0BE|)}vAWZkhr>sM1&QXR(JCNfd3|@GERUXgZ{@xH)c#1~WLy$l%lqX*JG8g|m zW<-c~w6vi;6l)0)kDn{C-~Jx_17oS_wWCO!K>9s2hrK>%hVfF6uke70gnJQ`&G8Wj z8eS^CjbH8Wg>v+nJMUt=O#MreIc7<{h$RpBY7hn$kN_uAQWWONq^aUNP!NQGVa5Wl zkP}Tv$e>Qsuwr$8LI&RY{~amVcd7||{_6Pk&ri4^7rxuy`v;&TI3h;aKFJJt&%Cem zGOUPygC=aRsYA&bh#Z3ekYHni+y`_RAQ@yMSS4aL?b@c3N2~=3Ly&MVCg%b9P1a^` zCZ_esj@Tn3Y7eQfWJT|hnBYTHe#wgD1GJG0Xg(S&n&?B$Qifz7iReD$bNSy{l79HK zA3~SQg8HMuG9mx)_E0SShfjnOOBEV~{aRfs!rPb??3c}bd*K>dv!qtjg=@skO8Rh( z%2`coQMg8}9RI*lqHv9S#vrx1EvKjv5aazSi@l+qXD?{ zeSlSe#|Q#R5+M=a0aP#x#2m>J??aMUT*jR5@7)bTaf@6v#((j&f4>@rGxdvMaBrEYt^t$F%WLG9 z%uQUHa|xv?%f&c+mDE=~mrMtORf5KJKOdFUfl#|h!4pR@zVjpsu@}NgdN33M&r zfo17(ZgJ_QV&QBA0n8 zC!S>~?t$e*mZj8Gvj^eO)PvaA)Wpzp%h=JsX=lG#+Sx>*zz??GF=5qUG#}3F3Bc#0 zk^R}~J!S}jd**cUnr~byYWN)v`$U()CJ~ zX2SS)JWgl$%4-%FyVfjN(HBa(A&Nv63ek(alL8rw5-|@WzZ}dT$S=z%j8M?v?i@(i zVCh2%;W_rob^IFck>eN+Kp+vGbbXc*E`JnujMPU+VS*l1$xsI(mm)P_7hfUN^nAp9 zc7W2WBfofpeLrBhUL0Ps0O$VZRX?>|{|z!{Gth%@k{(klm_hYKJV-&uY~*hFT%bNb zW-FKb5#s0V*M(}eR4b^ZwfpMR9QH`BfXOpNY)6p^&=M<5=iYnB_Xjcp9^q8bUj;uT zSLrTYIZ@@cu+|Zm<2)6TxBkG}>?)}?SoBvZ%0KW3c}P8a^eDC`>)Pc=Rj+~eqWG;A zy=>Q~Pgy~&mGS^Q@rP=#r%(6yo@nqDd+-2uNX}+Y^i%kc6%X0}(5DZM9*WYFpbWs0 zk74UPJc5xw;CGCyQFk=S$?rM?3-B0qu^~UpGhI7m-|=Q)bSdw|4&l9Yv)C>EhY#!y zy2Nt_{>VNl z@QR2*@!1Yt`-uAMJwZMM_tvtQOV(Xlw&1p+F|6*WI{T-4kmrrkw!mJ&3nB)F?*)Sn z15#ID-Fi+6AN(7PgsHTN&NMYNkoZ6D7^e89#d3}uru zRt9U%3vme^Ve1bp*_L;n6ia{v>TskWJ)HcL?2uY%E8=VlA57JJi9M>q`qR4qqi6Lo zb%HV#Iv3~d%hs#T*(uZImW2s|*OS5Ko_A-=c7~2KVGsFFTp95_hyy7Kz1{u~KwB6E zv~hCDm{br!b7*;q6%bD_eL3R#^%K5&eQ4?{b&O3eDgg>91qle-OyoffLl&kU!@p!y1$w`THd}|P%ykPws%dhd@(kgOdsGwk$wml%1TH1I# zsujLf0le`8QdCoC|LHDVSYc(*keej-Obi@`G=Uz1p_@P~cKv(tz7Fb}mbc~QHMb7D z)~WL$8GN5ee3P<>vrqlFtC-x-XrA(+syIfsOva4w&eytT7D4%UvAt!a}je{ zz`)jZ+X!Vr=-Tx=D%|cy(F=_y*jzD!; zX|>yr14n5=>So?SF*4sSG5r063zw0Da9zHz$7hZ~w;xjASik-Z;@3)yBuY-Ti z^u!*fbo^E;c~wIw2S0_xe1MNetA&DpiFxHVjK^`44!=%kIgE+2yH*Fy7jGM{AM4&w==<=K#OX29={`<71;#|nWrQ}eEbH-D)=(KAxO(ev&f$os;h zjvu2Y2f5Nh^vrFTGTz+kY=tc^S`qQtBTcJFolk75qlbQ~jB^8x%KR{*)1o94ZuOYl zNuV~lgYA}+Gs(9X|2Al-iTYS>COL50IbMsQ;g1f2ixs~W;KeZeR%c|5Ks>wasV$KJ62#rwdlCc5z&)`x zXRh@p)mAn%9tM`O>97J*M%CpJ{Oz0e@U@oECpQ?l*c#CcKgHY zx2K)cR=e(P8Z^U?Cb`j~J6%j>Ie9l6qk*;#mwpX-Ft%vL6L68Oc^fmzsS!J^Fd7!` z#|Q{h>Ad*&nk@=nT)_^ydAvGnFHcK#x?t2R5l-T^@W5E*`|D2{zeKDud`-LY${w53 zMQ7%qCnLu%ft&3=ET=DiJ@nrC@G8NNgewt$Wx83$?8rZ3mU+siKiRPVvDG@g)^*-3 z+VosOFOmbV1ShF3Ptv93p@|SH13$j0ML7sh|AC+H8nssGmPh2Y<7zDbu{-lITnFb+ zFDHF*DoQo9&Q)u9O_OUf3wZlDNIR$>>*9 z-CuvvKV1tZ|9N=T0%!BAa&iA&U zb5mr)%H>-@%}r*@#E}j0?IwwA&=EeDG zhmPyhWmRE=D#QKMV`nM+L88#j$_-o|?VVoQjA`E8N8T&3VUP*5{9rGl-v;*mKi%)6 z_5IM(yd(M1rV~dJI)n%aFe08mUb^EwM|?bB3w2=mcd@Xzjf>*MH_EuzN;O}Z!R`z# zbGk%^z!{OPkuzOL*qTls4Okn$<3sWfH1T`FV~elRI$62#s83`Pkbkn)quqhobM6u! zY4Y-fN1D7m>mzNY-6Thvrju1hUw@>z{+Z@ZR?jpy!J+2jhI1#K$z5;apJ{IDLyh-i zheM4|SGI>*c#sLsv|uN;J<~!D`wNb=(DRpm^@99h!-)e;*HD{yWR!fXbE*?9_?+N@ z7M}Zz&a>e9eEIY2ia2>L1xIFLxm(~4yzV*kDM-R~JurRDvyop3>aj)|{*HgPc48mXJOO2*rQ zNuKtH&Yi1x1__-4QFTKEcmyI!scd`4!j^M05r^6tJLK@=Zx4TThaZ{rPd;1`1uIMu z-Zh5>^^pCiFdY;gJdpF5JCGx&v*1I00VH1|>060CQ)EN(RPh^IZYqIW#W3<}4Ng@Z zLn`>G%9xC?x&0(8J3KiT?E=FEyM|MqGulxyClM}U_9g2N&1))^Dl}{y0V<2ohJ8KG z1t*V=8a#dZEb{QuLSt@&T#&!Oa}HNdrkE)WJ|{G#frm!T?6N!!&uv_*5yCY1oHZUm z7C6;!#c55iVuy~_T=ZV^{e5_k7gvOF(D(@fajZVGgrNdo{Z{;4e*67@;Q{jfp^R1& zro8W6hV^f;8JVz3m2B9;LQXU?cA!OHMAk>{Yu*c65H!o%;Ke@Je(u*awmSd*lm}A< zkieQI6I+UzS{cmFSa1t&*nz%q^ame)*gxGo3-1`b?LqwVK8k8pxP3OXOt!{AqcPhj zVlReHg0g|>7LUp`G!*ia;NPLM@-_k}#J!GzEgd-(V(nu3yZwv0_Ic4SYwGt_%muw# zcozPPy?Wg_f7yJqZbtBX3{J(&A5M4vPxmQzk*ArCb(0Z#)E1(kN>=--7rz-o&9||J zfZG^L09!%IvEpzE7t{0*y7*#qv>g`r=V=`ZH%NNU<;uo^==iRw%$En^b+h$yF2TM5p@uwc})dB^lnP9 ztnd<`@L9X0co{CMymHfW`faYws^AljGrxH$Bbtm0+?Ij51Os{SHvXtdJC5%lyRjA` zBP+0u2fmVi7<_3EGhI2(Fh?0)0O!11wcsX@Gyf>&Iby}b4|%ldA#$HQ79NY3F*x(_ zEZDM)=^xM7cs@$ADPpplA~r>QhSN%$*>8%?$xgvdQBhHCqQCi;@S~j{`@ePvQ^)aa zeFd7u@M81VdFA8$O4`$d`53V_(ehG0@p$%l<>TNB{CHg^aG|h?G(|1ei|OfjSJ*nd z=Dt1Bl-lX3UP(m9n|M~zlcto6a`8BxG$s0hUkM%WT86p5jE(m}0yaUGWCUCAcAS22 z#vj|GqyD?w#rfsC+Qs14(`39I<)Y{$Z1_^9g_`5HSsZUi9Xehkfr!$OT4V|vgt^G>v*2DGKFTYzhrD}4hYx#%2@|j@cz-ivJZ!LaoQDmT z5$j=tCE`8gRBIV_ z7rEd*j~!3xmJEh17%5O8zpD3oaJ2l+_7G0PEa{g@3 z#NNSYNxB%5rf@F$+_o&)2B&(#%7$c2fI;86OT!GV{SN_-zWqJG9kL@>#)R_~dcFuM z#fYw6d=^%ppl-VOTKf+`zp;|^jN437!#7MkM~x_0x_oUm1T{affEsa)dvIcNS#|T4SUMQixUR20#&mb5lRyt)o(y`1Vo-`ML$QH4hl&F}(1@qgW7L zA=07ciz*ZKy>IVK8_&KD@^V`6$~+X=??G?+q|m^$X4@Qs4%a&r*+_kJW)5>if)abY zhR@c3rD(a$WiR&jtvqe-8fTK@De8wyj`=W~fG3Z3WGuM{@55j8 zcF7rtf3Irc*Q&<+Nn3iKJpPwb0MqN_!q>eQskZVS`Lr>c-XJoJ!r#(*^5*SpOuy?~ z{_>{PdD;9Kqh&oa*aotgB{CTb!lpwScADLD16Ob()^ONCvJnI>-ap(<-^<2a-CUfI zXRkdKy{M@I3h86XgWx zs^HKREIDD)tHg)(dN7>&v`*3N+T&UTdlVX+E(C7nCTs84j(2t!3i0oVDi-h(e4JRY$v`R9Dy65Oi(T2gWYF2v=KIZHMXdH)FRAG~2D~_@5!k%4xiubx z=w7At9=P4^bTsG|b;TcFF6zVIw4$M4#T>1uDP=u{%b=*9oE4F0y}Zd~5H~F=m6XC& zko*Ct8c4b->MRA-&=k(t?+@lTRa4-51-@6{``-_PNpmo$^^+2)ixj*MMkq+DHKS3eoip7$x>sI8T3>=h!*LxXytzFd)J z$tzdGy%aBS@3S{z&#c)#SfaDgyoQ;ovzN{v@FJT>B?V^tXH|X7-aYnAX|`T1KW5gr z@UH!sO>gU}q8_qmR`HZdOEQ`Tf>>v9k_hqB7N3D2~?@0xu=qq1d`2tdp_&(sji(RD)fdHRD)4C7*S<82`XzKI_22m&`MrSSFyadW%Cj?7vwO4+@%CEX!HLB&3 zqSZ>}ic+m*>Kcw+%{UQKyC)(ft!ucpv)EX!R}XWDzFI_o`Q=UPdu=zLxxZM*9y6Z7 z1{MxjE^l}Z2AJIWq-V}}_3u+E&~P*dg^(032kWVm=V*V=p0UxwwT69#dw$R<1Q%hQ zdGe~wCm=w2=7N{EUohm%AkWY7yMe+=Uq}cLTdSAYY&fIvCtz_4_EeoIdJW*sgqEd5x{U?QY*gpF-?#6xTuvIblsy9jpD3&fS5D(Eh?v9s`!lOq&sUyQh0ZX{<+{UC zV0gG&pUu%^NGWk>gphpbUMw-MZ*R@IM>2#NJ$vEA!7EE(nD$Q>(1`32YeGjCxToNA zuZsO0YR>2%h5gXQwM924wCc<#fO%y$aJ0D4xwarQMor% zh|*W8Eo#&lKc8fCYlMDG;hj`U=V&%ji=bQ$m61K}x#(fMR>trS<>J2r1+=1+YoLE( z$)Iuy`G0bJtb+WnH8+=##|dI96J*3zu=Rv|L9=iBM){-Cv(4dr5*!3+k-w=ZZj1`( zora>R>hEk|4V@<=b83FH#!7H<#Q@`HghprK_t2rw_K4|*2{%E0}Nvh z*#~Dj>>+Tu6fJ)jgxBrGa5As}lL;iuib_?fvD~xEA0Kh34JR6!q6)mER)MH_3)kcM zVI*+hY!Z6cE;QRG=jUl{K&9e60q{{PwiT2IB(NKlUykwwflbBPPLZ4ePuRQ&gB#zt zgvdDV0+7vXk?|NU{4hX$FJiCocpT%fh&^O~4{Op`J&s(FXOco6Cv$RDj20LT3CAv6 zim=~xc)Z%VC{89~JOwOq=F={qI~a+BHO(-*?0PVhnd*QB0X+lKoxu$hG)7z#vND>i z-8RO8Yf%;0Twa2W1IIvU<{<~?aP$5iR(|v9ys;dtw`fM(-toTd@DfeIYHs!j({({A z4i^A6`w0879^BZvc4oH83v+n&YlTqCA=sY*HuehSj^?x3Aq}c^3)fL3Db7X@6)!*_ z=m6rSn|V+n+?b~tya;)F)-~XNSi|Pfgi&H)=KZ}Z%Oc~GCv<%OC?%d&)sEY_mdwe8}{$1E zd^=qnG%7WR-us624f)QYI?2d>JsVOy?+Ch-U+o0dS20mn!W-@e&JcCl&og-oJe?O! zHnB9C336S7)1mpMd3=6xe)-EG3<8CM*ppZ}k%x=X-Y~slV&XEP-PtZi3l!M;txk7H#+0odb&aM~9Lldh_l~q+}pWr#BGBdu&Z_tf952vy1jA zy~fAw)BU}7WBW5q@CQq5>nrGyMLbis>uubwV~@Kwp-LsvQx!B?)#@12lQO$<)p#MB z`}RClEu(p=qowL>drK9cl~RVDs>)fbf}W~Ms~Z2nVj@q~4ki(tL}?WHOUu|Nx4-;d z#{Taz_FEcNl9}lqY`qn?$g{ou=|=thdV2QjGxK;beE)Ie+>8?B*}|}3pfyFSU{0}? zr7Lyy)K_b3U8yDYo>*d1x>8GO#`YK3fMq`eEoy}`&9c?tKl0q7+hh&W%*w*hU|HE1 z8Z6$*fa9b#VGMK?hM?CEIn++6iZ|E}%6Mb-32D^v2FpSrZ?J4sasd4sm2&du!%Es* zIhL8o8EK9UmW}S%V3}!;piC|4kGS#4a0e6Wp(@G;=1|f*N_oHlbZX91Cx~$qJ&8P3 zcYhCS1<9BMlQZ4nyal%n&jJqVJ$VWn5Y}5vD2E$yYWe$+Dnc1_UmE#`-c%Cpl;LbvO8XWRe9LgNR zvD#bEtIb~qEYdq&I>LBzz=IX5=}LWI{~E9ObjKz3A&(T23ecG&Dv;98$;-(?>}Lr? zZR0?ez{X$=wOTKh>qa#rz1M5*&k5ETnTs);+X|5eFNbTa`!XD`ktSoHr5@|Y>C|8` zNwEQ&Uoc=}-JEO=*?8Hzp^%MQIXYwmURE4f@g9V1=*CG)j|thpZo(4Ol7wvVA?dYy z5VDcRMgb>D;=m16EtWy010}Ge^>jUP;Kl~!>hG|Pzr!{%4cmCIt$pD^f?fLmRV;{h zPb?6-$AV}X+agK^2FfhQg1jEDHS=uY@Y>!Cv4i%$qyC zi&SWXR8V6ov_kgX6Bbeve?V2QI}oO$=zBQX+QhN(m=Jt;@}LH#lu}gAV489C6v}~l zCN_qi!Dff)oWW`cuQU2{X|G|v$9x7S489?P3N6n=I^-vzpt_eh%_7Ljoz44w=5)Jr z%7VZ&lE@$_?$H%WY?1iHRrEC5eRFJuN$_OS37vAdT=U8Dt=iJ92$z@H^U5;b5A!Lv zzrC{C?bTgxcST@b&mEXI=)F8Y-QTOJNRpdoFmcUQ(@kw$bG7B>^rhzLjIXnZ9ReSU zST__ONJp`2TWG+`tT7p^hZ)7ZpLG(MMfTWxSUn&)6F#pk&mJ{Bpy{He>PHyM}BK+@tFMfNb#8b z_DJ!#&F#@|s9sjn4JNKRE#1_{HK#8(SIOjEO4nA*37xL7^x|>t^EQgd&38wM$FFnp z*NeyKTN8`NZEla--lgT{s(+GrO!u^P>$z21TL~s;UyW^lOTiS~Rx-iV^fTZ=gXKvq zJiHej)7%HZ*}=IgEOl~Wwvp){B7*-a$OXU%X=1#KI`!_( zw#m&hTcDZi$q%;ch$x>QKK<}war>lYxHpR-{P^JscN-^n3rqO!*8+<5W%Dtq(r*zY z)%y|MSx2f;isZiw`=l<>Cq-^$3a*iEsRKz{cd%vTk{5I(ib~LPdDj3X$jO5`F%d&l&9;vHM<@^-#4eMjFIh9aF7;LmaE+4(E>=Ghz8K7aPA zb;@40+U@4E7JgUQ-+OMlaH6%JH&q^qHym=W**Wyk9?rZCnxm+%(jkA@-#AziCQ!ht z1oKKhAF7-!{6o1P@wdY#A}k7oJ9-TS`QDfUvzF?)ED_lRX_Wlt&~#kvX$0q=VpsA9hf!EH}|=c6VE0VgRE6{(l{gta#u58?p+UW_I)XT-fe z*csS#3+7_!_Yn`Me7RuKuVWFwy29Ep^D))79?WKw`q9zEawbDd?3APblex}S}*_?LzQ`jo)NunSSip|XfiK(_T|JL z25RWIyw(tARl?I;UTw&^Axy!iMj7xLFx)Xlm2YNpv0?YILO~>#7vxk@Wh3EJkRPH$ z)SDgvDgf${h)ZE7lqlyqG?UKQqIP-J6carN{1NnN=unT@`pwICZ{@D{4#;QY`I~2W zEMQhZPV8oKMA@g+)8n67kKVldxp+G(4`2N}|5*9lecPN=ip>|rw~zi9jIPffeKp}TfIMv>atcmt+p}iizw~kux=T7alepM|J4L1B`eR}=c z>uT%OX=Fz|5F~5OKegtt8bhnASTi{Fl=)<&z~Y1eQLV0KNa5r)w+0Zc?_El86g|@1 zeC#OEUjay}Dr&I^07=obN*X5N(isLQr6F>Po2Jl^dX5{@_gwr};bB$fuwvR-jj5x7 z>9u0{xReS?VKq?9`crXOC#zY1YX;@g^HC;}T(CxSe!?8aJgLg53{?a;u`nofm7pRv z?UBV^T0KB4PJ->Y9Y}jirQ5`@X}L>;${t>+s8yq^JZlJDtDX+(j=60<$M-7=ZG3)+x?+=jY1rug}>oYj*?A+L@2yNM8QIjkp&hsIaQ%BBkl_Kv4UFPpoNWBjGtE^Pi zO0|0hYJ8%Q`Yq$}|BkQ!JHGz@$JamDc3b~{6_8)LCm?$Ffc(;q(0PTNQ7w{Lk(u_t%L$45PZKO@V~JpyDJpJpFDh-hdb{a!if4TxWP|1q&!6SEC4J_Ts5>Trch&=Gh3|q=ve}nO%3hO_?I&cjMCsVe zgK?s-cjhxMe>-~oaw|XaefVAYduDZzdmzWVm4|J@8Cp9N$H8B>KX>5|Ko%{}Cn0JK z;Z8%3oG;!ZrWQ2=Za#nAdGq%C_1p8yUpl9)v*x>t%hXMJPqDYv!O8 zIi2n2C6XNjobYjlZ0_4*3p5NvOXd!tSz!rgWe2vvlap%8V+)LXV6wVa+nWs@M41i# zYPP_H7K*>w0{>^ypl=eJv*=Ju2{LWO;BaI>H- zgnvmn%M@dTh7-W1K__24yi>K*q*zh#jyL!1!8^FQk{;eg&PsZCcY0DfHP*sAd|)LJ zyjuy_NLajd%P5lV@mit~kq}(l?$$3W)oNAwSm{noQ9S zbkrU7klqs}8ZEU?cK-t}^hukjN9*ip0K)I6d+W-;-lNFp=5H}g2hIFMM>B;VE`Muk zabKuKX{VmE!W1r~zwuKvHb*{1>?^hCR|7$(i&j^W0}#!^{Cj*Tnwi%%5`2M6WL_=@ z+YlW@`*OLIhj*L)Ci}AZ6K};jddQrK*fU}mkx_X0I{e&6V4~1%p@H{y#C^G?Z}Tua z+rNnZUMAaQo=oCVhQ_Vh!bbe2mM^pWEJp);!d|_*xHycrrcMc&c(!r*@xPM+r%GK* znMf50oT#~P5IDhzP60R_8>xU()U2cjPA9cm?Q}hGs;m?NjFHgzz%_sEOKSdsHI2ha)vp{QRkuG)6KVdGNta*Ru|clC43lgCb%>aN z%rb1Ky+T&WHI!LHO$IV+_V<2q=FBzWba6p9G|Ws(?{HW)7Wg;-&8GB!uKL%%X2A^=L^{(&{Ad>gJruZIch7$HGL4Z|bCU+7bu zGOw#xJ%Lx9XD|zMUbO*smh~BSnj17Bd^Cr>fjNE3V~Oaz4N1gm-6++y%uETgxiu1T zwMVIAgzcenh39qG8V+3^HtL!qvp2ApKL?2jqOhzf2F6zvQ%JYEnU2Ft+qs0&1LGr3%=Owxx(HrO!N#zF8|Mk zp9}JsmKQ{?qzJX5ZIZP0?Q&t7#QsR7`vg{ywfl1YyWZhaZcNbqp(`Yx^`dI1MzN@r z;GZ$WXe7yFZvA|D^yZ>@{wgX@X00%<9-m*v3C-F)5t^$@wVm*+?I=7~3=Cy8f?M|k z>fJqus6)Iehsd7#17l|kn^)1{C3GZNsFw%AZNNiWRT!if@|WI`1z1!$6cjh2D&P?A zf_8H@wu$y=*j%57c9)7cw$GonThHFR`+Fd$c%q6qnYdC82oJA4Qw|xsbU$*>%4Fx^OD=NJ?@ zo6d*0W%^j(mTW`FXnJSi-`{=r9bY?KR$Mp0IWXj@M@a*F36sz9!8}hVtDTk4GQ0XF3kaT{>6QHEU%S5Ie?16wgSEei>PE@;ueh_mB z-%Z`C@{PVA6C@o?P2U;zup=JAL2~B#Z02zZ_awb?cjzEzOplje7A4K$6YSO*jAPI_yNP+=C$;|Znz3;rm>~HUL0FWXjih`d&cWTE6^PXdor!!$k%m)&$hRC72v|6 z57OQ4^|7T)S@=F%>$n9v?;I;D>H6cDTw0-rYfF~(IM5fvwZ}JusqOCSv*5DWghSH1&;P&B`xU<_y!>xMlm|uUfDhtNFAL_a&oB5j zdaod>_l?@6`SwG)9~lPy?}~(BV@le+-aAnHFJ71ich!F#;J?kLQaf*_i2I^FJ#uGs zonqo~bbR{BWD1dl%}6T&;sd%pB5Q}=+}`4@{D=CV|M{=~lBO2_p@F|96@;#{Kpng4 z2NZq1^-!~UukEVQES5mZm=jVr&HestSLMTc<#0o%--=n;+;&xLSDm|TS4%&*!gh5p zGF>xTYfad^wlQmS47@UK>tW42sllsKsh3LHDR?^*DmR0dY=qLC2Cr)InPB$~4PMpa z?d$Pzn7z^{Ycm)ht;7sg(zPqf6*vXwL2Je>n1T8dfs!BGZ>jtMe@o>F`CDpD&~I^G zOs)_K)#_6WCu#3Okl4fJngTV$jAZItBoWZs>~_R|*8r5Dpkxk-osORBk|-Dcq*z&& z`b3w-J}(BKaRkHKjT9X)ZdQhApeED+3Ja1@7!5rJ;;Qf6AR$VV8eg2W-yeOKk|0Rn z6!(BX&*15hUL*M^os~UA~EjwcK6h-P%hGLyW-jw&`8GP0Kj>VXOUZ^eTLWn5iQZH&3jFmT1l9y}0>`D3>I%^?`fIcv%AQ8T# z?SgN+{XQL>#Ud9415VSrT8!8^Vh$i^J(~#d0Db?B9Is z=~|`hbIMkBoQ&Ap*JC`>Q^9&9uO&*o7&*u%i=!+o^_C8*ba8tNtC95R{M~uUsFhrs z2ls)D*>Lghsn^HtbGjrZ2)xMJ(wEV6W)Qj%ve`~f2t8@;cSh6c9Gx~wPN{y>crZL) zH6>3;(6BWgyNBB;z z?r;i%>NJF5f8l&)o$z^ipj#?0A9PFQ;)QOhj2~KT)g%8)!V~U=BLZ?4q^-;}eK!q* zI>8zIN#k_=5qO{@2sxc0+?<~mi&?&C4%5Ekfu^z!(w`LQPqV9G*`p64}CiFU{i`^G0;CuM?uYwl@ z13ECm)9=QC;?MWI2|y>Fw&>5%!TdYt^in*U-1F22JZ1(&zaJ5Nqh~L>-I6-!_~V0U zK#I`;{XhK@aT`j}?^NOVyY>$!pO4V09Na)gaLx|_X zC*0A6XxfQrpnStdnTCZUp2m#NcHCaUoPa_XNU+dV{{#4_3VpS(Uh04US9#q*z>U4I z4qp7v!UJHcpZfu%q*H7rySIt@i|pfJ^?_>)&HHu0@n^c9{r>DT zo3UOrZ@=W!30Jz|m_?62nhgCeNI25@Vhti`)m#KG?rDjBN1|!OG0{w8ucus~75;dM z>=ij0*k7(DK91tsEtjZey74tQU^P;=2CK!GnUOt0-dOeL^>*2Q)Z35y_M_geJgVo9 zdwc0d?s=qF#mlyEwep}X?19scHYRQ1zx?GdiV=utrFPDI$divh%<&-)oiFY08%OWI zKR>n)ro-ZQv3qj#_t8;warLY7;qSrC(WU=gzrMZo>tVfp@RdLO@V$3l86DmH+BrP@ z^}Dv=LoWS%JzAd+IqL`dF!e;sEW-|0sy2%o@;XvOq-B=pJl)dV@6QipA6J|@6wQ0% zpLkZ(8nd#wAE#W4SA4Si3o--oC45YfjQRk}^;^(vjY zJpDr}H^?d#@3~Y?-_$?!*m~)!JV9*7SQQZ6Qn~0TN!0Q!4JFCR>Rv}`Iw7rJEUAb` zmYUG_T=^A5SlW%eu6%CJxs&exH+tnC9v+q(YuOfW-@s;)bG!1Zr-$|9JFfhtA6#}MX z4)bbJmw{o|3)FG#a+E|<_+jDUWUmQ=xHKn%p+SfO+=4ElZg+btcEgTt0bZB07d;6j zBxPX$*Sg=AlvVINj%OwwP`gj@hHyHd){Uo_2a?nP>V!+`r;sU#$MIT@Kys;n`J(RJ z_;mjMqTasyaMDyq+6ttTQlO2)&1I+>Tt$1j$)rc0`2>&qfq2RPghWFwy2qYr{Ib{K zdtUSyOa0|DXXf4@CwJt9mpn@}^uK(@YI@-Pq(+X}Y$PQ;@lzeM2!AQG>re-5-hd#` z!g+UFY6z8Xqrv_XZ%$)CPbr|hNdd$iK@!J>T z++r#8`s~BW`}XUv_SKt@uRol%&(EQnV4xr0UYxu>Ir^aJdO+q+FplK)GpK}c>bK9& z*q}Y0P9oVA2AlRdIDngpVI~`*%8@uZxe1TM^8baf-l0XEY@H6r8@(Ga6s{k{lD zPxN(p&K_3w8Xj*r^t1^SH>QWUs6Q3d(Yy11@F$r2-{VS&?@y1MVzr3y0Xi)^XL1hs z$u!NICx1GQrd>F|`_mzR9NU?`5&>$MX@_nM%ZWBuz4>_Yfu2Dkt|xw3Y6$FdbTLSs zC7tTaAsmsoI*mOw=?Id-N0Zpv;Zfj~o*#bh29w!moTASIH|){wx&m?K3=smLC1Lh1 zOWvR_h&w!*c8p^fTUIJo4oTKvi^#(lAKtw`F|J{k20V$?h&GK6A1<=kqr~(>Q!V8r z(bW;Z&8Z6NqJ8nYu&{Cmj;1mYxo?7ywrZ-JLw#=Z$eN7aYQ6 zvuJU=FB2|yMQiTrLebNSRQUA1aFtG6h`a!_c2~M+r4CG?&ao#vxV&UPzMeGh4jvmJ z>RQa{;=1mou6us^ZI;Z_?dv3dVPC_@h4Ztg%U=$?er%!(bQp}B?|9n1T4;Hm3xGD$ zh)!2;j#nVkxljqM0A2&BjEk6`OI+R@Y9zrbrm219MKonT9}^wIz)=W#vGt7uiHd&q zSe{1rxWcoDwcf0NyD2ZkYMT30efqGFMkP zkM8#1N>AYmU(brMhPC0g!~D+2{}}G_ako04KbOi;k9C(nzFO3Vj&V9c z5qrkVi=J`VvhMPX=VXzQrk0vyS`zGgw^fE6^b&CJ zvoPNw=Uo!O@LL_=>V5IL(->7WY4@`iEyXTU3RJw-{GNWNgF2$ROPs;q@g%7#0)svP zsCYoxgA?j{1b;q%e{y>EBWKogv}MwDYAyC$E>&RyI?*d@<)UxuQE}cM&v{gS_Rrhj zmj|T}Uwwac(Y`r-UwR!5{x%JdUyo`(Ub`P}j`|#Z z2HZuhvl=vPZ?)N6!ezWWZsdLL`JuXYL!U$4VV2aQ`hb3T5KhNQq#CRUF)f@soO)Sf z9t-=6BtO9C44K8ZU*#>#KLm5a{PM@k_TP?<-O}L?hrJ*Dx8L{bAAUI-{_wYP^y#fX zbpL+T|9k2E-?z6a{qt`RfB7nUf7Nk&qwCt=4(hdDd})MveM+}s*{PImaU0sj#*_IC=eE)+0s0%~l&|K(DbL3+38X0Nl-kwe?=IojQaS3( z5SN@`n<~uQa?%>|O z_~!=j!MUL!+~@Aoyg>WXcK=vf5V8S7Vw2`3qOtoD+A=u%9!2{O@bDPN!98+&eQk0jg1Pgp-<7`Iuq#%% z+-TGuqHjMJvi`^F+m9vfe;R$eahJZG(WzT1AFY~8(cG_8lS<6eJWlQj4w#ax{0zd; z@CFnfVNX&iK>9%KYIX&%zL`|5q(Xr}9ZpO4GTd&=dZ9@fB-VgqX2!itaR8lmPfdzn z0Zx(7kGVEAwmyf0Xu`CYX3`& z3_%4f2QUa%wv}aHLorU;tGk(&I3h$%{0VdDy%yNVRg)zTkB2a~{@YvMUc70luS%7= zB6(7+Id!K_4}JRdshOo3Ob)@p|1k&SAa7fUe>rChiBIb7?;Ah7y>1-${+~~i*}!{S z|HsAO-c{@0^=Ahc#hatv>EPsMdu!0Sa2q$h?z>w3^ke%|ukmx|%hmgj#uhSb|6E$% z7BcL+;guVG(;2%HYcQPyTXv~g-kdk=7T8Lfy8~=3bmtNDzP8nwOnkSCLG5VS66GB5QSAMr{&E6)~ac^H3BC)0*-AQX2)PR2q2bAmgCTWYsD$iajABXmJQ)nkMD$vx1XNxx9RrLal>&+C-+k+FYPqV zyEEVEIY!>nN$Ip*t2L|!0V2Ct%WlIIyA^LYS~6^(f=N$#6RX#tC#QRO$*#_Vyf?wG zq_w&S6^nZHqiuh!K7OrUGsmW1tyjNR-nj7b;%gONH|ajvQ{R%h8Gz%Ec5#u@HrgC- zS9tA?FQ+t^Cf=Av`wI8n5`^_IQsWL-2boN0l?f;17@kH2H|qNS*Gi|*qC^xT&hNGH zEC)x=g{6OC0Uw5p9#7Rj@b|a3ZnFGhGYP?FRzE*}m5s@RCf;{{;dniS`pH8n_Q(?I z>ZP?@R1bC|Ye1_*W46Zb$UDeVZ{qTaB_~Rr9TXZVsg7C&nwuhf$>e=9a0sB7l=eZ_biMIpi44 zlZ**^4z2?9a*w}fl0}d$bH(JBeHD#duRHTGhZ-&JP^@3MZG zp>`UPlTS#+fe zHzsIm^juy}kyL{T)HzMzbj+DjVL*F*I;NAz;DJwln&N|0vky$Cs!JV5riAiPl6(@D zRUwg@a7BXQEGcR^d%-0w*|N30WhkZQa)|cRa>=tyZDr~Ry`z@Cvb{B$Mmlo``v%mR z$*~S}P3SAs7jT))mq&>hW9FzGji%$IAzx}F;9$V<$EkXHTCB_!iq0w2s065pfdj_%o9!$*{UY85ilsZ9{WM{n*|@`3_4Lpb3sxjV$IOYnPfVaZG4YOf4L4EJJP$r1{YMg03dlEvRPMk$p4Pb=#w~?s`HL zi#*{>s~B!C@S=Du>>+&Bt8$XJy72kAad1^m^mq zW!-VA8=jpmpE(M$ZLA)<0mxYPI_6d zIfid$B(os^8HdAhp@b4s3VH&wi>Gt$N8S zTh1oD5GCit-cZ?IF%ubOd)Ww{gdhIt}4x*NKISATPc2*0=At{zc8|UaXr&%dC%eC`2V(Q<@%~XuLs>%Bs$va z5s>Ol1tB`$sTmU`GhS#;NtR9BjZd4d;^va07*Zh_NcjOn8Cgx4XH;H z=Bt5BhAvOnU@(n_Gq!i|_;rXM{YiQ+_=h9ju;qq^W~X;@J8q(q?g*U#Jlb>rg!@WD9Z$CUW3Uz%MU3@(}J? zb-{v|X~h(gj=rFu=wfTS;Dd<^5THz=!R;EJLwuIa2claO&;@!;dHZDtEGiQkP5^HT zH{%!0&|+$Iaq+DTgFzGwuRRm;m+-MLwDSncyHNf$V-+0(Ugi+w_-RK(W|QoXyMz6C zWM=Bw^*on1hIA1C&46rvESkci5`+@v;76ijFR*98zGG|ak3D;3 z_7u$`#2`v3>Z%?L>7o&8afIsUpd%B?O1cL0{1rVvda1+=_C~|f1q9hOO%MDYYLRuk z0qg+ixhu++R=W_@Dyq2j=F0OX+{@`DCdQ)?3?tJDFQribNVP&^x!5;0O(i=K$_uT8 zkDyx9F$U_ar*Y9h(425FXpG`LYN`2MerCotiD4g|7~XPxMU=I1gQ*zazKbqBB)&23gQQ8f34a@1wpjNv>*nHl*!%pFGI zGl9T#@@alVvv#LFKVIk{-zr*CT#daOV>A>ULriLXwYB-q0vU-Ip7!~bZW;!RxS-uV zY5vwgb-g~!B6Op~T&e?`+s5u_)XYncN}lmIE~m3d9UZ+u&CNu_aP<5I-Ei?T<`t{W~D4Lk%AqZ6ihicN7DZ5u{oKnU6hC0BoXe^G~YRJUoC?qak8HXqd1 zr0Z*p*k)MFz5v1d)!B(FFGOQrw@ zy(qA<${B_ld^2HL5ED?HSI+9BKr}0PB_$=z-JzT<_Gd%o%qO+QpN_1|z;=($Pl|+g zJ8rM&x+Sl0^v3E=JZtRX*WeNggG(4%rS5jwvJt0Ks#|uov`Ly$rBbhMsJihymG9BKB_R-0m!WP#Rd; z$u%^;6{@P;bZk)eai#D@*XSGSgr&~86A@co=k!O`s_d@Q?N%Fqu5}vKQoUCEbG2Nw z-Fmn9jV$9j!N?g!izYOQMK-$D-I2&9&gC2chQvctmDKKAcqE2tW!K1)F~ z!+pXgYP`B5c{w{I@j71Sb&guL4tNgn$8+n#Lb_8@pxOnBMlELeZfP+tLwAJsldg#6 z)|ax>?nuKl6my#yTE{sr9Y3dO3enS}fd_>M_9-nsIhggGUS?&^e74f}U3*0R1TjrJ z>8^+Vn@s|T-oPas`mR539pg<)nH_M__?y!q}y&@*%-aC_L;wKf2 z{fYW-9pC$#@+;i#zWKj9BgPse5t`*%Uf9Sq_xp<&tJbQgmCeYFSIx>J`0NiWjm8}j zWAef3lSGW&76EG~Wv`1O<_yv6zVxI~Vzq`{uaf+?t-AeWQDSqewh9US#!2RDxsc2+ zi}I=yyxmt>Y@IPAKMFUrR8GjDr5=YJJ`N-{Ow?e{X!GbYG&beu_yzJiS;b4UIWDky0~mxUbZc+TQ@i?dA%yH3a1YQ?&9mU2Y3t5(=1;U%EWs+uaiG?eHjH`xgm8*tB|U^mbHB56W?Z7kwsB7G z6JIh_fU3+jHhN$WbPG}i9ZZ#12mN4Iit|B7_xI}(o9Pu5+L&V0{X53?X_kj^Y3 z|G~M*6~}RGtK^X8{(#)%+H)y^@W|Zc+G9&0+>>XF1pd47jC0KO6}M{+dA?Ey-Bj3K znd95a5^p>*$9F|r56bbq;E3)q9O2hK-4L&Iic*eAzRR`F!Bl!*rS)%?Du|ldOzRv> zNoH3w^be-F-=Ef5FFMEdwZt~hidth=UaOBc3sORbWz&xI>|Q z@r-iHwPqzR@tibwmr9-K%Z38_${l+>*D?dzUHJP?cB5k1m9pi~ZE2Uw8wlg$YQ<(} z`2Bw5McJt5pmL`Uope3XpfkN+)TD1evs#ZX+9!=8yHcyPmvznIt}QT8Ec&{$3nac) z5eO(LUbyH6A#QH9n?A8Hw#)z+d6+(Dnt`0K>KU?%ujjgHqnqWbD!`PndhXCs%k4!& z%a3}9ikMq`yH>MG5(u#?&a@A&FRpF*X4jt}(B=5#qHuJ0R4QD2*H*hE-w)`2h;NMc z_`SD*>JEJlaPvsQ^f||djciH9D;nYAc3inQPNQ9~V{w*YDp?%139r96+W(KXZZ}mn zJR1)HX7|TRpIyAu%J(haid$Gf?Y1Yl+V#>S5Od^l0^oI}V(;CF-tM!#2!#C6y+$3!-nWgB9>&@f>>9~_S0_BYSgXro{kVN(U~`xH+I{&LU-#*9s<94Aa^QkMRal(@7DrM=wH;uj0E6FwR_0Q zj^fazwN?L#s3=Gp#BaDgPF*~gw1jIvLSMw3)J5d(bcE~UU|$Uoqm7(J`6;z>=4h@(`c`JMsVim*Q=ohGlU+?tnAZUuIzO z&+f2CQtg|b*C91kQvFUtU$$qB8LaCYM@&#d1deO-gxK|aA|`J4=n7S#h?G=*j0<*u2puj0hkZ5UDX>WM{B`lo)xvmtZd?PS~}>JXsmX0S}xbi zrHyI1WkJ<vz?s?jkHC8U){XNQxbLUCWE$U>sc3hdZs?>6&X=zGbqR`2$20E}_~C%-wWa|2<3*u;addXJ3gtZ)O8nbf>0wwe7i#=BV5BG|hwZLncEU|y zYP|{tyHJTpnqUGiOcDo&hmcevfvv;-L?Xj3s#AA zQC=zKy)O_4nJ<%JK%fR$Hpje2%pE}2hH#jirP*Vs9ypy`hRC)QN|vE8%e2Qci7R2& zeqDgP&oQbDQxF^DF^K5BN=Wt=F1{7_-fI^Solq`y&t>eA06crG;fo4UasPk;YzET=dz z&eWK9pvL@vi_#%=gykqHTPiPE*;4CDOL00qRAPQG$LE`no1jQ+VgRzCO5d4w93ioLcC)PK8r1$y0JTK zsy7l$8o>k)+C827G94q|bgW|cQWcGPMTitYjFo7^3@ytk4Zp-HARd7Ssb%vgpQY=SAi z@w_WzkMfvUfGHA`H}NCnFVkjf4}DZkpquN%57xS=T5CL zcbnszvQw=yukdNLzV-)~;2XJ23b+-W-77k0M2I^EI)hWYm=O! zZ+DQqQvcbxir0ExEKGsB|L1^7KzcsNQMgnN{(it*p>zfDPohZ>4AY-8+a8M00k53B zJ$&~gdcd{^YkBmh&Kb0A#4>>P>@him)JirqLYn)-%8b*a8p2A->s1k1XYMwa8CCm) zlyf69*8JezGNbg2MMb0PIHj5v6;8R7r>IyxZ>vPbZ=3jYt&qS&En=3zMLWy4E zXg`!JXkI+uB7VJ3EZ+9x-uK2oz3^52xzMvdrtnq$u^g@Pf6LvYEtQwF<(6`gw=K1a z#0>_6S1eLlm+~ahoj%jm+4gL^1VnUCq)Wl5tk8%o<$Zy0l!GZ^~&G; zRCVZzvMsv`(&$Y>o7$&!8`<>bd(-mPp2#%^b(aKQ{T6fMm4za&e48Tg2S@ip?}y|kP05Z!bP3C{`=sCzp3PaKfv%mNY5q=mT4#Bhg*NAB=O;g^w_ePl=yYzZVqp?`Hs`hTFAFdtfMu(x z++h4`vHX7{`9em`p=rt42enAy?7XSy{K1@JSL%d)Iez_P#Hi<^6snm2C<@}pniyUZ z`TYkk97TNb(};S$#P`tK)5u$e37!j$-m0a17&v%5o!<7=cRv1o=*LprX6)T`d{2nB z-HB$`21#pEO4xvC76Ia85P6S+@s&6Gk3^6Tg80U#+uIt>x`71HBu^a_O9#dB0au&RVUGiwrh4eRH0!eLwnm+0ZLORa3W8#_#^Z27CHl#i2cN z9b8E?;uKE1VwqNBuC)@kX0OP6yK{e__EPABA9#Mkc%;V$5>X&)2X4&>!Cg73@S<*Q( zv38|#Yho+KVtx6{t{P<~z#JDh&sHb;Cc#~1B_39GcsXCtofT_Vg@>2R93o~{9lKO5H;NV- zGPPk=%)=8!Zr7g+Vc%U_=3%(wu@ru{^7ZPCq6j=g?U!QXJ7c?ZEJM;#c@`pR-71y% zuu>#x$qGNE@#BgzjEOW5k^&G+!mle=B#D1qG%a}n#SQV9sV)i01=kj;T<9VN`Bolj zshnz$Fk%Wd@!|CHQYz(2)252hz>TCl;8;r4ypj>+Sg)j|@~l|WQuov>83g(XF1=tr zi=?>vRIV|aPNr6JnYDEWhdEAQ+gz`VUzR)E+EMxuMhvq^r(@sYW zq^|Y}dQMY(*Qk1a8t@u-C;C>~-ZDAFQwS3|$k#LvQZ>q~1j1;h!`P?4TZv$oSQTAX zl`kXVE2SS(H>`P!Fep$>0KLv=j#MED^BE&fXKb#@?JWtfQ6R>)z}2crTXik1UeKMU zReai~?X#l~7Sec9@=;HMVCaSIEjJz3K{E6MGa%)SuYFn_G$^B{y&46`RK3wDC?whD zP#W^jNePsq0I@pugtH;s-g>Ji{X(YqLI)5;>RZ|xsNDxpqV!V{o%fI~d>ID+li5xQ zemUU#qk{xs+TML9AR_)TB0wk_ft3^x`{_NNe3G!3-%fd6Z4N8F(p`#gf z(=Im4m1fz=N!?7E`=f>dRT_t-4FI4(U%&OuRdcs_7?9IAI;tgMKx=+*1;Ssjo~{=Q z^Z?`M+GKAJGSx2nMAv`w3FDP2<(ge7+g7<+L}i~`6HZpI?<&paxp13q7_L;$h0}B{ z;lvgya=3W*7q@WgcFo_!*;2V1ISKyVGIL6%$%9RuXPP48-NYNy^8{Kv`V(XAU@Y2G z5TPte9QqyEnyfD9qVG64L%&09|0T(fv6TFz(DSa7AgVDTI$*kYVFnK(If6!wDv0p# zY7h*`Nuqd+4s#AOAwtRe7zNi{)wAzkT09J4iW4M&!rz+AwGXHKwAE~Ny&~p z>P@e%#H?*?wPLN65iM@wK!fO&=cvnh0!L||lF^xkT zKgQamr*&~sGl!zbCl;Tc-(v<)DP>LS>n~F_(@CkQgy2gX^Ul)xRi=6;SR-KTqAjQg zkd!mt*QKT;tV#7x9^?wP!vK_HQKGxY;S^b=V-BC< zI%>ws2SpVna3`e4lM<{u{~@)NaqeqK(}ftb)LwTPkk}XdCvG?^SrQCZtgv5@2##k> z5wv~9E@-Ah1JA}7G+`)})=O$}n&J`-C6UQHrR!eE#lSpASoYlA=3O zMA?mku^xKpy^=EJBaaQWY`*0R=sX2%a;4`J8(4>q9U3l41$P{1eFV3+fHJUN$lH?j z5^#t`=2?z!;7dx(SSXK+_``u4j%2qZZ%s0Vxm-_hC1!u{x(raB8Evw)eP7l`CdnP@ zg6v}7i)klpZ;724@qxVXf@s$Rn@(fnG~Z?TMVkAgg3VItxL(;9Y%ZO<&B5krvE8um z1e?z7>T@|2gL{};`Z4hGfv(&&z~uv5d|GH(w(B)*)~VcP6w41K$w1` z=X$8J7X(9{dT{i^g)y*2W+qWom7YE5dDjO~Ofw#m43Wb09b^M|aJr(?G+gdHE_(s# zlWB;eff31uf!`f~8kQDwKtPo<5xg-2cRZgg)ouLj8|pSVfx+vdw~H76lZ|0Oh;}qv zC?_D3iZ99HaiCK|^{eJ8?$MyKRrq*h`zd=Ytq4ty9*F7^mcm{L8I<=mywbo<`%q!X z-aVa3-)ZMd?Fj^orBTNDkpAS@6E)_8IUecJjhJS`llYA=(`f%;Dc zM`X?IyWz-lVLsgyjy#s}^p>ToYFAc+6dT1$*RXUY=K%3r6B<2S9YT+)v)o)`Rhmd4 z`Vsq-bb>11G*U^ApMeFWAoN>2jngg?{+N`CSy;x(jS3NmdKKtThP}*Kp3u_Bz0&Nd z2%QuC9G4bH(s4{zpj}cxE)YsaI3h;EQXd%R_(IRl{-A+3oQQuW7>%Z57;F0eSPUC* zdNBiy$mA>h92pP@3lg{hQ;s$QdSBow@)FA(wPcuK5XP)o!Z<=|G}EDj%+`i4O)u%D zfKETsN3H;7b|p3eZSeR`FxL$SIccjMQ_L1JH2s4q7PZIWi8nf+jExqFt%AlKg2H8A zEE))^67+(8z}=-y2F!8m_IiR8GHvJt8==eBG?v-~$8l9YPDpPGHyUhN3*nN|edeki zQQw6>4{d$DCt*>Z-V+(SB+ns-?D;0Sbl~=qkSgjYHbMej6H61FJsJvj;=~Ik!(`Wj zbS<&A(3G+rNt>7$A!(K!>d;v~VZx^_&xKmL{XVA~(b|hO$k#!c^p5_X?gxC1b7mxd z9)rv-&AxW9$_WTTAf%*fH0@${{>(w3(HP#)uKL_EQ zVGR4?+V6QfBoGHbJ78lKrGfXix8RL6Xq}Fg4{K{v2Q41M)YVge8v`gtuFv`b?w%2l ztN2*Yz}1gyxR?4|J3yye!QzwW>aq;DtGM`qAQ9ewTeu@|LWHR5gV-9g_vU0ks6GlNgPCUjNUV_vnjF$<$5TD&*buxGlbg>(I_j`W zW#rBF7Mry#b!bedeE50pNyyVZ#DQW0)p5*Bb?>1YUM8WabfY4KlCvSAIN*aKY4wq& z3YOY(V9Vd$q79L?F_L)y`5XFI0awDv9p6esLONpw6+Cjo z@zUHKIDT25o;Cr=4Wof)bqDlNClCnSi-rZKSS#30wNP^kPT8@L3Q1bBOVU?YqM`?o zk7U)5gY0ZVTvBONH?=*=Os=#&^2mM-ehn;K^yB%_@z1kAnIB40P;J@Ftn%5HkLu-8 z@${tTJUqur#)5iab&Tcw;+BT8Y9vcll}gdpno7G|IytUaPfCZU8+`X6r77;|;No;O zDwUF5E;ri8rJSZqF!|Dl%div9J*jI)pzYjo_R<_Aeq?b2Jy0RrBs?Mq zP3^uEqgv4}mr4y#+W+=-2O`S;wxF9X+$lrgXW&_*{6fmrd`(RL93MVlS$WPO;uFq1{G(%PU?VB4HSPN6{4NZvh>$a4byPgt$o(YjW;^ z#E)pt#myi2q6ZK>KL+!W6{KZi~?albLWZ;rbhsEP*K}5AWS+GO6F%Jc*C2#V# zrxyiY@E+-la(X!`oApd|y6bUVm66k8U+4*Ii_8IbN?JF8PvhKXl~#`f^_K3bi)r_2 z_x&DyWP2-g*+wx8Zgf#c&}b_01oSn%baIt($QL)BIeNx}*Yo=DZertewMg@&eON7| z+GC8XD!7SK#~zG9QV*C$+U-hAzSIohjn5xTCTl;FQacGfa6w1ZF3XyL1KZe2CBa>E zV7Ip>UN~?kk-1*f?vDP+j=W{gF7`4TE%{!Rhvkf67;)mx-PbXL{m>m>Vd6(nPSW{a z%*Tyr0?^esrnAv;d&pQ8+ofurOX3?LOKYu^u?7eni+M4WB+dO%9ek(7>QQAa4%1b0 zx00gd;SRpTacqE%VKP`JJsR$#h`om(U69aj#m)m=(g?0cuJXWRHAE9eRV6(D z-;gHJ;q5Ib)OrmB`(Zx)_!Y9^MyC8Tz7XB|glQf3BXww|u6)ULOZ;GICCyjrjP!Kz zfCgOInwc9Xg-EFyY&q|VQdc`6U$(d5ATYtKXd@0Rsfe@yyn%MxxAcPuU~9*-fwfD@M$JK1MC zVKCWZK7UXw8PI|DQG1Hk?c(g)j~DNql$P)q8G`VN`KBV1k3}3n&Wp*8n zl^Rb?Ubgt16&7fc>UgX=PtGn_ZG~C!{+*M05af}8yE^;t&Ke^5Iu9Of`oGW8ZP!aR z$1YlqUA5OqbL4pu!+zmRyGferA52dB6dYz;n4~@7#Is)Ea5fK%o&`?3YcD+vxjnXu z$)eT0WH;(UwaT!~Qb%DBaSW8?nE6q68i`k*Im9_$o69o^px{6xntK>vB+M6rF^Jf5 z32NM^8~Pnd2!yeZ?j%>rSaW5{tMtiqR@xD8r)T0>j8nL}HT zwuiQIF^IP2Euv=d)yTeL=;U&6U~Jz_U)LCJsZnS zM>&uk3O)lNfjmK|W3!k!ZY-_<4)Af62`);zoY+w3r(F0axJzs=>?c7-AeShHS9BgZ zu0>eEx3>`6Cp2{?w1@GQuy1MlJZ}giJc#?T8n4vXdm5BQYK8)FwB=?mB2yVn2En*- z%n6C`JDHfR?ojVHiN=EPh9%z{`?wkS%^k!m@zx8kXhsCv3q!_^7_3qHP2G508G)el zFyI*evE(#%XWUBYPN=^cOwx9ca2qytdq@GDjye3F0YAu(C3iR(yUF6vX`?)nF)~T% za3ujygp0+bPhG`^Lygy&Y%ltWIF9f)+Cn<6dEV{j-I3*vPk68;%K6P-lFB&4WDSk#1EGC|_fva;bOoA~}|4lC?@=Ow`?M9=% zzrB^=mE(gTpK#U0Cz-@Sr=eWLtsD)tt!0+lWK!m4qlY^B3Y+U3z0K+sLb* zd>n2Ze#GKD?Jx*pS$T0en!QIF*wE0<%!i?IQ`$Ox0}TfIGwlrNO&Ad;9Wq<`qd8Pd@(%<1$~$BtMzJPS(&k!O3Gp6ofN5t?cJv z4x13316E(yzXW@P+T|;suAaB27kknt=A_}e&*C=O<=Vr&JtXT0K$L7_`a4&qjNQ?M zdZG_rf9PIDNg_~(E;hc)Y1ty$%eZ;u>Py_iMmLWE#Pi*G_X*){J8Nhq&0ZQ0OE1d}ik(DgVK_iQr2|I60FkY5mD=)=9MZ^Dx%TFDCwQp@6w;eT*9lgaMozt0BT7P>*vwz zGfZC*2QX;f1%V5=h(~aBhsohgBeW81V0fw+sv5zYnVoTvVyH9ZnI#kyj6*H;-W7fT z7?A|@VtKDY>v~Bbmljpr!jU-EfgK5=b8Ie$Ch$8f7`W^ZrbREJ01Pck_^9Ko7!F%6 zBI)4bPSLThupQCu%Ek9cJ`&i#dS%w~Y>d-^8myPN8K#$XNI+ge>J30%G(QH(FEDY* z%q0cE3~z~8ceE#%&j|J(_k-&$EPy&oalKgjI|spNf&?b_(3QPA@y)(E9l9f(w}D#b zKJI+3B?k1M1wFy@SyGN@IaW_vrV*AUe*a=@z2XX(NZ~DC|c?+8%10DBKI@#q;5tjmv2VwJ3P~oVNz)^ zgFooV1_=Sch}#%?mu`0^lf;Qj1T07XxR;nL?cywC03}cA2utlsGq{FB{!GFp2=Zg! zW&0qh)BQff%0LtxQKo^P!T&G@hbMC7+{lYEBbx+6UqbwngsU_^ieOoyD{*V$+!PKj z)RB~QER$|UnzqckRAz`ZBPnOT{>sGKVnz)j*|^h)jj%=s0p3kj%MVH5vk4D$g}0r@ zGYKXcYr>fANt(r-jg%=y9Hn|Nm&U}Cm!aDe^YF;O1m`}Y3k7Bd*BZRblWchvJ}|<< zDBs{o4j7|T(KsYeYyz%~OeCwiISBCe2znY20if{ag zO^GkGnI;pipc!`cd|_Ah(mhy+uGfhSB&=G>k@_QP?vEN7cv5y6<;@}k_1vvg8y+4R z*e(^1@1Xuz{=uh<4E#5^e~Qq-nr#?E%9SVM#+~0Ps}TKjsq(pD=wRcqbv*OYN(lpOmp#f7Nrh~MfpHfDHE>YX1gY(^R~3Ugox~!) z(771uoOXPow=lygU}ay(vUb^+9L*wl$_Q-_xwr~HCg~rr8b;Axmhx6y0K$z*{b4PE zP2nIAV=2hVA$|{F4D1yp3A zzOvIiJ%U60Bv*>zlIX@usHeq*BVrQ8x=$O)5-wE@shO%HGPBcTCjW?CQygoVAc19k zxZ7Jhva&lgIhRZqXUXW~!>(BH+02{TedX`Mv1=j*Nfj_mZjACwR9}DS#Uou&o7jlQjUF}9N1=O^`T+d&IPWj`f?Jeocyq_;U6bV+0BX8JeN1WD(SWN=& z3{8_R1~G8ES9%o2ebW)*m4u{sW?TB_N8wZ(3$M0rGkA zyWPE1*O~Vywp5-zMP>ssh68sz4rYd8e1n+R3Za5xe#(P1_wSh3lyk(cHa1o#md@rv z%&T22RZ43Gy;5s-{htu@I`^H`L9ckM_InL_T?g9V8{0~XXt`9h8jV`z8OF9Yo=bh+ z+yYn|`Pb*IHnjdQ7xuZlGH>fqYDR9dKS$yJXzAa|N%*(&lK!oA#6KD{@vibalkpB! zNLNJRGP|e78%|4B(3VWAyDt^UN=}6dAIw&hG?-oewJ480(It!yt3{*u_oMW5h6jj?xgKj#J6>cREQlKH6S? zIY@trk?2XFRr(CW@IF4kf7(+oU#jBX?eX_vH0i0A>a_j(;^gauXhUygz%fsGvfp)% z5qG+uq*0+Lag=Tv_%9+vq0+y$w|EK>*Z2!5m=fE^8lw(qajyeE@%Hel+Izf5hR|i0 z?Q)Dia$cX_G}~LWl0%Q(O9m#UdWamV&oOJG@>zU{_LkP1Sl!`yz=OMQKfZpg*$H(H zoVh9nT$&j*J9lHRDv z5DA4edE#I*v7wh!H^iai*dlgn4c$p3E>!K>i0I}9fr}&yqIXh1oDD3frPI?MS53oc z`s$ClKbG{$1X$8W9C9QcZA~tJuEUGE)9^sPvDko|7BT1V@DACL31`1V`Y>s4)J9^3 z5gN-3sgJn}k_dmZ9pxk@Gm%6uL;dzO$-HbspUc7=n5_||f%3dHv6JM=HYuA)9U!d~ zabBQQKQyt{98?`#&s~z5z7K;>riPuZxIuLMVG?7l{d0T-aJnOPd%MWN;#p(+lB}uq zM1fc9D@As*Sjy3(Vw(H6Qk3{uN+*r>hDwpl=1Lz+sua~~YbZqxv}QkjCg8$%RwzY4 zdh}TN_o@`-q2%1FHPK8u#oF`Lnx0GT-~6J)+!i^{{e!M}Wi94?bOh!{$*650qkSMJYV6%_oggu#2^7QOc~9>rYl@ZGOF1 zNzmVT1>Z*A6?~mq?d~XrN@I;Eh4n;TR&G5~-mMmRQh;qusmDHcILeVt?2fY*vy->) zKAgNc|L_l3MdZCjyAY|fuo@}o--z@L#szSd&Qew(nWH9oh$dhp9w4dF9CpKCn%?a}?hXZIMovj&Ts%Xn0{~Twi#sCLscG5nuq2 z1U}($AS_c|woP#TWLS+wly*YWD(PB%^RbQa`p|@by5rd^^)cdpAzXs_ey5}qutB}! zA=ntqSRA13>nI*yvJXw{!6m|Um|6K%TFm(_L!wODI>yB9Ez8p70C7}8H$pt~u6Uw7 zW+LSV_7FsLgBmgt?5o3fB!m^=BR#t35DNpV)Q!YEV;*(+M!Iu?k>5o|yu`tpU73^F zv9-jKw6Wo_rSda9wp4D$$Ck>?{Mb^70rD(1dpXG%=GxVX(M}?n^of~{{a>b@4vu-D zuRG2xOJ(e@a}j7aW`0Jf%*9k0eq<#sI8AVj*)Ubk6S@u# zYZAX&?-jq02DEUZyD-Afbw&4sdG}nA1sS?>&Js7XJ2zRnR1wY1E4BM6O`k%~qp8+( zCDj{A!)YVBth;6zWQL1Zd$%i&ayt?T7kU!1$Kra<+sG#rSzXArktt#x@_3N2A9BzaMv`})FInj$-dzanF&qK?5l+6hLdyn$Jku^SU5OM+Klm@UGpwO^KZP0LPiuYoX^joG z(5;4>_F1BaKO=a#<6FinP1;l_-Y0K*y$8hG3D~_e|w=#58Rd zPV6vCYhG0flIH&HIX6PJeoEK(hC+44Y%Y4v?P6mM&$$rta@n*e^_(w$XO-vN){p&8 zJ?HD#;O}>gS4!o^^L30rmvZI#ImYuYU|voOm_I6FdT8YZ z9qvO84+akF;D($5!xFrZpZ&U(n*qDE+Jc=aIp7Ie$rP1Gd2x{ALf=- z-CzMRT+b#|Qrg15m}CT;%!!F2%hsHJO-kH%Jmi9k*@l|px?jBjQR&)@5QH%pNA#*m zDK&JI%k;5LVmbzm4g{oFXU(UKMb`^aKSAtZl4r`J7$sq#ogj`OCH=8?+W^TXd$BKx zF~}Iqbx98p_6~J&BKKabeJ>(Scq2A%nkPJk!X7zCP z%@lAW$qZ!bYfE}TbOXvk#DVz2@2HKfgkH>nzSoJdo(H3qivy#zp$9{3Rvhr3rM-*; zW2V-F(;Keq#+s2_&v`V9+};Q(GLs;|eJLbG!U3!)i|6vdv)SB9l7>H~?aMag-Oez$ z+!Okc7kahKl;`Db$=BVItG^{rhfAIwm&E1L%FpA{%E#r>N_;LmZT&s@ym&7_^Tg!J zV8Qn$Zm4PV>C)}v$7Z>NRe0nD{hm7` z9n>@^oYfrHE-5pnMS32}foQXq+Wn9%@k2U_ODnHfeZV8s>5w2ZO2=+*>ExaNDpqQ| zo=x-jnq%#0eOqZF0iP=^iV`@}g)jR)xEB(^gK6;S(zSLld4FemY49h7=Dg+Q&B@K3 zlkw-Y@^a_2a`Wf3@^k349^la-DRXA>yxgoGkFGJ^CPSeXkXxG?)fc`kqHqM%<+`x&*chIFXp- zg5Kp1rx&*VDnDPD|4McYiyx3+mLM2e>dlh=Fge!Pn@`Te1uM+J!;DvFuDmOMVfm)v zSkvJ!-S9nQt1)9R#faJOsxKWzEMV8-B{75{1$H2dSC*;8;IuOtil2-7>a(pZItZU} z<9|uYeoXwHu7JfmH_=FzjKO%MO5VoL6{hnTlx5oTX{K;Te*3UTRFOqaY;%=n|Y-^IeqgN0wjujBKf?f zF7Tb1f)*;HPO1WcrJt}330huwS)Dl)ik5^%WG>2t)d|)E7bMTWJde(4I(E~k<>w8M z=Kk#sFy4U1aj8__*c-5PHg9@q{1dzZbKhC*4Zvf++dR5;oaFa<1MFh0 zQhmPOfZFfQ8&J#H8(=$i`|;j@JKgyC`SI^}6VV_JG(Q_ODq(KEZQe2X&vU~e zo0AONP3#KeemwB9+L>U|fD0i@e^LfXY+!94L$S2N+_oXnuT9dglAQ)BbW5H3VH67) zI+VeYp1-+jY-}>q#QL{XdX46_lHEsh+gtp`KGUMpr3h}zzA-fK-`djvCW$9d*dPr- z(8A^xqt6on*L!0>ODOnaH}Zn2rnx_A>}RcVRIG1E@UUX;Hjn)*m5P;cJ zJDJ|Rdlm84)m5D63*0$3(pT#ZYd=~QG5&yT>)Tr{;?Cqw?Imp65}Rn;E`Agl5*sN* zX12TbEbQs)KTnraE2C{a#Od@Y%eu3uMeip?<)mhTIlnw?t^+3)g6*wc0>)mCiRFK| z*wfXuGqGC(HyoMyF?I#x4j0i#>)bV--#3~5%pIPrhNmNUL~D$sE&-yBt`KMd+k_>V z^V)j^fyY(5g48+2wXc#(g3po!{>UpszMX>YtsBn|d+Ba95+E;koyx&sr`B-UiBx#F z+wL6$1{R8i8-ze*UPxA&KO#&T#;#>TYUF}KsbVRzWvV=IcxPv(%OmjiAVeY`ie!_S z&;qV)mgF?^_czNNDK$%W)6R|L$u#$GUy)JC?Q*$Y+gK%EI-9qAMU95DhOfv*YtEAs z(Jy{ym9NOrkNvKFMR#k~_xg$)c#EE|uc+}%l=b|4MU9-*_mYEa^|8L9^|bu_)cm#e z{MH6){zc4HobYYtjMDnth&Gr-A9DRsbQuQIiA7yMcr|>2P!I2oOd)b_!goXRiM3yNNeGyf`M@71I8on~u~Jl=dai%nB;I7qnA_QAM^X zN#^iE2yo88pl9bF&|#?Snl5s)U2JT;$klR@qv>L$?V^=~>7upTcJaYzYCL#R*H{N^ zD_K9xEdE3%kJt5ZUsq<6UL4Y3`=ar33&}&7d=HaCVxV4+AY3<=NyXD4k1#QW<)X{y ztjy=A&F8Dm=c>;?K%sBtrO&tWQs`UxX!NbzRC+=l|Dl?NJ!PgU>vN4zMA3`c`8uR| zx|3Z@WPZhTJ4=vz+D0(!8KYd9<&;zt>=S&@GJxxjOKD=bz7yv%*27e3 zVMja9DBYf2cdDy$rEbVWkR^4hjfP10`j^_UN+)?5ZuTa=kH@~^nGj5 ztQ_d0o~zYkGqXo4C#y#*53@(h*gaagnLS!L**#iI3?H<|2}04Bqy)ODqlryK=qwg7 zpko%2VwD6HdO@blb~M5rjT8sOY-ditDY3&T61r-2vi7%|NYF@bwwPRuG3!}l9&U=JE%g{H zj4^9~6V@9}Jb@(p%KL(lR4!O;KwktQ8UcXp0I^0p3HsF985)rARgPR17{$qyp2pje zIU$D8wn^{>8pKpXH;!61(1UZVb?`yQR7v;8$jG9>9s~da49AlET0p-pb83V$as89* z7Ci*ICg;{7_%#MRjaZcg&wwaKt}zJkWAZ!mAe;DD)6ekEe&EvBbJ@F?0stROB8Ke^ zX*>}DH`ocMlC^7K+-N$CeL@{HOitqN(ymRif+M3D^^EQtx(pMcCmD;Gj;^*$nQ_cK zk3eUbv353^85H^%;qIh6B#J49h%XwP*C%y0j&uO7|AjN!jH5C?y+Y-tS*Y8(g`BNi z^^2CuMZ+-K1t+pgly=0b1Cju+fis3c-dSC(Fftboq#P_Ivz{yKsSB@*p24c3*-wex zOf(LYwjS|`($E<(I>`(~bXv#eF{82ES_1B^E+e2{#POW;+}PA!m8##uVa+PD7}I&3 zq1FRz-+*jJR2aXBxWnH58E%JovaR6ISYISTt@Ve)!}3X` zUOg!tp056Gan*Mhoz&?ce@cARW~%P#;No;O+NY;9(T(`Ck)27D|9sl zWo@7DyX`G46PL1O2(y12Oi5DrVAK&4j{e-<{eJLz(JofYh{3Sy)uK_a7Khz>5+`@5 zXg?DTX4!Ulm@Z@I?c6Swsv^HW5E2IflJeejJS%+E=2D#EVB&W-tv(10^PRu1=(Jes zC=8+qixmsmq48N~tz!`Ek@zNnHwl7-;Ms|pI6+u=;9k8M1lZ_=Z~YGYI~Z=!UEoH- zqX5!_A!*04rpJj>syj541iruVTmp^5iJRi;8}X6S;Cr-xFoirj1;P+?8tBNt3i-8Y zZQ?xe1e=jIhU)A4+goM`mjNHYnfM6kn^39P+yU>cJgyNYK%#j~XYLUUmrBjz%_S&g zDf%q?PIkJTLCK^wdHyWV*66ESuk9Wm$L9)B*D z((NNj80FcNjJ$3TBA-=r3M#Dt&~h;NrUU2or^1gkC5&Q9n6wT}+Tgxng$MvMVVh=6 zRFo~(q~2-IC);et$ZYINP9U|^?g?s@#yCG!Sknm6^DTM)?!2ViG1E+%_*)w6#k701 z`+iSX25D&xXGlmmJts7v{?fuJ%O{YB3h&r;MpRn~#XJTGEw=L(B{G0VF*3@iDQb z+l@wjKhe@9%Wz*i@|cE+rU*CIT&v3)bDH~BR&mzi`iljJZ$dPS{)<#p#EMtAivW@9g3B$Oxx%bhsY;bW} zU=_80o0RN9XH~J;~Ex!vh~%&T?l35!)J(7@`{T z+=~A?p+@By7}B-*bwSBCj_bA|SZ!eXN-Hrq0Cd7{zS*Ia7tdy?yto zAVilY@*(!R14IrDkswY&Q=d8xe2Ste()86QE@McyJWm>-$xG0(iq0&biZ~DhU&Df2 zyR_jD*^%kg8T<77@swpYPNPhO(ES(*W~_b#4;sHa;`)fltHFGU1K*6w?RKX$(L6Nh z>xjdFm4a~L7!fZz&u8Ky9XsM(HS-8a8a8F2T{GOWX2Eq-UI1anla0$sFLL(IKhuNl z!k6l@L{@A*nV4;mS!mjlo+b^%+V?X(r+UPB{k1!Wr0H2|r zmWAw?Ht>bCZ7?cGAS!$6R`hw`FV~_x29sG6%i&#(gm%Nr@Z^BJOx`TiY!8^>MIDHA1i-k*4p2tbt z-onN159y=d1vehw;u0r=>ADgibH>Y~$>nfB;AmhLF#6@!d*W>dMql6oxFYE`{)YcO z^gKfRw$*o{+m^0+vr=tVHcU&nZJ#mC-C^lk-LogPcgfBx7M+>}q}2;XPBabsY}48` zzqIY!zx>H=lsPG(Qnl;{PER}cb#6X>ixOX=#}hl zr}*ch%VrVx8=m)dj3KGl`D>axV-DFd_K=-BebK*6NgwYOCLsw505P1A^Uxd3S} zI_bODCrx#3l&Qm6raAp<^+VMv7A>b>~q5-I9%NHSADn?iiUn#?dryj*O5h{2=Dp|Kf)eM3)e zlw$&bFWEFfJtnMWI2G)G5_b*Tv&dFb$yKOHK~} z%1C!OWr;F5!fEh`cH2#A$-aLsq3o+?|l!uylgC+&+9dF0KE zb!lJ66SFyr^smTFw63`T3sRJ{>`rNxUY3rPj+?>XKSPxY=*7jKvoB)Y59Aka6x{@2 zufShB@Z#85#AM`h2yJK2-2Ym$HHQh1FHaq~y~BWS7jD^e4L=x3khn0iXrTxsFO#}HDUA0sW z_^PGuKv=riWiBYoK&+O^4P;5ysmcvwov~{#2Z)v6tCm_DVPPM9&oF5!AxkoeBX`yj zDpWRVnY5}opJO`c2u$UD9iuf%MoRlf>qqLx^H-{kmPSd`p@|6Px$$6YGvlSUfn$=( zmZYY3qpZS%DHu%G`LS@2ohcErvUEIM6o-IBR$?bjf)wXzO*!Z-$;LnlRo--FDs+S4)q{en8mLBG*^q*AnE;gL{vA{W>{6M)c%@FWf5@rbge?!}a6>?2f$FhqO2kyI=*_4cCnBGU2n zsTX(NTlf%Pp;K&@D|AG10&q!l|MnGTe1+wjeYlwkYSn!H^L>S9SN3_eNTX$EP^78(3oJ7(dhy2@{N`V_?{8%d)lW+ zLvL#DPIl~i+&Yw%Q*(pS$Q73AbleehbIhTVm&OQX!m}CmVbF-`j=gY%N$`3*+SA8D zk4yu?k=)TFkWeEg2jkFKz2Q&=<}zZ`yMt&_cTyO+2u*Be0)R?(vurCq6ire-aJZ{< z*VWZ7Uf5Gt{t)q>JSp&A`j<#9s7iagyW{C_xVJagUC?byv?wOPtdaWKvOQT^2AcCl32P)S%ZcaI>eX*)-E_^I^=E5AWM^;%$l z!B4%`J5TDTZd9P#Id+Y;yQlI~&oApL&|ZBepnX$6b@kD9$>o57RrTe7VFPTZYp&p5 zIUu|g2T5~}0(dR8Da3o3z`NC>dH}%7j%vqDSr+3RF+}GIk{ypVl1z1Q5Ok7!4W~kR zVVtN0)M`3+3tW9eB%6Snc8E%hNLFFqOs*ElDRWE`X|0l%gQ-4Vs#wC|DsSp39Za#` zGX5UZ+#gk59+nQB)Ai)#in+^?-0iqsFBVgIx#ENOle zhWk=%7lnkaNlvy=*5>)KIbkmj4C7EjF^Z)6FJK`cMF(xYKD+oJ<}cB{Cfs-CahB{* z%|vbC;T@StcxoetyqYQI_)1Di4Ib8wDxFj02OJCDO_@t?$>k=BCHX`I!G%ntA5h^I4)-ZED3m-Lh1>ae?y%t~R zsfC&xVd}czf)kVAJ?ier zL$dQypooPFWBh98Rr)9Mjd}bY29f{AS-#yp`>y}fhn;Dagyv(OUSjC^Tv0QvLcXMB zP1*f$#WppSOR@S(xX4(hDYsZPc`oI6PgZic@4|5_-;<8=P&n~y6bF=lq~i4 zSgj(o)JxJ**YgXj2#HN4ZbfV^b14!9R&643w<0!_yweS=B75icTm=9mXOg67pk#ax zI=&+~1C0xNL>oYUcAvr&?Ai7@8fdP5iXG|1u7cb)JbR$^x-WV&4Ulq%#0jkc&Z%g- z$d=>{$J(S(EV3Xc57a%6Nd~4=C_4y-E~wB{2gGh`VgUV8q(?5NgQ2hf8p}V}LwJNW zZaA47>rfoI#8WV85#gz#DhLbg5P^b*n-ysaQWJWV?Jd-rVSkUwr-U<(!zY>~P)O&_ zIuVV!v`fQiDzX<4CBcpD->}+^%De0La7nvud!Pa%@++*RBg+s?jf_@pZ{6A5ku$-T z!zjOg%;8=r7hV$6&8zTd&GuW9FCRLgl3(W7EIMmoQLo*A%^>zYDIPTxQ`{~1b5MVC zd?qg5T!_Z;o3rZGyURL#zP%MnhBy7u#T&e#Q2^||IeV8mar>jsq>;Oa#lwRitKa=p zZ`Z!d`8BgQ8h<`Ny{vlv+v@jO^QLrta{IIAU;NZ-eE0K>-Do=chfZtIxvq77etQJB zZSS|!pY2lh-C5)9$KNjUL9=>XeRF^MYJ2Nd|ND>cWjB$mcwQ$LPcHvf`JvpN-+vw4 z1VhJR*w@j}WeH{o-7H%LlwWJJh6uATdM$NK$^cEn6;Jg`io|;uoUP; zQ;16F%tIu8y zy%FLzB=iJ>nxP^QIMJi0eIcJK70wr026L#hBo^74qtA-xVNf%i7Uhbo#EMe#%^&PZ$y@go2K_4(M!d)9t#!7^E9(AgBgW!LZ1z z;h2$(3XZYQ44(tLbxXcF7b{&l+61A7^j_g*pa`Up0+(9h;$klGFTo7MY`uF=uqem& z@j;wB7Z-)sN8t6>!78$L!^!(%mYItUbNx9 zWWdI76vp3}kyo-z(F~CdfPMbn?)BKAM;9ytX=Ir=Sa#KZ+v}m0nZn{_q+a1>RUsxy z?z1sOHk?pnvO-60P`lp_p&mDOo2cZvqtL=|2W%^DZrgWIO+ z1eSJ4jjAeExJ+HDh%0e%a2`_oI7Qf^}!Cw7P5-^cgw z`hNEl!n05aqbqwa1vexYO$>36-sJB-Lg6R~Ew4RLe~^$+q=Ny$lB8)1=8&qR;obKo z7oOL;B_G1^VMhJ(m3WbUffSEa>XDrJH}((=Ey?ML-vd?VF8!l6g=57*aA`v#3uJgG zhE$32q-$UEo()ejJeWfL7hjSd+V@@WTBIeLWtzS@`H_RtzK~DD9KAv&Joqp1=uuc0 z{tKI-38vmB98BN_G=pSY($pZEiF`tRPP=J)3to*;a$jWb>|q9e%E%*;QSpHkMOScK z3;;kw4I2b;%bu)gC;{QDG%=B5WJefu36)27v%-yWH3a<5_LdBb2N42cxj2%48B|0M zWjozNvQ)&R;spvNK{^w>sRIfzm#hHcK>R2aOs~o*qfQdwO)eo{pgZcl>7$+0ggXxx zh^C2X88NCPFA=#dG+bJGf{y{tQ(u;oqazYPr_o_T^Fq^t#_I(3%kTIUmaY@tfEjIR zE+}$E;9OFm33LFDqv<|mrbtzV&h0A#Wn6cbEF8|_8H|+#RBaQ&x#@6($!;qSW@LCg z_GkJf5z}^epM6t`1XVeMurv-`Ojz?WR>X#<7;aT!A(WC|-fHZh$WYZG@j|Y`3!Y6v zQLBYf_(WRlo})GvOs0c9nyf4DK$s)wP8Y>-2xDb1Vw?NzhtdZYiV?eQiReg4fTzvz zv52dR$gAN}#MA*WWYn(NNhu~wTojr_nxtc@j1m^mQ7=?O!fS)Y9n}`x7mAuk?lmg+ zD$5n&0QQ-Um}a64;;!ori2T?242$EUb}RKZP=v_f&%8T3{($RnJp}PjhsWom0)6oV zT<3@)L4-gA-Q?QDaLqD5w&K~9!BF!QcRaqcN4p)m24q?MnR6|#!Dw1-1 z^*#Q3R)3EaD4cwpxo|w#|Cy`rGnWpiR*rTP^tyAAEK%QqRo08;oRyUCKsEQJB3Z1w zQ8iZzn5{f_8!!|d6>EhM$+GB$=1R!VmD~bLu`Lg{4OYf|KFV63jf$=M%_qg&6iWs4 zP35e7LCYqKxmmct3;YzupS&)ik#t=`%QOrbZ{i~(<4rKLAOgv^mx^xY2sAVqBd#J6 zGvg{EF+;8*);8t193j;WL2?}IFEvr*S`ZWX5sG38K2a$`(Av8aKGVk)?=aUi>N%jh zNTP9`_00^$ib%#(tcXO6#hD0Q)Lg`3)C@JAfU8I|gs+Ng42FS4Fl*`Ti}PW~PtNq? z_yuK)@q1JOyxoD_`-EYmeY8EjFh2!GMSDO7RrlrmZ*IUM(sx4>wjwq#Vh={aJafyD z#U=O2pi}*oHb?l(zW4~c5ma47O?Pn391f0pxBKFh(uxAEE-2fi5NCkCAnD+v3Dz`G z!}^V&e0Mk$iWyR3;B~uPOAibrbWL)pN{O>W+NhQz(t~|TUqy~IJRWF{+KgY51X5~> z(_$x*zlfD&d@OUL@-!8f6`D)rSCuBCyp=XF^($g?W4|I2F!w7W5re-Xk}~-#A~B=C zA{LwdWH8aT>d+IgvA7qG-MSHoswC3~rb81yK!?J7yvFgHtNP_BGyAtO?T{+J!s!Sk zz8*>NaZR~8D4CQgnN;V7@C2L{nG|=+%_Gbd^8zNPLqZYA^bC(XwhSY7hxAHy^Kb0M zsEAGc7=-9#aH!zqJZzfJ<=Cf5JmxA)6bj&P(H^7+F~g5M&l#{@Gm$ncBJLrnh{Sv( z6_JRS1alwP@{^GCipVXR9jZ%#b3E1MeaTgzs&+|O4VLUhS&my?CQoXUI?u95E=(Un zFyg4>Sl?2Nn^2`(5WS0?fjOq0PP)MtHf^ug4W?+aw?o2$rTP-WjrkysuI1YCMIPcv z9IhPpJ32wQ8djt*iyKT_Eie2Dwg$fu<~xT%I$n-hQ|Zr1`Jov2w7DmWD_GO)AX+1^K1~uh@k(a5*a}N}Ihmfok8VnQsythdQ;r#YMeERaGF7 zKzRHH?M0S}lPT)(6{Z>x^!@EEte$cW+eY8hA_8&)uNx;)CE|Chh^Xi7qvJ<0)V~d! zfPhYBA@|x2f~L!uk_HTxj7O@^iIo?!>-mMCp%k_fE%(XIE%6J@@70s~h=%vAVeh7u zGquSq?9Tg;;3SBnHO+!nTnA2K=!K)&N{*qGCoeXX$N&tmRn21BK*Bo&&T>|G_`olQ z3BxFYm7~Luh*@Z28N;BKPs-i|DjoeEjPP&RS>1#`mXXgHdJcZ>c)6RrgM+f+ax={c z=+>e^A(#0qPmfj474>W#`ln}gL)T;5f&Cma4H`LYFk4gz4|Ym@k2i(5kUMM1CVK}r zPr7;yGgTI+-tVBD*qNLLv;Dy^UW#|cIds z|7HnaQAl2>$ckP{3HYjm*wOY%P@H*gq~}r`cK!-K&ZV?zd03dlpBgF|=r!(D-lrz* z(k!~%Yj`@?alIX}nyE;e@-7buJ-XhU~gEAM3j_QG>6+jHY z|5p?p(4bZNQbh;GcjDqB7B*QbjsJK-^IQs3B_U`wFG|%Qv!FLt(gr>JGUZhUrf<;E ze5`1n+LWJwdu|jcGYKqVZ%FBP$e>I@BEI)0A&y7LHKuTQShn$V3CTBZY;_sO&xdl3 zf8_roG)c@%g~$l;8-Db9;C^WzR|~GMEUYlh{s?Ii54|rHuB*xn@jyFI8F(+{je-H{ zn3XN&wM4m>7O(aqIrb;75o9D%BZyCmim+Q0VMm=cHNB%JA}AS&R}pcU$G3iBa){lY zVS1l*PE&j~eIZ@;1Z%(ZQJ4W;4T1Jt=asLf1h}Ub~{p zDMl5bmC3$V&3)OKVWwU*ODhSUm1i)K%&>lTaEqB?M)eB*Ge-)6hK6rnmi@Id)a#)P zu!ri3D33oTad1h?)x{7&s7fVR2ZB)Lk>lWDI)qh$Utg^P(UK39a=9Y6X(7$Vf;PqE z`Q@GdgY)B-Y>jEjnMW}W)0f5mm-3sZ#~IJ1ILb!pamJ>FQD(qrrDAr10;W^MkRzW0 z)EL#H2s`DO4?XYJbqGZ=_7&DbrjGTIl1Q3B6wVzu!yB6jiC}sWeC)$rh9INP7||%5 zj>0U+R#o!*_LkknIPaKECQbX>n@U*DMJAX^@vRfra;cnMLdYOFg(XrVnvn+A4CQCR zj_s*J2Z+I--cK+DbRY=rL@_~ZjZkDbH3T{0I6OQl4mP7SSc&3@s?1~*cvZH&78+1DTJV|o4nBbJ zlzZ2|A)Sjb4z%4hBn&4QLq>>6N7hiLq2V;a_xFVh-^M_=4B3Z^f8B11 z%*aRl6sRPq5IdipR*0mE4DD|Nw=Gdnkr7wIT?Es<(+qZS+qq0&eM~(VvHIZ>u<%pu z>H}7z{&X>P??8h6;EZ?o@OQr7O~aIY9;YQtOl!%5LCImvw;@3)$kNht5m(T3x@afB zthYxre0x&V8o7?(ad_(18EYa;bi;uLGJBT53B6l{{ehvbAsWe6upNoqfUW5BHHDip ze3k$u$U;i|B$2e_v-!tD`a zpzwRU_jUjlD%}~j2+%8&?(9ti1-UKkTMQ)_?}xUFGbGZb@S;C-ko1@K#l5Q95xyNa z;xRMfYXVWQZbxzH-f}x3H%w*fP4fO@;#?%*C770c!Ss~T3K8^-wAqT>QeEbrkw?Bt z=?tK(;^JAB{x6-9lxHI5z_uUqeG=Y+XopAc3X|N^b z28bDrWXZ!7B@v^r$wNTv1QH4hgu00&XM#Cgxs$m)cT80dPih%t{tJ>Z| z{%3Xao>CKot7)E&o=(5I(lx_rWBO*57zRt*pGGtJ={Qz&>fdiH%A>GS*NzZ5X{9*Z`E_ExoGoepl z+#S}BKGmuxCkNHq59JW<7fG_G3tb(CIs!0VU&0z$n1bvi2h+`%(h5-0gqUgc5$%ie z9-`-|(u)LF(Tzcu45>r=66iUMjTYK(4aek_n(#BI9;G7eWHJE&VSsL=@^6^bz*T%| zEW*BqZ{2(?RZ!MBl-f%TAfg`B9vA5ujzN2MyQBm5y?$D~+lj4;Mm!5)P2 zw>S<0)GHZ@@*gzXdm??G?RG??enE)}H1vHCUHvw3M6}&b$88~M*p@jAP75*e19uQ! zWs_P;%KAd&BWjjXmQ!A+qu{{sOJvx>=iv^*D%12jTgoaD-Kb=6x=}EMJ5zQVDJvYG zmLb9k)^%IB6Mf0Nb$syxuCBK|Ce0Yra>(2zwOMT*7p`bP1J1B@8H~~LgckN?HVsJ#l8b$JFrQX3=xf0vTCAz&Jc4P7c$~#w$ zpyg01cyN!WiyUp(Uv*l!;EzP9Qe7QnFbwZ+teW@|Se7#s-Ej@STxm!PGm@SlYHHsOQvMayv8Kmc~>$6?ihq!_7 zcBTAU_0($K+sO0Fy|-UIZj?_L=Y&2KaxhN?I4jxg=3!2A;xk^jK^OUXpG#fHb;F!V z*MT$*WQHT}DIa;|J^MNL1}pt7)YFwKH=61rvpf>=Oi?CdGQKHJuvR+* zWDZZh%gQoZwy4LFH&3Qgu?<8?rDz~ard(9Yi65D38Aa1t*7khydM4xhV&CPa-mi+- z)c+OVqnmiZIAy4f=GG1ztkx}OVC}HV3k$|pPe+8#EZ>%Plw6(7%t8#FBx4v>L~^Df z=7T3=99G1G<{{r5^!j_1ct&TU<1kqvW~ z!DaMv(_ok7nH%%^^rUttk%p?l@5X*&^D>{)@>$K)SGO*eaal#|&FWP@SDC8mVkxCu z$qI|5K<7r3e)=+|mYXKEWi2R_GRWygjtAY5PlciOm3=c~g=4abiNzVeLl|p3+2aRnCu3+?&6>8!@cx1sazNnR%+1<( zRPl?LeJ68evZj?awXBiTEp6c`nrDzbmztmRI{i4)Z1d`$F3GO2tbL3{DavW&_7MyK zQc&$2$r?a-0niwOSI`zZ|EShSq*OgA;Z*mGzp>Ay%datdgbW07Uq4Wy8SiGU=V~N% zH>>8pR8Qc6nJ?znysQ_@-TI!uT=Af$hn~O{U%0>%IPY#-%>(%0CA%_3-|AOX%+RICTezl+?&?2zJxn8kdYDu7nPCnmZnAL4PMlUI$vlt}Ylbi0xA zNobtQdI?@%(W;|wKmy}r9RW88O}_gGQZDekh}n$Ef!$I2+98gAjxSSLtXL_}m2E1c zY{EG=lat%n+){$w=V?>HKWo@j#iM$DwSe8CsWKn3sf1yH-?qnc&kaZRa2#$tJ-;_M zeF03ovU2<;B95U!@|$lilPe+#sXW1>Z7P>5VxeTNNXpnGgfmQ_r(FEA5}MGi_iDCO z?4j*ZSdB*(PCd=D3LZ)CxT@eIUXcz!mOU4)Y(d8ZCxZ%IL{*e(VW=klJA+f4A;w7F z7fh$ZKG{cD{wBgQtu-n7XRlV(+?Q(k7cH|?Scy(&<+@pB9 zT=HDS;3E{8n4*M{Lk;a6r1{|D3Rm%Vb>?Y{*Acqg4F6N5S>p3Ce=Jg zMsbv+{Mb&%^E8$cc*=KBpfK5hs$`4VjW)8jeiJsPNav+=`KQn{@h?t{fK?n0@ptn5 zUY`PY-!RdrFLp!mo07p;)o6-wJnF|iL{~7^H)o+?Bcpy71KdQ*uY$NEE?6l05+9EE zC)y1-+E2L4;Ffqr$I8;O_zPrr$B^hJx@2B4;s`PP_@SS{wpy6=hR=SZ91jr%Vz=+n zPh|Z}KK>_Df(3BDK%)|cz@L$s_KM_dnT0BT{x9{4w-WY3IyJ(DUVQUHaA39Rgz;G- zfAAIGv&LUlyB5DnTZ45uMEx`4I%mV#k|ar-=y2;gtl;iK$Zo z78zs>BsN~Do4}Ce-(sVl-ZQihLN||y{j7RNjObx)Xx!7BOxt#`rc%1s&PAR><&2(* z?~ce5D9zeUc!DZS(>_r59DYJ9+L6Z*K(fl?Fhm-q+?-8&xYFj#t*|+lNSxVfi?Rg{1iXaP?KCmI9O%mNnbPlYxcfT z&3&oDp_()Dg*Br|3+8Tp;gHYgislmGF!zN|EgTMpc3T{|*Vp22P5A5UV2D`Lf#-tF z^vF7mRy|ieILhX=A`U>$&1vhAGUTg=uY0l5d!JSq<&6@iUKKU7_;kW3wmTLHqvyiO zzpgOaoQHp*Fv{gqkTby~AwJb~U-PnTxa{km& z_{cy%zT*eme}2 zusHWUc)0H&$R8^lYoXM%{m$z<&uwdV9CKmlWlKjQi)6%R*xVAa8R^QC%FS_WfCj8T zyweSB-0Cyy<=js}T$);u$@=r_#7PbdSz|p*YSE0vh63`?*VOXuLTA&(BFO$i+Q^w& z^{{a=I}*50;g@PG^XCE{Gc_wC|8wXx@k&i+r$={VTolhnci-!J9)!e8XmM5om++3w z~qL8%`b#tcEH1kq3t18z`lR}jS&fJDA%UNIB*{^xc>g&6KmYxr_VIn;=-)m4*LTL=$>0Cym(M%-VRPzq~#D@5ied4{I5#C>dX^pboH726rry z(1@c^>&BnHOJdb6iiJTixc2|?xvh2Jsp#H&0E6;id4|PUoT-gCU)6XnRT(0a_c&c+ z^HK~~UfUi|o4C;v7cCXNrnKh_>!c)pMI$)1MlzeHKrQtUu(hW-h~;oTX-Y>+b4f6I)n!}EN7TeVM-Uh*OZ=z;UT`f zh-1|sxrERX`~~X7TN{(DQ?i^Z971p8i`{ftR*Z-p=MF`iu+j6?gH<8PYy z_7mX}i6OG*46lhD1w`DpGUTaIBZm~`R8&R|kq96KEs`Y&Y5;@v-aXSiBO|}$#GQC` zsU3;G(B$Y;s!By2$AfxXb^@5B> zU(Li`!$}M9{qUjaZNZ78MKug`b8U2rm;Pl$vr8ZN1JCPDK9EB8A5g4F)HAhj?xCP2!jZ7O2axa8)LWt{?Q3f($6Zd}zrTnV^|oj%nQ=C+A99S4ML2(1`2mmfIp zOJ%Eiam@UVjTVj zaaO<#-QRd!UTOX!E(6gnjIsENymJR_;4i_nlW3@%r0U!crnDk3A;i29iB3V_9&+6C4RSRhXGYE(R5w zZabe++MR?_~{OQ`}jj6BTY>mvJWgd&HOyB$)XccSm5-GsXPryktu(EKZCix6J2C_KZ? zFd_vuX%gr5neS(;AVR*UJ zJy7ulc?TGTE^%NKs6>F8qUKTi7zDdN0kP4<;Hc7~c7E}ZzSnec9(=rjFOO=bp4W*Q zs4RAl`SPMCaf4CKBc8@l-yhL|f%C=d)gMa&+*Ej%#jGG7t|-{=aGnEsTBxBm)hm#@ zMxLGk*ULw%GjO7(i#lQla2f}JwnL+!Glcy*X}mjt#);%RrM4Dc*gm?q;KFK-NTy*! zJ^HGI+PRVLx5Vu2rwzHNqtwI;ab@#oy8JfPKZC23hqfr!4)A0Wpq)e+R3@z`_512+zP4F{TGSgImJZ@XB}qYpMZ z5tX6cFL|U#gB0xvVrTbB17(3bRIKX)>35*JFk#FgeSNL=K4nGtQ zjja8J&ILbwx{UP<`gnfmE9fmwp@zi5@PNv^zlRb2WO1jT7JAsVdHIHNTdn!dqkA4 z780|3Vr=ewhvJ>2K%?-@GxYzceZzF}u$eBeu1*@XF=56%RB4!Dtm||FMPVeFt9UMg z5%QD8CRu3@*veoCGHA?K9dB~JVP*N-1#pM=@Ln?4@)*?2Az#dTE)HlYg{?$mq}<$> z>f$)8*NXX-A~=iYEuU0vSTlAA`Ql!(q-&LKJ zpYedWf|R=VNd<^kIbjhz#0V05I-f~hn@S+%Z<3QIs-#G6yvcHM-VCwZ zvJ9h?QswbUsnVg`J1!>Dba+OvMjBUTp)4Cjxx=}=#$+E*SEL@T=ol#|(?y4pJa=s9 zaKlJDFQY4xxKU#m3sqOaL13LTFbvubReD3ZlMZR|(q(Z~y8x9D)elTmMU8;NG^Dum zTC}Ijv+NUJZg1WyC?5*+8jGB`=$o=7y?sRP}tlOg<+~YPtL1hXwAXE zwOcs!-DY>>M1Ce*grOHTqg2rHg*7yULatu3RtkcwJXtd%Fry>DTh#7G$AHmsBWr0$ za$pofijbMHFBn0#ZdI#=Tvj(8KLov0&Xvl##gT2XTbnn8nI7-b1$zVCfzF!++*+EU zx;^{!d|s36fl{8;C@;sU_dDE)b|$AmuRa*YOY!cdIC1-<&%(_Y^Dl+dPrqxt6vMkRXfJ!> z4VGli!0TZb>jWRDlDVBefg5)OgDK-TMqscx{khnN$1{*Tq&v<4MIO-EW^*PU_`>RN zjF-~+{HjA{=}Hw~=P7#6g?@i(?>tW1OPYhTFZ(3zNi@4lmwEnQUC0c*1Gf%7YheO_Vhi=;)_3&q+(lbgW%`kIX zF$aqB>mH z9s|Nv&mOw|M2&O%819@>w?{#cK{sH|U8MUx7pCvnIzCSG+nnP2t(&GSQWT6B!-Gp% zl7RSviMSOiAPVT%JtlmK`@0kVDr8e|BgsIRY6=nth8&D#C|A?=*kv=R)2$RmY-;DE z+PK<@1ZF8d9MN?+&cmP<1$2;AYuny^A7l4ap$2Q(tH=+Ll`!v)qM9Y)gJIuY()UP6 z1zxueS|>@CrhAR7A1V`xWtERO*Pe~RaU3P#Zjtp&P0`rC@jTFln{?jc!UA!DC1nL_ zb|e8A0fePbl{QD!ZHOoriz}zNI)^xD9A?fN91Pr5Pstj*;bi;c-XQRLRHogrC2m>K{&>E8l7ekq`3UYUcn)TLD8u7a!yS7o7*K*k zg_#6Z08zhGUmPUy{f$G5mdzHIzdGwr4>ZdcF?&MICy7fe)C#4PM=|y2>j0A zy_UvnO}hztU0K`pV?c+_9gA=|XCdep zF75!4awx&&@AjGaHz@_CA`($yDk32zhDuSct;SSg-(lOX?~h>TMZJ&obp$JsIY?FS zj-~q#_L{W0J&Aq2VCSfLOH!nH2dPC;xYLdCUp^7a;vTs9G_ z4B!rY7+mY-wLQ4yssK~kT&Zj&sm)PyOSHM^{yeEp*XqJGVFyE7Q={LV{pc4{FJLui zBUjYS>^d3hIjgX~M`P}c#YPc!(6Tf$XZ|X7Z=A08>K8-T3=KX~U7j5p_0#ct!9eDL zLnCIG`|XvgZ!q^ed-`J<#Xj|YZrA6S$9vxtdLF(*qS7-1#)AJ8Z2 z&)pLf-R;h>in6P-`*moFDq^vP~;b7$7@KvO87P?@w!GF@&!KO3zP)h6w8`*Hh4p!5G@>H#|R0Kgy7gwA1pOY0U>mK zr)%GNTzVX}_;7+qA@_PA^8*|pBx&FTN{@7VOwy3~iu6h~WVkK5vh)RO?%P|G3P6ET zI&YEtcB+0npDq>Yf+?kicoL3{8*D=Q)1|zX8(kas&YavAvVO!dS=^@Jccke-O zg>hg?;olqNo}#=-6quwSg17@qzIDrvJsj@@Gua$w1|=pr zvALya`Tq^Lne4$~t+-wmT{z?r0AZwSNM zA#Da%O?oG7aR=K5-7Jn=K4wBVCW@aJP96OzUZC>!5G8r`^$4 zix1kgyqWljix$yTL63cVEOsVpHw}A7q|@_O9x5Au!speuWeQSP?A%THV<qXkR74Pu@z zvNFv!bD^NGyCo_0Xzt*`Y29KWT;8HP-4&K=H1%8ocj$I%$de_%XXRE0tdlu!Gi(#wvR<+D1ieqHlmb zH&5d@zey=hK3*E3+EzSqcvpPZ*xgGJPG}Zi)ltt8hbsKk^V++6+Nwt<=CHAYz=j6k z^OwZTi5a-Ba52)f8DKg!n$Ni>?{k`d_fjnS*2Hgg+&E7ci@8$T$SpmpRPnf-27|p% z42HCKk;Ae~rz_2yuC=}O$~sK!pJ&h-$lqE+Fj+8Hz-`_6Az;BDGP!RiItQt{?X95` z3|;4riyGXyK8vN&qF)#~mqZfjkw_2WLHaj-ng)X*+W1#SG2&G%mB|Mr5aFkPMKAd7 zyYJ#(h&hSj=|b5kgkQi{ynZdxSu0+7rBf;_ThY$sU$R{O&6pg1j=B zcagdcpW}ReqPKwI>iRcRZ^R~2LQjdK>Pe%{ulkp8y1p}Mclp%7Gn>v0G_lF#Ocs%D zEFUjYA_t}z5{MsOLA(t=fkq2g!!EZ4!-gKh1w=pZ$*wGK2Oe0lA=+ayM0a)ht_~jx zwdEGyndym1=-=MCZM*w=a5L80qgzLF+M~E}Xp|A3x^7*|8GA!Dx5PM{*QX~ZZ_;8j zusi$FFIejeEkkpHCVor}i`rpf#d?;j&RN{l9gO^`G0c7x8AH~L(lixa35VcD(4JVhIaVf$oS0uoUW@h@F>PPJ8e9*s z#Jlk_EEw==#~v`ND@d6P{T}&lQBB>6n{BlZ?cZwmqxNZORQm2mXM7k~rB%!}n9HmV zQY3F>G483#Tj9d3X|?Urf6~U@Tx15XLe@}W#QFBybEywCZ>b+=$!%UJiZ9W5>$|4S zHct_J>w?NJ8ZLrW-L_+=sESlb+YVA8dMYr4f+hi@Q8_TgmJ)KOBb0AQ_`>Vo-2t+w z6BRSD(<1^h{FFI|1#}ZDIT2VR?cY6a%nQ$lRGq`0HQR4RH1$adhcZ*iARj+i4cBf! zh+RIKA;<)eD|ZY29Ms<&pNWe%7ou_e=B#@4?y^pwZ*PT1ga?Y+G zW*>#2TrS;aGJTdm{*<^#3n#QFy{DyQQ^*%C;3Db!CogbiZCc=p3qjN7SMJOL6@o@B zI2GNb6HjJhvJ<1u>q^g$VZj&*pklg-*Y>ysA_4mm7eN7HYE$)yp@tC8goW5(~%Bb zHgEyAZZL*s`&fT0z8vtMQkaOmXi*mo$1qB{&4!~F6z7see=AH#;Hv-?;wrhjZ~-&!y-Hq7g^uuG>GS)RCErfRV z9#vc0-Wp)(HhSJy?GuiZ%P|i+74aT&V z1eCJ{}z55p+YFib_Le>$C{(TVQLP|s0>|I_0u zYKEa%+{-=e*o|>{Y#v&Mo-@tmTpx?NW+_^JmhcZg#uEPf5!Su!iyye>;&1W;O@8qD zu-^E{`}lF9l#13JQ5`+1r0LJk)F>Dwr1sa2EWK2CJvC43rc}7TCqv^8BvmXC(M1x` zbFwZh*oTYARITSiq`zPlA1A*zCDh;Edh2w%UJCzq??@r%5#U0;j1f29Xh`A>P2+Z$ zUl+%FmE@oZE@-kst^-LiK-~Yn1fAQMS+Ax)P!lA}o65oN zBlPG#%vYd{5O_Pfx(K>M;+?-s2p~)-KnRjAAv^p^xPd0{ZkxWwxfPPk0jb(;di%aO zr_2(Jzjtx|u`~)O15|VtkvPYo5J1<4U5}F9+#vD|n7XmZ2(0T#5O75fLLQM@mug>P zwu_%q%l!TS7rp9Y>coS=Xb@wRT%4Rbfz8dJZU5H?qiaT<+}`VH61)We$zU}5%k$Y% zu4L&}AwN$ZTh!0og8^r>Hs^@RM@60=A-^9BZ+=KL&x1EV^qs}N^oL#L1>LmrnyH)U zfyq8ezvuTtO9hpl?(yHdZ)If77TRYI#3p6j9yY&Oj2bNXOEeIR>1AfKH6op?n~fbzdH zJ0zcnlpP}T&3B^Hyu+O%!+S@Ij?L#^5v4&7PJ3spI!|PONMzu1O}I&Z3oUN6c>&Vh z!Bp%$?NLs3vy6!dNrAw^=DtX$deN#M9j$2!FPOOhj8pxmY8A6UAB%yq1TN`d%`DvaJtMip68Wq2O``Z*xMB?Fnu zl0>V|H{pcJM=!B%^AJnYbK%oYBr>omt#(A!DQGyQS~#C^U2!onQd07j#Ck#!wj!3u zSb{4AthJC0j+`{OQ$Qtyn@I}`ZH~PW=BE-c&+2-eNbu?u{ToM*wVw$ zb%IG0f?2y|y;KL&AaS_fkSUaj$eS{JS*7%qPKl|QLOts`O8F$+34Qfku{WaP4V)pS zK%?Sm_iugL5h-!)d2N{t&h7==WJ^5!cGvm*`W_m%hc7bzumOv33Ns@Np%>8Gw=TLJ z8cGS>pvD+Kr|xRS4drcBIE*+Tvi$%1H)UU?owJG>c9p&my%Y7asUv)r??lXJpZ(^XZ&-%>#<%f5 z{BkRo%9?_IR=qr|AMG~kSDzYJmz)Uw$qs@c6$)Sc2v?L?_>58w=vg16Y|p`Gzn=Fj zik?r`U9V|(KaJe>O3z)xbNK3iy=39z2g6Y-5I8HJU{3}_rLQ^>|MV3mpW}4$TyS>Q zGnk9xv#XWnV&=I`=R#Hok6m!vTkqvT5mn#!S}x)$V|C!72PqSg^lkgjc^%k2&*zF- zkqt`wxvZ}lTbL2^VRK8we7rYLD&wz*BS)JIe0TEKU-EgaWNAf9GuQD{7}#BMD zeWy5+L{8nE6GF&kP17(m!^E@$X+?*wnze!mMi@kh9yY_mMKH{z8RXgLLPi{4;YZ2& zY~pOh`Jy$+uAaBp31*ZZdEHxvT%qBpYQn$+o@D4^B2zkOcxcF2lyjiL5e<)!`tJ6Y zf}j(aU(=8#t4~o3Dh$S!I48yVp4Zp*L^Y6Q0gfd@7RIHww~!#$#%e#eWA4Vp;Kj%Q zGw3o%je3T(TrDCClU6-4^6wrKnZFqNHMk;d zQh$kGg!NClU%8=1%82FSU5FhYtjEHcH-;LGDdg^Ir%+IBpUU8fE572ioYIuqX8CPAcTLl z-RArSdd?<(WPHl$-#J}xfRyI)wmwzK_Cusu3&w2wk9>4nP|XD2ae6+ZD7$0fwOS*J zP~rT*f;2EcW9W3l>W3cRUSHKhko<}m|*3~30Zo1;R#6L^eOX`mz|afdfawk zS$nt#6m9N3K|=|v5v~s}vN|?*a0i#PVoID=k567%hvW_W%T0zNXXWFwgYysYDSV)X zv9;qDcajZ`>7sF$c%2T8>wc7`jSX{s14~1$8XQL(PD{_d9?EQ($c7GCjG<{*8oNJ6 z;46(0zH77BhR$>3)JmFaS}?5k1alomD(PIPMkP@(t|Lb`sqZiWEiC9YLa!5ihkRSM znqR6mcJILE#v23{O=YDVbK5-U&@=a%ma@mJ)!SRhzcd=qhLRIQZl?CvkGV+y#55Hj zFPy{ga(>P1jmDqPPcN&ZpUvv`rR#j)l(J6#bWr;Kr@PaU^QL+I(H#C{R_|-=x|#iP zd&}~>pS{-Qht8>U`g`}cTH)mOACBiCO~v+@C zn|n?v#eKMDZXB(30}nMlhTydQZraofX~WE?3ufBPni_mGNoEa;>S8i$!PLy`IyheA zpkA!4=jmHClgra`e{l{ivQbA3Y3%hGCVKrsL-I zqa));%fU!3y|^$PcV@T68~@X#<0h1fJJ+b1Vefdz0QtjHc(c!ihIr!?-fYr@#6}Jy z@%+Cjvp5plQ#OPw9hQ_GQ4vX55*3k{Em090TN4$LoIOzyNm~_Eutmq=Uf}dDq zTU5lfbs?XfXJ1srVhe*`laQ5B5u4i?6_J#sQ4z`68Wl0IHX0a8M?2MAMCdH()@>d&fJ zKG*QWeJxAv31p{f~ z+&ff&P$u~W-oS0)o}ey+sSEu-l(!x~FOvglU*IZ*8(eaVQ?L&4eYBXWNcHv>m#M~^ zgtGNj{btFgPGC5~kFv1jWtJB*?GLO2L>)NBZgt_#^6h(Acq}1|KmgyNa?5I&B9I1} z-ATbDF>qR=u4_}FDQVS(zvap|Va?!ao`BVjgaVR-CTAp2bN^=~5P46vdSt9`B+Q%1 z|A3KD!wMH07zu^4@qm$Fz@r7Nq-Sxgk24Y`jsKsKu)dM7lBxE`+Y!%&4tH}qA_+R& z?XCFE4h;sz`6s8IOlk~oq@b`)N1tx4qLA9G)RYaC6wc2G6_p3olnIehZ!DU&91q_% z+yu;=&)P$P97&jlA6tb`UxRn!v!E7`3W7!I$eaO#05@Bl6$v$!se*n;HZ)b`589G5 zD;1H*l25CPN_SGz4*M7%Ls##71;P)Uj@^IlgElU8hf$w(2a~Yh`Es_jrtUE6!)7vOwJ%?byh#)OtW4Y`CRKfHnz%k0nnKWtg(CD>k5P|(ZT=O7svasdF zu0ODQpjp8`F5KXzmbKuJ*Rb$HS;Ge|xGIrD7Xxe;qg2ofs3`l;Q+@TW#sLtj$9rOz z(nzy;4TQ8-vJ~)Pc8#O(#l>%(R$$EBfnL<$QCN&pbbCt8WoEDISj|Mw#?vMmG|!@e zrth4s{&;=Gi0dn+X2kUsE?xYj8VaJFq=mDJ1~Dn>lavneTu6PV*7I@d!{*GsM0CHs zh4pRQ9zpK|u&X2Hr_2k4{YOhC8G0$lPrAE0GLR?sIRzg1ciRH=4=Ut>c(`^`yrQp{ zGb4YPA^kF}IOGH^^>>M#gcm>N%hS)U{0bU27FER*19qQr88h+zW@5j$ydi22!XNho zo4us?abyGYz%N5q9hl|FkyT3CV!ZvSCuOI#A)7oj1ow#*0gtc1CjY+?Zx3c)akboj znldpvWe?%)EyIck&}qDVL_!p(x!iveZojxU5izvzsb>PVN0T-tG~$VC%Y^N6B)$a_ zuVChNFn}$+kj?%lkoei>LT#dd$~npI-enataa? zOUI%K*EBRol)&T_m>r0S3}m6OOcIM8(*)ywkc$F z19{>ox!W)cg(SIc=A6Cdg{ia8g%@^BZkz1pRIulL4q!014g6zBZW}{SiN#QmX8;vI z=5lY~YvRQ7Ze>s&q|MOd?C-%4x<`IdtOJAI8${s3P*AklT^04D)NN$T!I8Y!c|pF+ zo;_|l;>F)zkOF150t}Cm|4ZSz)r(wi02*|?U4X?$S)}g#~y;rLy2hjClD2F zB>`k+%f`BNrgH=kH}|Cqpn9!jtXN}njy%A*OO#}-cxWyaKyzOB1OjN)K;+6&XoXPe zqlM5{E5!c9O0Rxeac1bbTqw?p#uJLO$mUoi&Ylaa>AK=95iZmD;w)Q8i3M@Wibys} znP8kU;Yek&vC79pD_JKsfrw4JV_itqF! zMIQ)3RFNJoG&fOvFom?RK9Bpf;uALaoB&^UM6L4x;LCal@MW!`2IZ@VS$#E1yJ@Xt z(M%=<_?kB+hP52n!v+IoF+@VZEtnm z04;Eos>syVW6oPFJ|^1C1Pd>AxcW$!Qy6xkbIxbgFQb$Kt4eTsD{NuL%o|x91o5@q z9K$YTZnuWVSp+yP(tM=P-oL@4q-vc(M?ee1)J<%>HAGbhlPjRjvBP5QIKc0qY$M!s zlj@}@m@`hEoGR=viFukZY=P2iYvbX_SN)Y0+)y9sM{Cc7Rbo(*%Z_ql2gQsO8Jx_w z<&ttR=afi`4I?xh_gOz6hlh_DsS_nas0?IolHvIm=NG0>RdBh!9Ve(bK2gTw0Pe%` zaBbMJqhV8SiIzuczeE28qRE?}LIt#Tpr{PV-%l}BA=Da31$vBBbspEC>5o~h0!>ir zMGU_m%lrq2Ra)33lp8~e=LpZ4ICPq5IfWUr;=Sj!ncxn(2E(%W+uK|G{L#>H4jP9d zs}<6XtLni?UE^$a&bjE1g+IE!hHoIvAf5zfNJA^@|Cv7eGL|R*K!=& zC^|W?j+z>%Pd-k2{x6^&+ zKHgc%6UY^EU_uuccU527RoH4>I-R={#vdb(=)}oSkDb7JJy$q_3AAbpd!?7+y^3gU zQ_osueVQWpV*XKiBr_jaQ(Z@*AZq6qANQwxN5t!E=~<(gvp_8_v~tCqY3bU$SI3=D z2Uiy`Pf;&YW859wIB?S|W>8&&snuIu``*uG#qOJ{AHHM$9=J-}d@-LMw9>RL>FfSz z51Tr&Q8W3kzZgIKPx`;@o9=Gczv4_rHvOyg&Hi zCVlr~|G(Xm7M%R{&wreL{q9HYmtng0`NiJ%|N1Z3JMI7Xrc^!8{rlH<7k>WcL-il2 zulql}-*11g-ksi6UcNBgcj=b*&tLydchJCL#)@=f42CT%0$=H-26uu1`bsuk?6q&# zm z?r}Qnro6+`>BxwQA$**R8dWN;#s^)5D1u-w(Tj+KoQ9rsTrj-s0Uqg+URGTt;fk^W~{di=FEq}d_OYxm9vufPK2n)}0gdeP z>guFHvwU)nG%28Ib~>bWy8Tvn)OOmK7Y=GVK~b2g+4l&gLRBQRU#TUmm7XtKC9ZRt z{hP>%^B=^CcYRF|Zf*9FipYyb{iI&I5;Qa66InfM}u5}x3{=W@%L}{eiir(Q7`x0 z03OlizbIQ0^$*95tH%6aTjH`_gDx!Y<-4;+y?!RL1$ZOn*^m0L_4^B6N8dO;{Hc0! ze0Y5IQD9`m&`bHe^{ar-;UcrUZhPr>&*~qpL_YhgfNQ7ik}tOpw>f@&-uLqKy~}#_ zF#Z1W_^SRZLR}G#gyp$u^5%54A_~|ZEO+&?_Llx?l)s(dE%}TgOqi`KY@ zttqUWu=2tx2&*Wpk^uV^7A|LsTu$Wj+grJU$Q4E25P4JNt$F<{;50Odt^9$J7X=)$ zt9K3F_4a%{1<%1 z-meR$HCvE_pzg!st9#;-O#8j)SYR_51`lXvEY+*JIJ!JP6<6n?RugCS_l-A~=kG2W zNJ{@bUUzcA(s;ry>qqs=`dO`x8zg;Mzc~4r#xI_qUBN=8uRdPX1uc_Y^$%(J7ktGY z+`a#_O!Hq0MpTTA$oyY0qEc)`)&nEbOV4~nB|K$EPVnF@0+*j`v%xw*k{5|=GJ2K3 z1uL7?Rl{&C8e+~>Qp}aqRie**5%hi4yTqV3kl}_Wu%Yyw?JXqgU=kMe@}(3y7;XWI z-(P!h8I1Zv#~*lo-@S9f7$fLFpze^1kSIxfjG;w}LW9-L%A)cRai2_SVUX&y``ciz z%q1o${+9MZt<+j&E^M8LbF+ItbOYxTnw_7z9e5anNJHvc2d%|+dDnM;cSHr|^RIsu z^!LBL5)Is_pRPWgRzK`5?15CY%b-_+tswF`2sx>M-n!&zBx{$L^SUBWK;QmYxNXNK z`a$xj^{|Kp)u6Q54~f&buiDOlew-m*6{jm5YtdTK-(NfgpE7l{>DT3!o99>K=Dt+; zR5e?#u2wf<#uc+@?$(D-<+HHm7Q?6Jzi<(JYL0_u#Uhmp97K<+NV%fxXSNp9ieG$6 z%u^AORu#RLx+yt9Z^T-0PpC(`NMD=^ z1FQ(XBHS6pR^iNyhC>R*rosx`($hv1S$kSY3P}8Kq#s>7eRPH+m=~ulEzy6(sT?Kz zQa~?sqOmVDi~?|IQtp$Dw8@G{M4w#Pi#0ULs|eUGgsk1zaxR=cAsQ*h9Hm_gsGb?J zF5+;ux18P}7|WfM<~#rlb0cjd-0IhO(g|-#gT>^B(E#)h{t*M&yh*mV=-MRABXrTj zdj>WLO~R{JLf6C*QK5mJU;C-C0{GuF$~nE9U6b@|R(n;=eW}{3RVbPV>!IEj&E5Li z>yds~uPxDD`GxvQ+UpeB>OTnke+V!aQ(#wk{%Q5rLN==_tbR_{pGpr}HTPU9 zqO7U6<~FT{vQR@sFy#`gev){$O30wQ8KSKslEbtyYw$8u+j~+wq}FbvvTqJ#=>|kR zkwB-(u!mH7!O9l|wj5^ZmG|JgsH=jW0^e}ZvW64H-&Cx_j1q@ws8I7bT~bvLr%Y-b zsxPSSuwnutvj^`a&|COAP7FutW)O^i2%}@FQ@LO;tU7DHfz+i#;m*c}wAW?M1#^uu zVhlP7>NQ1~K=0e$YIU)uY#4y-*`M9ssD}|K5VX56n7vd;!i+d|u0c!6ep}L&RzyN@ zzlzul@TbU-69TEdg{>>!k2Q92zEE#M>w^s>^wpsXgGmF5f}8OD4krxACEeaa%7b>B zV#qBxpdj!%7?&h}D4rQKZv0U(@ZRfeZ?$cZ_RAr2?_thKIm!tF zSR>WdTFMbL1jiz`m&CGRz@rNWLNrQ$6xa^?YaSJHmSL8x2l;hZ z>@llK^zhWPI@0|-0T6GIuXi38FK6T}J)6@Ey$F96I96xr7anwnuIWSq`iBO(d6 zb44WOc&>=h^~{r!jKj7f5^~vAL^4j>ib%q3TM-F3Ze>LXxxg}i7dJ+8bnT0-(+QOM zf&?q^JsfzRc$p^8IxM3L#tQBYFhd5^IAHzS_)JSECz%QBZ*sa3iqYuA5X-1hGRT-S%>3hNMhm4P?9-JT z+&Fb&cIp;7xK;gc)5w*kdAhTD7FHvo!J8&FmMttGzh1gMu*~<+9XQ(5e9f|UznnJU zm%S4%EPnt33Dn5pOxsLx8tDZRo9dcnX}BUGt?Ar;sRs+1VMLZA-nqpSqWI4Zy_B8h zKi7+on%IbGiXx0AtLeWE?NT+6$#aqZE_uxYJ8d%KIodK2TGkpbu{ao_g7NU7n=rJh zM_;QIo0@R8_~61r7O@|OF6i9iZ|;C=puM&Sw+~b1EwBqgyaescXZ}yn8mQc0_JB^C zdPO6jGxKO2X7gqtUo2U#f5A9{2Bz7sE0DbAjDlW*dDKiZ3nrE%d0jB*C!%@HJ(t2I z%l60PjE+qUn9LX*StG|jNw6yD1ekeoxT&>i7BLwI%! z-)GB2fIU(a)l=ybIkJbhpr4(fiW)GV&fmHn`tWtLZx6JaQ6*xzDC@=U-rHlUK-j^( z8h}~>;<=B`Yo`aM2PM%UXT9GVE6yUxQH1w_-1EaxAHNf6Zr2_l=}Su7 z`JG?@)8P9znx=vFE6a^FooiQ>Z>+H27pY=)Kkw0j3Y`s@Ap|dQWcUNKc#coso(p$? zXAbo~_&qW8ger_wV%pdb7b7TOX*u^4HyN@T+;QcXFk>BA_z=yD)|t}|^PI4q$bjTX zK|6*Xa7{NZ-%Pp^*n?)PoDS{220ZN|swH6cK0vy_J6D+@_}an9zu`NC<44Fq*u(#% zv71e2?DgB4D6=Ns0Nd}*bKCp{(5%AihID6~(BHf!yW-J6k&1I;(tj7tOG)X}bOM}I zRT+-!!m)lT#egg)X_M6ot3Wy?r*BCELq6xS)#{_^D9eDWG%py{KSr?snBiFF>ZgR` z51f|Ep)RgVckej2O4F~%Brjxd>ftc4q^Auw!q9`)5Y4aObo;pWZTW==_ixv2I-i}8 z|2KnerMn*NG-}pg&~CJ$u=N$&O{aao8y3KvAL9tRd^YOT%a-tGuL%&qF;P*=Y$v=S*I5@ z3Q#7v5CW-6kv~$qA@v6TNEL^5Cn45SaQH3$6Td~NQ3G#aJWHEZ%^%6!+TwVdA$VfHrjHT;VqR7Y^+2KZCiO-*RRH0?U@U$jXmL;Wy zCf@dXieLOV1R}LTU!ME20Vo=*OGTO@3lQvxD8OdQyo_l^Mz(C`FbJQFW)wEJL^GPU zcAru`LR-hsnuCFBw{YmY&F;vF{QL_>X$`%o8Kr`jFRY;o9az=En!eaYlQlB}Gdcpi zcI<9+3`Qxd!99l}UITHj1~D`CMNC?yte!38vbvdl{17Y}!gA)SnI12N2hBb`-w9r= z9~KU+`q9CM8oORhgRdnhE&-Q{@TwF^rRE_^|?$6wFDPA?TsE^a)HZNUu zgQXNDM-c&z9Tjrsbycbg@mb%QylgP40LIlNRSr3k@Kd-7q4D_hme_^ErEGhrkEld! zMWU=x_K@TWqvjmLrd+5o>wxH4p(-6Hykz5UpjzmRFsETpV~@*pr{VqETag~y-F<8t z$ZUi%1s~s+OBg}u{vVwt+bQ(M?DlpWw+GRbHud_&)P3P$> zdhSWx0=azAGPDwEFxjVa3CQI$mb(Q05z{h=^OxHmb&cZ&Zuli%4R=2bEr6wpp_2)AjPg{rx7uz;9zlV`GHMVv%6S5J z?W(3+p`10#W@4tCYVM0M<%*@^(TaJaGtyzfWc|UWTs?2*573lDW8kqv$kPz={|Qqr zZyo7Yy}pM1$(_zVV9Mod`2(#4Qmv?)E0}WBsFnMIOu1R5Wi`!U1qnWzIkEKdKi-Ud zF2%DpG9x#ypcU6oC$fD#jR5{%Nm47QB&8KplGF++(^>&XHWXNXbM2raR@4vh+U$Dl z1TLdlyx@@#HD(fZ!L2XRqRGSq`G?&H6VfBLVgHIfJ|I~E*?N9#ho&;Ts-8Kg1 zTm4rs_RnK9rc%Niy}EF`0Zw|m^$K?}{pDt~R~8p)X$fh(w0{gRCnjUeHMNmHPtf~e zeQ~9__MlD0p(D^`g~e{WKJ)Bp$^-s14uW{)-nbf8EbGPf**s>1k!o&<8a7i$`02E; z>mevzlY#F}{`yNkuazvV2$%X=lyik*J-epz#Z#m|glX3GycJ?^8LRCC7?p#0kBfh3 z&MGbd-;QKqS_Tu}`=(jadabTw_q97)gCjl+oLdPlriF>jONcNIXDtHEUDAa|xEWR{ zGrclKVTtaPn11lp($~b*f!?SaxNs_i3?^ha?U$m5WaZbd2G@hMe`AB1rG&_<9k>X( z@LLq~8TviuaNwQ3^HQKtZrj80tDRTrx_fx;Iv2su|9Ia#9~srQVRd(={T4KR^PCs? zd?{Pd%)C{Ee;4KyG=01W!kJcR7NvJR;$ zpMI)eHjdBFtd}G?>5tn1Q;!q^C-}s{aIuTsNLMi5jlOn%arNoq^8D(&c7CGTNWnY4 z`m21y-q+zP@Y%+@TCLt_>`mVCPkh?_)Um;A6uUdj_dz&SCs_Or@t5{aN__RzSL!v9 ze*cRIFhGMK^_HhqAv-Y1@EX(i_!j0c`e;Ze9w&V?z^889o+b_u@bdq&_onM@TUpld zZ|iw<4=3ubwp1ZO0G#PIZECWWc&f;mI#sV5Bq0eANiYN{nW^D&zia59-(GtkfZza% z1LP=iIq@J8I5-o|UVB=5D9pCFBNNhPXyEqdnrVi$#Hs>vFtG>R8C;t;Jm-;mN*&s!7a`Uk((M)_~ zzSm*m4q?hmZ^)}GLT8fr=RoXgFnf4{@)hr1yi@0AFWxlXfBVge2DH)H|Drc~_X1}y zj`;nkh8c$wd&iaI!@o4X{Ga^SKYv*aj_mGe{PFGUZyMfBq48Dq<>Bo&p7+aD@T2t? z@69h)?O#WucOT96>(XD2+_$IS_vvkac~NOurQ!EKTz21$Pk#LVm)@=SV}Yjh>Gk2q zbKT98H^&P*nLN4jZJ6;lAO+PMv|0oAmfb2d$QQt6(^{?zkgqhiEX!}EKlh4#N$Da$ zYs81YFQ2KODkczK!LT=gzQ-tczA^M&I7cxz*rHx9^o5nG<&yS=o$qObZ&+8#YdZA_Q z&Sy;7C`ZF#&N2$#I##zN;8}Sfz;jdA*|w#uCc+(?-wcz#Al@}_Z@d=ho>4O@+7u=8 z7URF5AjY<;TLKns_rd`;m+(V)Lk?{eW{5E=6M*Xr!KcFr17trbAXk(Nvdn_D6iX8UumGJ!rHfv3x-Rg<%I?h|q9 zm{r5JiiW*TzW2m9a@sIA^-Rf(B?e0HC}NAZf75Y{OMe)6Zri)Q_FSeOi-v$vgaeo# zdqVdM=27`He{y)ViF_;Q3a9gh9p`0D?Ais#soHP!k>I991Pz^zM^5p&DHf45y*g{q zobO?QYG& z%epi-pUzU#*g=YFQ%Q~~=hb1ioJ+ccWs-(aKzaodZ6l{%HmmtPQBd0JAuI6~)Ik1) zE?>x+ee2433T;N<^xY_PC_&i&nHRZ}UY})6Fhfx)2Gy`f$ zD83;Xr=fLf&vQAMQS>42s`1)}dfr3+Y9s5p~yC7Vh z@d`R@u)rhqv=7{0MjnVx@QZHy5V$SEV#Be{)CAFK?DAlAapAR?9w@wF)bl>dWvv$T zm(I32a~U9)q$XWZ`5rHjT3VL(BG1BMO#PSt^VB8y3VE7(mOIjbj+j-s~V_{@qVHa zRJ>R0kL0{AW>CgP^#|=RhhA4%pS&(Grm&$*mvjO%7Z<7I+rOulC0FLP(<50l0Hu|j zb^*OD0e6T5h^*+L6L_ry?Y3+5#vytQV9gmI^PJ|Djc6=8WBAkQxF4V266n44{q`L7 zkZslScFjzSdMIjc2>_kz(Eaiaxx;KQxPdUYjONXt9ittJeq_!e{4VDWl1S@Q4i%2_ zC#!MC&S5w`Wp_~y)M>-a&vtfx8U!IVue7{2i?%W)@+VId1#yYX|&%5Lm5l1FNrln zon1se6o_`3LFSn0`!r$U3^9d24T!H~KS-E5tS&qju{2UVnI`>W)Zq%{s_6*3z5B6` z`N7l~VPPB@RTNz03`heUfP(0kk;{yOl9q~20bygt|8$l6Q!n=?P>{9;et@~-j2(sz zJ)fptq%tF0NjN1OA@fK>VKPV-|1@)_lM>;Xh=pg`nI@hi8ZY6QFnx7_H!=hqAJFkJ zfns4wodA)@T#YeNXG7EVQ}y^yqsM6C6}z6M_f%#o_KMGrjQo{-eO%0_aZX2ki17=kmuB_M*a$%*zp;xHe9|obf zjteGS0O|_cPe84ISBifSd05y{wQM4d{J{Ag``S zD$_P#FcQRSQSgTKZ!^jrwv3GQk2hXy4AeOw21Z{4*_%wlDp~Bbtl{e;a|CipKf|w> zjzv3T5+a)FXQZ)eZX=vz3>U#MOEcoMu9!>rncAOhTsYYP|7rhg4pwB^XE;vg3VHZr zgI#3b#7cs4RM(W`+}-(JAFQeo1U}ncS6T36{p(7H+;gx1x{FqGB+g@a^qVkaRx9^pE~Lbxz{b38d;ayvk%_JAa3 zlhLX`P)FB-nnV+YRhtNXN&F|uRk?`GMvK!nJH3h$4QqbeF#+n%uP5C0AV05_b+Jj1`NIV$t@6uNoYRJC(T@2F9;LhlV*TMV%^iMkZA+S zx=IO3a(TKwQd65 zdMZ2G6PqaqaSkPVFYY<_gKKB-oGo>MPcuB{J%Q_iw`&rVc=fpYmnZL4flEW;`g60-A?xY_-4xWNW)hdsZsQ`QxEym0TvB6+EQ z(79%M_wqgUZgSDAnG}WJs}e1il+9TzR|sfM_u2?SVWDIMy4%mEo?cHf%r1W70-%4> z_xs&&D%dqNLe)W?bMY3Fp-#rly~G!YL3&a~<`3C-1J8%=l0X?D`z~kBGJ<=<IRw~76)>;bCE6Hh;rH8^XzG(i}1I62Ni|_7Sxt)&x zg`D)9%Lb0z!8jDVXRqJM`w(~WMmx+43-wI9zjt>y)h*i%#1lT9vL``j*w2ML@}bXq z7c_ZcoBP0q&;RrI?d!(bn}b?()Zk4H;R0K);KoySNraIxq@hK%AT zunt^HeaROC*B!zx!>_?AL^?=5tkm+U$+fV#&(*eK9Uj@I8+zvw=5BM_O0iNtUS?a7 z2j*9@tt>ru?=r7Mr*h(1cJh7RiCtd0)E?*>xJv|vC?Ip+=WXrE4Y0e~*EszYOF{j;B++sA!i%3pL3pq$iJ3%K`*Paw z)aWd)JEfjgOwpqt%`MFg|3gc?qoQ?K+F0sY#Z)~C)xzm%WvSF#@W2GAw=8n_E}^$K z8(O@#Ft&JQ$+v#&@RZ0~|MvTpb;W$qWLZ}-?@`vpmO4q+JrwfNO=aEoyrl`ru*g@) z%m>I%L|#fE)OEFK5OrOp7e*bXnO@A6XRNFABCP9btr+Vg+USsr5+*AX){_`gmfH}H zDENvZpyFz{j%D*QSlHdc&ZACe!a_$50&tPW4ey3OT`@);#FpDr7Jf1Y}=*Y_u9|6) zVTiU>()ZWMJ$#LmX*k@QpY((n)|uwkPt-ko-FT-?{SLz0QM@2X%+@X1X2m$Q3e_^Q zb>piui!X#Q&n{b46Mllzyjd+j@3%hY^Lg87yTO?L& zOMAmItM+_L8y1_NB+o8Q=v|PUYa7KmNVZy6pU6J{YUB>6MdNGds^<=d&yP>ee>B*M zDJ#r14$G5lRkK-bOq+09_pL z0Xgpub#Zi*uji_kp;uqn{V;fcAf@lP)FtDr3qB=-5OZFk_+@x4f>HfRxF`(obu|FZ z&oiC>Jq)YX$m~6COQ;9UT_!$CiBt)RWx}eVf!1(noPb39)zIy$rqjBvg(1fT2QgCW z24P5vd~nsja?qBjn>mCAG<^ozdZ~GQxQJ`>_!k^)tcOYH)a5*)Firps?w?5_6i2&C zwmN+Bqn`K6h-WrL8Z>;2i6I?_zo@ge70_YB$6QuA&IYOq1OBGejeVxnOZ4CjW<$jCMCSs(8$Z$;jQ1tm?Pl+Y!Z;mxf`iK_ zh-O+F^V(2DgoDddCD^68&ngNde`Im3hr3ZRPncR^!e(o$6Y5?ifz0OWfW_)34a2zO zp`lMGUovd7`df!#7F=wf1CH%wl!zFpPyy+{7oiawx`>;dDXT+cK|M zI!?WC`tN?;3gIEJ=Pn5^dEl`l4~2qz``D4~dAMhpxap;fip_83|EjChJYaQ|>;vc z!Ks^`6zAqXFD9bH!^3iZb1}YX#y%Ajk@)G3>^$}V(XsTwv7d@-sIIng4!!Z=#^W4}c(d>VB11*vK{i*gM~(!L>(E#F3rSHCCSU1z z$Y#P13Sd6~rw{kr6ZKaI*Tj0lEpyt8&eaz88{TrsEQyodpisoR9<91W#iZlZs;jiz zT5Jhi&#?tYBfq0ri=&(?xFBhl(Zq7b(D_R&>Q(6RWMuQp2&N#EO9oF3d{@yhn5jlg zbZxN;EG37UWqVpm3PmrR*Sql7j)oTG8&l5@B(K{#fd++n+6F251QnucOcgY1vGHMdIplLo}%!mpa zv`;YsI(LYVra(BxyzZzwd2okMgp9gzI$j$O?YpFwv?Z;4iq(mS8_U1aL(k};BPom? zM3-thr>K`WUU?wAQFvNH%V95e3tsNF7SZ=#v?wgVOJS`(fN|`W*z*@Ilr65TDpo8>V-mHDH9Z zTAMX~2jkt@=aC;Mp)vrZT(GQcD~Qb{d!*;*veEQ;r^#_PHJKcSDC;b?bDgsop85P< zA<1sk7YT{nHsC;PMkkt;6s3BoMu>K;;u^4p?>Ey;H7P8z!z|A6?IGizo${KW0@lY%o=?7 zivs(%Huj}z?n^fKi7$K`qhDREYWDly?Q}APB|6Yhn4@(s%8mI`Fq09pm*Kzg_%D|{0$rH> ze)Wea`sg`*x|d0AMgNLT%)&XEz5Sn)X$yY|?-)Th_ql@A zl2x_M4UO~(bGJEIwM(UHV<}i&@W2INH55p~GxO7}6D)`*{o9r$W)=im-bX-cgg4WP zNqw|(SML{w7O9^CL-TWG9#e#&#YsKXBpCWoY7uM-LsPFpP&Nxo@=FnR&V_5@k>T-_ zn{-0{>S3};8bN<`wN2QcVTA6CHNwYWvty>FCg-uDau4i|nhy$Ls7#59;=wc31f%LX z-~iS9NUz5;XWFH|h!oW{s#T*}#=XCxxX`ds!mR9EE&37lS-o87@ z9lkyO@%;Ta-yWgF8`r5so6XUskT{*6G`=}{$<^G%5zyl&qmONVhXQ`wS@^&J@q3E) zCbPSD9XeBo9iea)FwNXZO&2&CD@wQ*k;B-QS!6Utg@z&)R^I90E(^n45tSU?frwS+ zs|u;VIEwg)G$4--CSF%$JVdNqLN5!@9&+6CMi5$he2HbL*_n%ua~Wj5#l1{oD_>;3 zeM+B%U(;B}sN_lAcy)IEUL>pDbWgJDNlpW^aBYpgV&+O**e9(lt2NoR}$mFmhYh$Z>&Qgo*dA4H6yM0U=bsb&af-DXqf>kAaz5 zK#(+7U8Z&1-NXCMdGo>VHwcV&FK{}Zqhoz<`aRRCm}P5rwtlkmRRaOP6{BozQrNO# zoi;W!num*%tcN6wW}!Y(jMR}+vqCtvP_Rx)i?tDYtd(CB0mK(4UK=VN^mS_#sBiR2 zuej!%xqnw?Ugm!C?aHQE$dk(X;w*1kh01f$?dW==_3<}Ow_91SxzDV;jda2VseGi+ zdkOkV`Jq&(xO=m%rN@-hFIiz(T91jTFw?22%M_V(QgZ6(X9`I<+)fH?ua?t8dF>C>pAV7br>44NekcR*3*|An zub^TT5DeX8!UN6T@=uM5UJaC{C|O zEgt7fg;gr+&iB5|y*F1uy3^PG_LX({VUGhg%NS0-uYQ}dx<$KUo#rbDTR%LMu=T^r zk(F*)-SR^zF|k!tdO7t%6J@&~C#5c|d!{(9Pz)7_wwsClqmzB)KGEFnw}c8vV{F2{ zp-sa;1=C@p?U;O<38R&_5P^;O2b*dCL+Qvtss5w`hhsszLBOdW%VU&#KDV+$nDQ3I zZkP6e)^of<>@#eL-jFYh#<;vaJp*^Q$MMt>nw)cotsH%Av~z;BxHKqik0gXaxe$ig zEA>5Fh-ME4X1Su}Mzp>I2*bg_ZNdT_-)V;-x$K1S#oy3!T32pHTfn%u!=xq!msDkH zD?F6;RZF}VPK&o#(;eQz%oc^t(9Ka#vO+M?_k@v5I)S=r;Jy%|23)UWQv$_Bz$C&4 z4h-1w0_`|BWAGJnn}ufaGr+^hT8(FBvw?WAJ)~k=hyk0N|{it|zPZ$HCQ6(=<1qXDD^da373 z@+z3n>;ff-q7rul>Je&$jJy4zFCn(N+$>ueWj%w*f=2Vsegp!vPVX{!0`N^LN8j%S zlC2TGmvR{_;J>0ki261dvDV-T2Z&H&+hrJ|i;h|LF*f8*=ylw4``RvuAu5p~r&Q4;AK7vVrw+M042B=LzZC zGHTm2piW(-1a|5w6}VGZ=>eX)N)PhX)wZDL^%yZPL(N7PE|jp{1?u$V{n5)PBqt1I zz&x^~rz>^TI>PTb5}*>M=SI;A`+*#x$O+62_yj^Y3<-|T_#jFGe!TZsO=vY@YAJFz0QYei&GdKxLvA3pZ-gO8o$RScXx8E|G~Nx1)wOkqJa~+0 zg+n~ob@W8CTGiG(gBXa103jU~#LL!pJMNHkiIoB5WpG%571FF+_o*WK7d|^VpudvzMKu=CKcMUQ&#FZ8CqCzqm7m1) z+ZNa_3I&PFf5gv1IDhipg(=6mW!CIUbaPe0`K@XOrPlCQUgG&5J>p<^r!#VY^8SXv zeEQ}e|4>t(mI2}h!5rOipB|E0{274txS@l>IuFPD8!&*m%8 za=Z_CJ7&7|_^*F`e859^)bWNKkn~j1n|Ok5Jd&t*#gFfrMd9%5{XP ztE^+-qL7Brbq3VAf!fzql`Yz=baUGwsLLt~xHb^)9kT3-Ssz>__)|(~@TsNIG(lyQ z(@^RhS*4^4k%CP>QLn;BN;EecEkg_*Q$^bD1!6>89g^E~Z>3zb7_uKn1vJ=HCzfjt z4rWv>2i?TTO{$d(N}&tB&}Bvvj|4QLbI^c}`B=v9a89$w(zAl3O1l6Wkd370IjW+Y zm8mMR(%k2oGMBd-6{O4+?YrbC&z)Lf?zX6)SS}YPwWN|BShzD~Zn3~y)B1OZpjnyV zVSR}*W#ijIAf=z{R<;TSyDB^pR<)99u3O?lC&|d)c_pcGDwU)bkSj30otWFy0()DU z@^oE^IxAC!NFgn`T30Da*1AeXw${~p(iMbih%ANqf|G3Ygw9VUrs0`R9}$8GNais4 z4=FF=T@PLD{OqCAxz^rw4yx5r6grh_cLxq?HpFQLaTWBK^dSQUJ523!V|B$KO|k>$ zw&P!l+ns;_a`Cw{95y|v^z@q62uF=&a?!O)Ex%zbAvKgiEAewnnp=V< z7We92k;UM)HRxWoik2~%h5bbz{$!h^)Y@{k=PR2OWUic43oC^ct~`0ubp4Y7Sa#O7 zD_N^RD9N@Aoyrzn@RLQ-#jY6iJq?L9^vO9&gIYc*F%tjyeKbO?Q(C4)SjDcGMZzym zsbycd3kOxkplqb`F#Y|M!!LhQIyyE@aFE-`3YpptA{FNvG*1Jf%Sl0{WZFo#ry~#bGe~Tc%u)YEJ@7hO3b);sq3p`= z>L3d0;*jOuPILSq~SNd^w4CW8avBq2P=rAw&P#-P!ka)-HnEFAz6*KqCGH zE%DH8hb~2q5#!tv!XmVfnn62MT zJ@3;bbcSy0Dv&pNagpwkD^SP+7e>J(#bLvumm}Km)JmMdN}Bsz4J(bx%GEGeox9Bq zD;4{sJR#0p^g!s_cXJWRt6^Ap=eJ}Wx=$-b!75j(wvo3B#j25?Wv+yrg$YGihROb=qncx9AN@J__-`s+Dx z3n*07sNZ&mOc(#C?J?Dc1ckeoh7O8BSjta-lYsEpb0$#(eZb8Dnt!Do&hm+b%s^WF zb@107Bpo%+hS0&BJnv`VbNe~K(CIu@+03lRS|$17;^qI0R0u>UQtM^Qt^5}}A3c@@}CI(@dfXx}7Hzug;Iv{b#>dacyot*C%sS-4Di3JorPFIuPjhYeCKn8;?}Mn=D3@lZ@h~mbBcHmve*hM##0{y#SZazi z$1n&&SX3mcA_#-AWEK$+0+Y<+zs4i80RCi_^e8y}6kFEeELAS1Kv0xl5^7}g{H>(1|?D$Qm^FcE-#Xq|ANXvZ0 zTVn22&IQheo9$lH>Y_#b^nriEi#8HZeCw*~x9Kg$B+?K5Ev>kL7WLwqSt)FVO zzbSowgxwgjb+H{8H4)z9^>0YVKB8lNPpqsl_TWnw%FnPlMtB=WGsy=EiRJ0xZNHW4 zT?EGPBS}C$#i80qU-6R1zg)q{7?zW_^2+204SQ2$4caD1| zF<@(1hf@~n0=b^Ru=9hVgsTUO6yq8H9KcGdp*`76{{ z!4tofs$2BtARm`6)~q+Q_`*pyxM)>u`!T~w(AP5){Ok7(dSXMWYr$a^K^W%uWV;z} zbVUzS6I@6eeX6KWNTUN&k-hdCd>_EKDld$oBNg4d?{@@SWHB*>`)-y_VmSJieH?wDL7` z^9n9=hhx;-5^cCmDHQZZ-%jmpXCQ8xa`VWHYMim$53Gg)<|SAE$oh*iRz znkk!P`ypR_+^rG-P|RIy*Z0@N(45(ck;Afg9F8PTl5UgaoKC zi|T;WsPZ{0pOaUCe!$Y$mTJ6STC;d~XGm|7GGKpIU=)f9UfETx|q#p00wr8Vd!0Y ziwm#!g@6R7cdaR6dnDSuet?f(xq2sZzCSl^KaUALWyBwNNbD_PVmQP(x*rv{X^u z+|pXme>~Y*wXEafX6k1&|98pO^NB#fHh>07T)iw(?2bB6z zbzb&rG+ky!=O9BGrPmv=Q0w-+4ku;z+MxGgAr*Ub40xJuS-8T0$a)W$kw8Mpl$_r1 z3pFLsHSeg<0!=v0xqZn%Rvq_ZD2(=`v;|o&mN&b#lW90gfX9lQ11R`7J#afzy2~^lL`5Va|KYMfjzVYf6td}FC__DsssDq`k zFfBn6jyQUx>q%&9zCOR5x9#bji%MT2$n0uW)K*l8h9v(!V zM}{%2&ImdA;zYU{$HRoE!`c(U8aPQ2I-s7#z{R`sn!pncC&LK~9qz_MM<0E^>$INV zIJ5wB25>sh^JcA(BI=OlmY|NA?%XTd;AbI{Ft~LFT}c^jWv!-ZmhE=YFfBW4&Id*I#a(m~O=eA>V4XM-!pdV(1m(nvc`_ndye zqbVhG{5{=50)61_#p!yU(zY+1p4|4|UYpL%U*5hbfBVhV<=zS*JH=w9R6sJgRR%7X zh@Gh|wr$`};+{#sY?X(?O+T|0*TQO*)QRXxI)ow6lFu^x4t8sA`wR z%tDq`D-<%bYQ`jHHa+VMm~!UqGj)}cg(m9hT{fEhae|em{?s<(BTm6qBc;mbtu=L( zhP|e)5-m2dSle}MHuQZE5XKc~7m@Z)$~iHWYNt(OG%&dcufRwigHWB3UV zl7xK*^?RK96+ zU-^x%kAA*>+5I(l8GQTo`|&vc)$6ktL6HB;z@;Z0U3O02(XMGWfBvdnD37gIzqb6Z z#&1vGU;JA6?(*sG&eP6Uzch3=Pu?6a>}2xfPI0!}1z<^JR8s`7+}ytb?6hI+qrJoAG7Ei1~qk3F8+@S0EsOxtF*addMw83i%cEEY?nnI(W0K8tbzCIp(d-_ZB zhw{%qe%1cITKMK@Metg&sv5jjJ^^^0?3X0)`cT;Ow*s%JljrBH7A$^EEra z1iYqa=c}tEOJ7~3VC$=^Bx_$?rD*S~tA!Rn8eDodKj!;Pw))jo3U)t?Dx4K(f;MT* zAzx_WRMzSB)juu=Zh!J7z%aMqdm3zvE8sB!W7wrb`bvVvdS=20dK1=s0sge+vYseo zprd^_osTlTI-5TnNqXVT)(9YNhL$C5q|E@9gFF#LV=wnpO zecV2Lclh@GaPV{0Bn&<5mWsPO`Rli%FW>xXI%e+p=9km&z8m*1j^9;)p-a8*k59DyaKzdQ1?w6;1)DwJ(7b|Nc zg4iSgUAE}AZ43}c61c`M?Vytd*(ui>MNLT?L;z)rey`z_o)}QeCnp=md&wl`lc4kd zFZ|BtZL3fqxpU(qfSx)x3R4W9%)%3Du4oC#=`tU$TsB=q)OXeQUK6Ehk`^w7=w2zY zD+NNKt1n<-xA31`w2T)G8n<0~e(U?)kVBoGnx(rt{_UvG8{^JyXJ}Q{==6P7^;`@W z9}0c?qS;#yGj2Yi z<|ZoJpn44?Jd<3vBM12I&SCg%JTZ}$qwfw-g@?1$BvPNHd)JL>?&?C?s0;3Ogdl-~ zqg=#Vk6mg0TU1T$Ibj9*3<_SiqGE<|Y6qDdJi`Y-vW%h{gX;Iz1%m@;aCHGy+b#%t z@iffAUNBV_doO$8iiBnuB&0%IBb=1>j?-f(#)i7-`^0d{m9M-w9FsDgs4uU>o0;8w z;9eq{`wJcI{QCQKgRSUbEu&6ydcv+OWtTsrV5>s;u)Kv}o1d^n0s){^?a$g)4(>nZ z$~-O|+tqx@s2)~Llm3`7ixljmb8e^Gh6<3mnog@z`D;j$s&3LT=sIG}rsD3s-4 zN2&Zw@+o*uf0D}DzMf4xLC|4pX(#kDl~HhOS(fHhZ|?Pwc1DA7&xi)TKk6Y6uBUz~ z*+#i)l*@*l&u?%%SMpV}v~iw^O!(4NO!Qmr%U?esYZNy6h;My%Scz=wN2Fop^QX3X zTwI(L%E!?nzV#I^)+v%$QFY#>xBAi{@UUQwv1Ti0QXaL^;7glMXPmb8FHH}fl2-3f zm+;k_AB=)g%o{pAwQS}KCM}3rDdnF}uf{Bq?eZrj3joo0-k<#NKF3+*69I5`{x(~w z6suWlsd2V)c=kR?MlgqLqmQDK^gbPqr)g3ig!q`=LF-A4R48U=(}Od!`!}>+i~V~0 zL`!D8N-5Kl&9_^pS~73^nz@$Dl$J`mC8)(fJ<*rb%}{4B=|XmgGzfd~R`=8tu{w!s zK9*u>o0sbP|A0&B2K9f}TzWTvvh;uL?*G@hR-P(n3C`WP5@IoTsO^7AII&<>3UmUL z4#HyY?LqYF=kSiSNZHMmrJYCs&sT7qR+zwTnWDlZ&Hd3UI2KM%PB%?RNtmN8D>&wl z5A#)B!EwcBCg~+p7jMZqwq8_df?2RoA@TR($fi$9%Zf9m60x~my~b3!uu=)f5aYy2S;=T2eSwY)C`VL%WAAjz3~+*qoPOZ*mf?Z zA>3dj8eIc|t8p|Dg}5u45&a<&F&*Zz?zuGM7bEzy_z`%QIeI?EGzdy>7+{-PMZEO= zHfIs)^BmfG@aW3*2Fkys_pa?;IHS(+17Zw5xV; zN&J)sUv!A+mT-^MG70H2JtoN1l;&WRbF<+%x-vWZwGyD_vGqWM9vK@ik}nMG5z+o+0TT&qFvlGS#~~J;XaQ5p^^*fg1IOB)^b@(dvM4o;gyflb}CUX9!b6 z!%mZ9)IPF(gWgmJm^1LT1?9?}dmuBi4qdLe%{`~<;A}$pUW?BVdJEiJ8ZF0;1!~{B z;HmbyUAOHyAZ?j(>crt2AEH+th)D*{ z#f8`6d!&GjW|E!_Ow{wH_Ax@3U3e_C(+Et>0|gg=K9g~pu78^gyOX82u1mu; zu7=%CbbV`JjQhsW&v71)Ts-vkHEV<%S6OY}u*BQ^2_H81*@qmTSf!)YO1&jbVxk-m zLyoOHP4o&O$84D?RPG8nuIS4?eaP_|CYo}wTByJvk}p?3aoF)%7N8ZaO7|JcoM2K~ z)}6)T{GqU3ZXf-;E#>8`afp`QUMlX3VicWtDyG5iMBr~}UP|4-Bz3g5FfvI4TN|3r zP=Lk_hEj#&z2KS9uP?5c#}TuFP$a~IxcMGb=IC8V6M*lb2;Cx|O=k)QqbAaqnr@af zVt7x`6qnIpeZCpH0*QFxBZAN{!1ju4Xvlcna<~?TgaMLh9{BB1OEMoPxp%HEM;m1W zO7Mnm`oeWPpl`d-l_e&Yws5K?&OWqrFqU{Z>UPmRhBIw1z>RGVUSztqtAS7NTR<(f zt^|99LGaA|&}Lc+ZFxfkJ4dES>0C5Hc+t6ZFk^n#rvcx7Awew*je&KQt~s!-78(R$ z2-T36LuI6`4E_!)9^$1ixDxO;v|Y1ID;K^Bx&)o82zhb%i)ISZCHo}L4~@gJ==)ro z&})r4%&MkP$KUZ(=pd_T52O2Wsa0TY}!fl_xZHJSpiIFzYHU6J}kdX2hHh zCfe4Fxx4ei9}XQZSb|DaVg#d2`NoebvN)5up(!7^yxR1Zae@Wcg56wD-YPm6ZcZYBHP}tLH^9E@C>h=2& zeorPFaAkw>*|a@E#FHBk41#3ibf*z1;k_Gg;P;TeWdItZOlT7oVd*s08BKC6<}%>7 z$e1Cf<}7T^hDRCGKAphs7fR-I;6i}$+;wP{P^(PjXf_zqir_#OsU}I_uFxfMWTHe* zh}lA_X6e^(Iu&YBCe$2n&gsAtkXKNqSu5t@GT%t|riL^O zr6By3r}R_s=IBttM!sQJj|)e~3k(Hzp1Ws#5^~37W_kqTuf(6TuH`*Fa>qw&7{(v1 z*wIhHxl?I>{LA?8yQ`l^FV9O~z3YAapWaHGJJn*fVqu&m4v)sw)s^rcg&QMqZkgCfEId9R zMWI^EHSJkPWNIKfjK$gmK-Y6GLQ_THx}jFLhf=2Ta%wGX2p9o+U}xGB9)S@|8l=h~ zd4CQ%!n%VSsGSQst3)S*=K3^$e6Ti^&k9Gy@0rTyFE_8JsC;^d#l~0F!mmO9yRZE> zUtRiduD+~(RVw^4`p*5Xcl!1(M;GtC??2M){n{+GE|1RJ-H+equbRP;c{ucb@j>NN z=;hw8M&+|$a#x`8iD7%IiiB}{L%-S_5RIDq?EPxTC;8H9DYZ$Hc$Z&|zB7wSezn4s zU(GI&=$!Ve%|ErGFZ->jeAwvBENuSZda-}4Kubb^*Lcw=5`&QkSY#oK>GGa~=+a5g|!c?fYk>D8&0WINlVI!a>(bIW9S6qkYU1(Y( zgo;WcA&Yn1YLE0-vA1TzOc|jBx`1Vt#CmA|`QClZWmI)uQJlpP2+#o#aXVr5?S$ON z5za(3wY$TNO`I;W~ouv8Tik8w4JY9wm`&Xm(3hymYtI9ike#@yJq`yuW~Ck>XWSP}Du~aopYep>D78BN0L$Ne7_;A4cFHc^*rPFONaEzeS zqRBW=`|(Mz5ERU=4y2gH<}4cb^5-Ur!$Qp>A&h90@T*m*tYg@PZD;6A-ObbwKB@U2 zO$c4Vnzn(jZi1DH{dtgW(y^P?)ph@t11-@XJUj5!1--$}+!IkkT#tGPH!VG84|GJN zS!XZ8Dcs9I9JU;Zi9lDxz`#TeT1BRl>^dXD5EnFE-Ce{l4C7XV$-c?`i+55k3}Wc` zm*`1fsJ8HkSlv=&$VO*OBnKx}Y^Bbfp-8|Y?EZ8}6*tu0sG$s6iMtV`xg`>C%AS6I zvd`~22BD(Blh}W+Z(I4SRVilkCHkvsbR6er=h^|1GP=W?JZ455FqNW_FVSDsP3mnQ zR!>$dU9j@p#gZCk#f%P|Wsg%q{^{{wiTBp@Q;sa-?sS=zjcS)MnG92!be(Snd zR*_|k)U8$S+sGI#Qt3{oMX6$1g_2>LGf?-;9KM_DBvgtTrFm%_H3MQAr&c5S1HJWC`zyA4-wsk2uG%w70r+B!ie$Wer6{1 zm6&VYM_f}dW(xQVHhUQAdk#Pg!to%GAx`tIP2%j)z%lTFHcb=p9Z%KRmF$e;=+5ZC z&;f3mu?Y2AJMNGfYX=1MdphtZqu@BX9F194hb2Azc1Zkm1ub+KB8Z4^B32(W5_w&> z`@DN&(El8dFfH}$YR%q|<8RSHD$OmCdQ+XbSHU+K^1ep`!5ek)GxV9CESeen zg(P}re?`KbB$4@0C}pSi_j*!udm`BsSc_(Vzx0G}-S4^KN1qF^(e!9P!wQbaUR$CX zX<#&fR%g_9byO-=domH)&wb%|;H+n>f-^wX@lk*}L@lS2qb71;6VDXlhY?MRQa%$3 ze(zeiDrsXIxn(OwAL+Je9-l6L3K|l^slBMFJ@K@2s5@&!Tm*+>yd`WI!dF6~B|$RV z?Pezw#S6J$3C7SqrCIOVzVPiGgs+A{in#FiB0QivY3TYaoZP=C}St66c`~>eY zDX8>44T*V~VLJgb*&z zL`sk(GYoloTQ>pAi%UfH{bc=(<_sx&Ufj1zt^Y3z2+r|y2ehd|Wj#mtNB>5}Jrc}W z&T4s#T)EMp*azgWf(d7pT2>uclv9dpc(;+>5pqj^@~a`qV{yOW%M2Nz1QmlV&P~A zYcw=^W6nMcp2dq7tbh!vwL-pTZ|K;bmjhvQ4-*;Ki-e#(g$%3`iP8#*)Ur|81R0bn z_e+;l4g|x# zQ*c3L=Tp!1kNT$%?1z5JAtsQtb6LVVwX^o8tK9Am`_`U@%Ax%Hq3a?6(hItHlJ-RL z7ihZO#D}QVL&6LqCPzX)hYlF>slFCJta|NKm*?&LLR!G8ICum^Q|Ge0H9lIjt(|MT z)bb|S6vDZiyezRwybEqB^G#g+m}e0+w)igyXYQV4ot5Zx|J&00_rp65YInS9v^vqt zme61{3VK;czqj*Nkxm*s<^ai;BowK6@t7bNhy}SK*n%V}IzeEE&^Cvjp`&sAmM??#RgRDqFpH)cA-$9EoP<~8<4oPNy7hkPLf^S zmL!|K9Q;~Du>mE{2?VH5EPzy3DT$(b?6pa3bWL$2j)s*45r@Qr#KVMv|+PFB*2qwyMT`3$8nY+J5KEywy~gdH+~~ z>o*WvaV@zI^U8p8u9~CE%W;xCTOi7IcS2dF`N=+_LeY9AU1XhE;(%FY}4c-c6)isZuE&Zl;GtYw>AKx{D`e1(U8-MHHD?%$ugu9bFa{nslvdjpN`F zO}a-i%q(74e~76<3tB9B9tAWw9WSg@a{=d~C?dT3yt8ZUj?%E1V03_mgl#Z%79P2#diO>ouK zV$lUH@=o#f+SlT%40>O?P|H^~6JOL&2CYo|PhoSPt@yGErQ&L|>nl&*0~KF%3fhHy ziN)7dd(8D`NqCjGxz9FI>GKg@4~4BQ4dJydY1{73%R!gP zrF`LX>bbYtv&V;Wd{!fzAS{|$DP*DQ58Abn z*REaC@BK^aU2sr?7FpZpz~+M*bHUccYr4;~wz6Djk^XkTA>7;y2TpUi6mqqYX#Gs7 zm;P-;3=e9#D7h+-S>D|V!y78&Bl zrk#)6zbiiQlNi;vv6Cl3y)<#kqoq%}S1WtTDwV6;FY0`%R`$fDUyPj!=0lH3HFHvRYd^kYifiQnxcT4i{xXa9CBlFo6xQGI+wZ9W=_~(CG|9Rp2|Jf+zPyfd| z_lj={c7bByJqB3$wqSHbI|p2q*VlNJ1JH9HPO_w&u_)Lob>x9FYP(t*bi`>WRm7 zBtSF3H8t>s(hSFjvS=Kw1(0WwSRFcK#l96Jk8xW1OA!oKUs3MJ$3bK z?l(Tq%ja|TV>q5L-h1Y|3_`+7kTnm7W1(T@B21doe3Me zpXQN8*xbXk-+h9Ng6b(jhYfL{OkN_r>jTRuD4Uj9f%}Pm?hn0QbFux-!l07fPq@2b z*wj|REU#q2D|3UTe_QZAr3G&pE?xz{ero$)!F(uWf!o{vXe?>t*TI%goX6O!|J<@EbwW4W13 zv7KyfjIZkWU0)OE(;X=y{e;%mg! zEYcJ7SIMe^eqbv4!ekLPGc#*#BZ-tLu5NHC;F`5z#ke_e88x>=-AE?*x<7rxYqhRM zO(U9u{a#>AeiEu~(?b2OH3~>XDn`LPEEYFYG4zDZga;;Ya4uyiTaN+_4X?oCwuEwR zV(on8lVn5*RT8YIZtI$K9(O>J!26Y{ zgud}dAh)7-=NN z*9#Ss5F!p&pzmG`xE~7NXu?{ohq$*TADujP@IV-m6*jFnp2Z;=#t+Xu`JN1J^-L!>svv$kv=ndC$eVc)^v9!2 zci75x+SHWPAQwtB2F0MB>$GKRq-gJiS}hzIlZb`vcOYZo5JEihXcA5c$`Z_SINC`h z0P95m!S$BQ&TzB-HX7uqK|WbR&VL`qywEPWz}V?WhuJo* z1&^@|mm^fqjrBt6am#K(Ik2(r1wT8Sko|)vTzd;$pfhX#K>eNG#^}r4ohM;e2vq!2 z-aoFvGCk$pF&B~}z zO?9=tv57?ni%M4XL+gltp>QEJWhgpD-C<}mGx&ZMi#iZ5EyF}zO_?U@YPoTu0h8mz z9R}Gd6=7MX9=&^a`l|8b{ORMw=jI;A#V%*EGkD&1oAfTtyP3*0vsBBc=4J|;TNc$e*Q5Kb1_X%N#6jO8@D)h# zR^W7<_LVpLxdgS9Z1|f86xm!+?%X{gHo~`yL|NIGgw4i~s!X7Ui zbW$AN@zKdi`LtMBzsDx`*pjM2)Z=sXILVK*@M2xK=quS1>ZPH+8#}>X;x73i8=lVL z1&fyw6BkO}Ra;-P@+{vP)!kBkr*%z|FA${$)tqOXYVQbY7_Z*^U=)mE-q3-8)uK)R zGVDUJWIz9T><>M|>mlgE9nOrXzSJ2dqWk^H5ASpDUNz3%tnQ3@Fl_j9OR}D9Oo0hs z$8eE;{J5=PT?gjyP;w0#i)Tlox_NGKM>u(PXsU4j06W4Ice*!-52{LGdIx!>F7(%lICFTzWPF^=K)ZD7#DVsuB9t z{2t2kjV@^?+^`R-7uLn~bgUj*PEd)y>3_^h943vAe&E>$c9wu9y{#pjMJ@;<1H;{h z9p)%AEG`&GU?F#b7KkqCG^PITx_woJk>g+>E7S-z36jg1+x}2J%f&>yB*xvKthxiBo>HhEBqME@nbS~)dHyERK@bwODE zqJS3D`izPGo8F}aDTcL^e~T(W1@n)?>Q7_Y8v8VcL(;B+d1&c7&frp_L<99-${n^0 z*7`Xb6|y{BI34$kqCfE{_W&X_3^yh$LD#vg*k-~F%NA%hA`G2tZ3NIYaZoq-YQ*tA z9HY*{PT!VM`*a>**BJy~*iR8P)b@2;(9rh}sGT7PwIfvG!W+;IcT~QM?vl5 zhi^`gDzliyzgDOG#IUxo`~l!$><$O9%m~1f_2gR3lbgb|jBeCy52VyZ+l-gg zm;{1Jd~$EhW|0h|6FEH-dWfk1V%ii-06;grAIy| zSQH?vLp0ZzFtRE&QUx|mD4#puqUM$eqeXqXSFzOW&|5S#*c{!ZwFvsK&LA;D?JxO` zwAivys*)0Gk_K4Xh#ay|S*?)m$`d&?Fo>V5?cH1yN~dE0o=u!{NeY@es(c?zC^dC9xFMm@Rq*b62S z*6Sf!eio%{-`6Pv#%MUqSq4E+FGY5)3kcm*Ef6fJRn6qR01+G4QvYu2+k6k$*55fWz~mA zgh6M95!P0g?{zvVMj5t>C`&rnX^=?z<783Yy!q&voq*n{` zxF>Tq2}`~4bQb>}uO&SS4wnuM5x%fVQvg&v|3j!yG=uE&;*4eOEeJ-<3jl*ae7`19 z2&B)k5n>!shha>QEaja)2P_)~r54ZH%kE2&58g>};(WYaY-C?(WZ0^EDnlPn(690)HV--kh zKW#;Psr4Lc{cW3xd zHGPs|dtG$g;7Tzo!oWuW5O2Dm?+x%|{>;TYbk3o)VazOn{0tojO;A?6a{0v?UJWqG zvKs7(#Y7tOG33;78iD%8883~PnSt#U@M5P2+qt>VHDac4TBxp8SbF8TyU&Q35coEZ z3HTNk8Jd7)D~N$w75L6y{LrFeRs`br1Fje5+%HGhcg5XQEQ?9GjB>eJxD&9ZjMY~i z!+iw%JeWEqTL$}VTcczSN}I)Un9(!yie_eae+Zg60(DD>D2Xp=qQtsN2Pf9m9Y~S8 zo*r1N&x5#4;bL8F4j6e<>0-vZN)t8KReHD)ygdoXv9394Qv{E7H3uK-5x-b4x}+Y0 z^5~5BJJxlsNW2bKn^xVTc<{sFEjwrQ%z+HT<;@&I)ir{cUODO<8CUAqZ8`{IUlO(? zH|Ewd{1)>d7)cF5N^=j%@Y`*5T$;^=hTkRM#SOp3@+R@umBT#jzQxl`(B#5-`~-I2 zB{Ooj-S_LSZ(#Q|&qj%MUyQ4eqTRQ8Y?*~*yDuJBT^0VuF8-s~eT#Xk3RG9B-ml$v zB_x>w0RBy_%jcVInQ0-RK~> zbBx<*UQYp7LSE6H?htRhFtwBB4g+XHaqkiiw7>(MLcFiKe5!*^aT{IQ_ni5{25zjn zGtduQY~DVh({c=L#^d>3SxKqZ%wi1*7U?J{VRH{jNx7mIda;U<_)T0vDQ#lzJ~mH^ z3EG(fLsn0$hf-Z^YxTrRz0O#%Qu?=g^4q8(N(qW(vXYq<}++m(?DFK`1o5k)Lovv0R0= z!*0mcyF1(odvEg84O`~X@<5=C)xytJcrTB2m9eQ)-7{v^dLozLCH6S7c$FTiebxwR z(~eT2NJ>}N* z24TVv|Ez(q>W}p9@Bh?3#)42ux9y8Q&mf8^Y z6C`RqoD@>p@MwNo7?;!=3p=q6cK3P`qlXF(|JIT=(UKC<$NZu2yZu{7mM=L)09+TKA zrdh^Tu9WhIZQoZ4+w?X`!uJoQ^4f+eY}U5b)+YEpN@1%odPfRQG6?^@M$D)`WXO!B ze1R@RC)zk7N_us6{+^IwuiarV5?1eOjxH|=ot|CDOgO=HAeZj#?e552{3#P<(Sn#S zs*krPU+86i};mS?_J+%VCc8p@!RsnVc@nznUEEO4>dPH~mUc)Q4H@5Z~6ldm~xzG7AFI@nz3n^?xYzD}pdSAWNKoVkC%jVAOB7t?Wv(sBd$myvjs z0Tt~1XOnuV&qyk=*#hv5O+|zbMvfHi+}(-AX!=~!QaFbseiOC>c7qFP!2avQFGDK~ zv*d;u)Hf$+5KzjXdJUq6`tqu>i>Vs)V^X7QsH-#x8|o?s z?K&6E;MxhEH+xRsxEf7iIxAnx7i;;nFkRH#5==MKoqNyvvW#ZG?>Q|DeZScmxv`%K z!y(0sWLvdtls1XYwd~@_YG$65Cu?p5c6UD?*O1`6X-AY_`Cs@)+95Zs#7!?N^ng|w*{=%VQeVb`7) z_}NOOSj}2{D`Bm71CKDya9mSdRN(bF3O+C%!67*ie&HCSh zs{%%>YF~&GZV{b#t;OuXy+ox$g!KrALR;!A;E`N`n{^N6b)CMZ9SD89B8tKG0MO?R zGaJ~_Sx2Zb}-AwceTd3q>nPJAC z6B`{3VRuLSKjHl{xNqUNJc5}!2=nxqLmMSY5$&pa6Nl^W&Zx)kQ$4SkA+Bd3y%MvU zxZYxJ+X)3^)NjmQuT5BdpXPv@Kn1QIyYGawoGVmdo!Hmv4TVdDwmqPHcIv5Errso! z_KPpR&xtRGuzp>5B56(Sf>5JSGQ0&!3opd(wwVyl|F}m?2)0=qXX5 z`L(S1HCpB4)G_0Xs(`Ps?*kB#FK|}*j7hsbuWPssb*Gw169Cml#7+kRDnFt zyLm6Fz?6}7E!>)5%H2Sbknt7%twzT%J5)3@0>LmR*62BffX&@3SntD1?a((2vobv1LL!eDKvu2R&8>MA{r zsIJn~iFC+7I$BX(rKlIxReG9HUCro5b(N-eR99)~M-2a^#&LQoQeCB{Bw-@AP?Iq2 zsVPeGQl+6P5o~5wk5#p$*kxPIPLCe&8e>9 zx>H?xyhMGftJM`KT`D|Dh0+g;v?v*wh>I{g0^*82=)j<++TkOj7b=QP55zSe z=7#RGzSu^%d|PUCW7pP~GM^Sgbmskc3O znWD&uD#!dn`OeJl^GRex%CbB~Xnf7z(^8$2Z=@)u4x>N?EJVIa#t%J& zlZJdKAv#%#b(P5@bn)!8M08!HDx~Xb12G+|B_2i}M7DeJ|T#5ZL)UhXM-s$&6 zGuhqI!7BbhvXa2wjfiJfCH2(ArmP#pxkz$Dze_#xZw(`gF2<2ht;VXv@(hMyiYx@e z7$)H!G2)>pgAu6&?qK*lO5?gVMm;Y(AgSfIu8p=ki$n_fT46&{xkbWFnp=WI?(X6J zTB-dWObvn2?gdWAa{{4mxas#y>;Gr(%exb|vVH$nmfeq7+Dfxa#B>7Tt(uk zj9pWb=O;i0#Y~OBL+-lov%fu@BMFQ!gIwiwb={k`4bmC*8TS0!S=}^esr9Ro%4;cI zGqRd#t`mY>vW(@B8p7P)S561WN~rIFDEBS0xx$>RRa`rc1z3GE=4n1Qrv0l~O zzge%3>RLq5n)S>A5!Bq6^;Qpa)x&7kn?BHWOs83j^-ewfFT#2)UAHXlP3Z-*-sJ=Z z%KFJ=ayrI5Sb8c%#xKXYBZsge$9^f*wBqL2o7UEP#IaKcY4gC4AsUO!5}UJ5Xe0*i z39Zawpqz67YPQ|=*rre8MD?4RRUOM5;XB)<2Jnu>1QT7;*j|9H$@2TUCX-~)6Eo;y zvIv#n@pGuY7RR1fvnR}@~poy7{dzO@TKB-g!%)Q&+Dga?Bw;lj@(r<(pElV{LfV8 z#`qU0b7O2;=7vz8u!aS?Fj^RPfz=d=#gJ!sJP@A!AZyta3W(+F+ov6juDQj8$`OSt zv`2%S|M9ATe=T23`;|5!!!H~0+ha>m4>$K4Bj$mlmm<$`5 zNgc*?Svt{b0v9qu00=c1dgGB}_Y^xO3a3F;&gGpz+l?sh#2rb#HcOTBk-3XLl;T< z`U}>0i3$hE9GIyCD@HkkoBLM70rJ&SZhbjpF74J22T12Eqr4y-U~Y#I+H^EqT`n3R z23+AMk!_b`;7>sYFWJnqdjE5&oM~vew2nro=koqft$&nc@=^+ptn2;Xw9H7P_aD`Q z1t7iR+2oVwkx(|5QOF+ve)CP=Yf>O{( z$wRa@4-$vM9&2Q$pwba;|AtY$UP5q_M_>JuG2>8v_QvSiw}0($_98JdZ1J zbKk0|qL?d|N^6S{b7}Yd<%M(ET&ar2iuvXU9@UcOh&d0xG;_ovX=im$MYL=+#~Hp* zGI-j;VP?$C7ZQ+`8SpdMQp}f9gJ45jin(b8fFd$_ZoqA`qG)JKzhFMTyE&?%By*5~!ld!UJvS9C5EXzzjhfl%0FxhxTaxnjiT7e|yeUkYgbCaL`9 z=E(I6a6i|`Zb1;wLYxUMg+hG!EmW?Gb6n`xLTqU+)SOCn;kKu8SOajo^j)!XJkZ=x*fv~k$omOjWGWmQ?I=QHwRH`2d)Kmif`#@tXC90hA;0?P= zvA)-xskCViaa0pX&jcrct9CS*f{j!17ej={ddC>KSoDd2RqMmBL!fGmTjSuY+O6lG1r1$s4RL<@aIQ<6O zM`8=zRn5Ijoc`22Ncz+iPQMOP7zi~+KBuMDO5n|A(re=MGx26C>pVM7|9ql6jnkhu zvLLu*^mIzs`$ktEIuT+rPAU_H)1NEmaf~qIte%^L(;s2c1NrY8#_4|sF)pTD<>!;S z{)KS-&CPNABju=nfvncB9jYel}?hrP>1JzpOQkX&c z2Pzi@q#&`Cs#OkffPYuw1KYNy?^Llb#2@(4PeT}3ckDDf(&^y~9IoB}<0tr^B+jpA z_veWlX+1^=dDYwkabwkcJ?3>BR)@I4D6Y)6}R5#3wma(+dTDd`HwVGSg!52Zl z>G=%p-ms;S)u%P+q5X293CREMF$to;Fe`c~lQ9f4zeaH*ok{J3GAs5FEalW3NZ(H;lG$8yr{{|!n+D=?TRtLE3lQtK^! z&a|*GRGxlSiYpew1Y(2$Q&9`4!3qO$uphLjfz;l6fRz|d8eO-UEy7ks#UqNmKd6Zsp97~i0mT3llSav2iPPdAiyqvkZ=fN zsl~WRWc2(?UpEK|;UGTbJ6+dlBo!-ll3vfnd|{OMFFF$`bqGC$=9+Y7%~Ii=3ErY1 z5k@t)K;mCCwr7?6`<~a&=j2;r?h4%I46~W(YeOc{qEEQMyrJ84l1rCGjj7A8A=q{6_+&4aAjCWwS|4rNLUX^Jn~A;fpT?cz)65SWTjj8-?C7vr?i* z2!xC{bQ({_Hj+pt&3H;E>#+3uAjJs~7aZGjQ55AJh6X^&He{g-Wn4;^07`YnuZ2

_{j_1HkH(l=Nx@vW8ZCfBgOFV;(f7 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�cXIIy1-sZ!r>)!9>=b2ZRS|hN4v9psgq=M=AAa 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%0D_a3=P$;*KR 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

spqwa+wA=8flQxDIN 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�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 (K6|r8@D14+A)9 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

H43AHCjeV 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%0A$%pa9u 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

U%YP)e7)9|h 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 z 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 z�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�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

k!Z8zavt@GKbR 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 B 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

&FOpe6yzly949MUPE}JP5X8r*$ytj04D3W>olxx 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 +BDAY;value=date:1980-05-21 +PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4QBARXhpZgAATU0AKgAA + AAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAyKADAAQAAAABAAAAyAAAAAD/2wBDAA + IBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCw + sLCwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCADIAMgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEA + AAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhBy + JxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZ + WmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxM + XGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAA + AAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFE + KRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm + Z2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyM + nK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKAIN59T+dG8+ + p/OkorkuzMXefU/nRvPqfzpKKLsBd59T+dG8+p/OkqC+u/skZYkDAzyaLsCUyNk/MfzrkPihq9 + 3p9s5sLq4gIjQ5jkK878djWd4p+L8Wg3TI91aJgnhp0XufUe1eY/FX9oSCWCQfbrD/AFaj/j5j + /v8A0q0xn5C/8Fm/20vjH8LvGjRfDP4s/Ezw7Fx8ml+KL6zX/V2Z6RSju7/99H1NflT4i/4Ktf + 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 +UID:0e7602cc-443e-4b82-b4b1-90f62f99a199 +X-ABUID:0E7602CC-443E-4B82-B4B1-90F62F99A199:ABPerson +GEO:-2.600000;3.400000 +CLASS:Public +PROFILE:VCard +TZ:1:00 +LABEL;TYPE=HOME,PARCEL,PREF:John Doe\nNew York\, NewYork\,\nSouth Crecent Dr + ive\,\nBuilding 5\, floor 3\,\nUSA +SORT-STRING:JOHN +ROLE:Counting Money +X-GENERATOR:Cardme Generator +SOURCE:Whatever +MAILER:Mozilla Thunderbird +NAME:VCard for John Doe +X-LONG-STRING:12345678901234567890123456789012345678901234567890123456789012 + 34567890123456789012345678901234567890 +END:VCARD diff --git a/resources/vcards/035.vcf b/resources/vcards/035.vcf new file mode 100644 index 0000000..5b78c66 --- /dev/null +++ b/resources/vcards/035.vcf @@ -0,0 +1,353 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:ddc3671b-3ed4-594c-b23e-fc37bd245a7c +N:Doe;John;Richter\,James;Mr.;Sr. +FN:Mr. John Richter\,James Doe Sr. +NICKNAME:Johny +X-PHONETIC-FIRST-NAME:Jon +X-PHONETIC-LAST-NAME:Dow +ORG:IBM;Accounting +TITLE:Money Counter +EMAIL;type=INTERNET;type=WORK;type=pref:john.doe@ibm.com +TEL;type=WORK;type=pref:905-777-1234 +TEL;type=HOME:905-666-1234 +TEL;type=CELL:905-555-1234 +TEL;type=HOME;type=FAX:905-888-1234 +TEL;type=WORK;type=FAX:905-999-1234 +TEL;type=PAGER:905-111-1234 +item1.TEL:905-222-1234 +item1.X-ABLabel:AssistantPhone +item2.ADR;type=HOME;type=pref:;;Silicon Alley 5\,;New York;New York;12345;United States of America +item2.X-ABADR:Silicon Alley +item3.ADR;type=WORK:;;Street4\nBuilding 6\nFloor 8;New York;;12345;USA +item3.X-ABADR:Street 4, Building 6,\nFloor 8\nNew York\nUSA +NOTE:THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES\, INCLUDING\, BUT NOT LIMITED TO\, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT\, INDIRECT\, INCIDENTAL\, SPECIAL\, EXEMPLARY\, OR CONSEQUENTIAL DAMAGES (INCLUDING\, BUT NOT LIMITED TO\, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES\; LOSS OF USE\, DATA\, OR PROFITS\; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY\, WHETHER IN CONTRACT\, STRICT LIABILITY\, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE\, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\nFavotire Color\: Blue +item4.URL;type=pref:http\://www.ibm.com +item4.X-ABLabel:_$!!$_ +BDAY;value=date:2012-06-06 +PHOTO;BASE64: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBARXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAA + AqACAAQAAAABAAABAKADAAQAAAABAAABAAAAAAD/2wBDAAIBAQIBAQICAQICAgICAwUDAwMDAwYE + BAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkNDg0MDgsMDAv/2wBDAQICAgMCAwUDAwUL + CAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwv/wAAR + CAEAAQADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgED + AwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRol + JicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWW + l5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3 + +Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3 + AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5 + OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaan + qKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR + AxEAPwD9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK + KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigAooooAKKKKACiiigAooooAKKK8t/bU/aq0X9iX9l/xf8AE/x5bzXth4WtBMtpC22S + 9nd1ihhDEHbvlkjUtg7QScHGKaTk0kJtRV2epUV/PP4k/wCDrP8AaGv9buZfDPhn4W6dYPITBbya + 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 + /9k= +item5.X-ABRELATEDNAMES;type=pref:Jenny +item5.X-ABLabel:Spouse +X-ABUID:6B29A774-D124-4822-B8D0-2780EC117F60\:ABPerson +END:VCARD diff --git a/resources/vcards/036.vcf b/resources/vcards/036.vcf new file mode 100644 index 0000000..b0e2526 --- /dev/null +++ b/resources/vcards/036.vcf @@ -0,0 +1,47 @@ +BEGIN:VCARD +VERSION:2.1 +UID:urn:uuid:a04eb0c4-ef5a-538d-970b-c4b204cf8d2d +N;LANGUAGE=en-us:Doe;John;Richter,James;Mr.;Sr. +FN:Mr. John Richter James Doe Sr. +NICKNAME:Johny +ORG:IBM;Accounting +TITLE:Money Counter +NOTE:THIS SOFTWARE IS PROVIDED BY GEORGE EL-HADDAD ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEORGE EL-HADDAD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +TEL;WORK;VOICE:(905) 555-1234 +TEL;HOME;VOICE:(905) 666-1234 +ADR;WORK;PREF:;;Cresent moon drive;Albaney;New York;12345;United States of America +LABEL;WORK;PREF;ENCODING=QUOTED-PRINTABLE:Cresent moon drive=0D=0A= +Albaney, New York 12345 +ADR;HOME:;;Silicon Alley 5,;New York;New York;12345;United States of America +LABEL;HOME;ENCODING=QUOTED-PRINTABLE:Silicon Alley 5,=0D=0A= +New York, New York 12345 +X-MS-OL-DEFAULT-POSTAL-ADDRESS:2 +URL;WORK:http://www.ibm.com +ROLE:Counting Money +BDAY:19800322 +X-MS-ANNIVERSARY:20110113 +EMAIL;PREF;INTERNET:john.doe@ibm.cm +X-MS-IMADDRESS:johny5@aol.com +PHOTO;TYPE=JPEG;ENCODING=BASE64: + /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQY + GBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYa + KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAAR + CAAaACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK + FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG + h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl + 5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREA + AgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYk + NOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE + hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDzS20gtj5a1R4feMqJYnQlQwDAjII4Irub + PQTx8ldNYacfs6213F59sM7R0eL1Kn+nT+dZyk1sQ2zyUaBkfc/SpB4ZOPuc/Su61vSo5tTf + SrZySEDKWQgyFgSowSBgEDdz7Z71S8ITNB4luPD2szQvdvM4t/KYsoGNwUNgZwAecCpVWLfK + NO56ba2sSqpIGD0Na1vDBxwKwYifU1dhJx1NJq5m1cw/Enw9Gt6ydRbXLmNljMcUW0FYgSCc + YI9KXwX4EXwz4hm1STUhdM8RiCtGM4JByWz7DpXQ72/vH86idm/vH86ShZ3uNXtY/9k= + +X-MS-OL-DESIGN;CHARSET=utf-8: +X-MS-MANAGER:Big Blue +X-MS-ASSISTANT:Jenny +REV:20120305T131933Z +END:VCARD diff --git a/resources/vcards/037.vcf b/resources/vcards/037.vcf new file mode 100644 index 0000000..8807116 --- /dev/null +++ b/resources/vcards/037.vcf @@ -0,0 +1,80 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:44e43a63-2b48-575a-a5d7-933fc3eb2a9e +N:LastName;FirstName;MiddleName;Prefix;Suffix +FN:Prefix FirstName MiddleName LastName Suffix +TEL;TYPE=home,voice:555-555-1111 +TEL;TYPE=work,voice:555-555-1112 +TEL;TYPE=cell,voice:555-555-1113 +TEL;TYPE=cell,voice:555-555-1114 +TEL;TYPE=voice:555-555-1115 +TEL;TYPE=home,fax:555-555-1116 +TEL;TYPE=work,fax:555-555-1117 +TEL;TYPE=voice:555-555-1118 +TEL;TYPE=voice:555-555-1119 +EMAIL;TYPE=home:home@example.com +EMAIL;TYPE=work:work@example.com +EMAIL;TYPE=school:school@example.com +EMAIL;TYPE=other:other@example.com +EMAIL;TYPE=customtype:custom@example.com +PHOTO:https://d3m0kzytmr41b1.cloudfront.net/c335e945d1b60edd9d75eb4837c432f + 637e95c8a +PHOTO:https://d3m0kzytmr41b1.cloudfront.net/c335e945d1b60edd9d75eb4837c432f + 637e95c8a +PHOTO:https://d2ojpxxtu63wzl.cloudfront.net/static/aa915d1f29f19baf560e5491 + decdd30a_67c95da9133249fde8b0da7ceebc298bf680117e6f52054f7f5f7a95e8377238 +ORG:Organization1;Department1 +ORG:Organization2;Department2 +TITLE:Title1 +TITLE:Title2 +BDAY;ALTID=1:20160801 +BDAY;ALTID=1;VALUE=text:2016-08-01 +GENDER:M +X-GENDER:male +X-ID:14f9aba0c9422da9ae376fe28bd89c2a.0 +X-ETAG:fffffea9056d8166e2b7a427977e570c87dd51279d11d9b137c593eb +X-FC-TAGS:579c773f-736d-11e6-8dff-0ac8448704fb +X-FC-LIST-ID:8ad23200aa3e1984736b11e688dc0add41994b95 +X-FCENCODED-582D46432D52656C617465644E616D65733A4D6F74686572:Mother +X-FCENCODED-582D46432D52656C617465644E616D65733A466174686572:Father +X-FCENCODED-582D46432D52656C617465644E616D65733A506172656E74:Parent +X-FCENCODED-582D46432D52656C617465644E616D65733A42726F74686572:Brother +X-FCENCODED-582D46432D52656C617465644E616D65733A536973746572:Sister +X-FCENCODED-582D46432D52656C617465644E616D65733A4368696C64:Child +X-FCENCODED-582D46432D52656C617465644E616D65733A467269656E64:Friend +X-FCENCODED-582D46432D52656C617465644E616D65733A53706F757365:Spouse +X-FCENCODED-582D46432D52656C617465644E616D65733A4669616E63C3A9:Fiance +X-FCENCODED-582D46432D52656C617465644E616D65733A506172746E6572:Partner +X-FCENCODED-582D46432D52656C617465644E616D65733A417373697374616E74:Assistan + t +X-FCENCODED-582D46432D52656C617465644E616D65733A4D616E61676572:Manager +X-FCENCODED-582D46432D52656C617465644E616D65733A4F74686572:Other +X-FCENCODED-582D46432D52656C617465644E616D65733A437573746F6D54595045:Custom +X-FCENCODED-582D46432D4F7468657244617465733A416E6E6976657273617279:2016-08- + 02 +X-FCENCODED-582D46432D4F7468657244617465733A4F74686572:2016-08-03 +X-FCENCODED-582D46432D4F7468657244617465733A437573746F6D54595045:2016-08-04 +NOTE:Notes line 1\nNotes line 2 +URL:http://www.homepage.com +URL:http://www.blog.com +URL:http://www.other.com +URL:http://www.custom.com +ADR;TYPE=home:;HomeExtended;HomeStreet;HomeCity;HomeState;HomePostal;HomeCo + untry +ADR;TYPE=work:;WorkExtended;WorkStreet;WorkCity;WorkState;WorkPostal;WorkCo + untry +ADR;TYPE=other:;OtherExtended;OtherStreet;OtherCity;OtherState;OtherPostal; + OtherCountry +ADR;TYPE=customtype:;CustomExtended;CustomStreet;CustomCity;CustomState;Cus + tomPostal;CustomCountry +NICKNAME:NickName +IMPP;X-SERVICE-TYPE=GTalk:xmpp:gtalk +IMPP;X-SERVICE-TYPE=Skype:skype:skype +IMPP;X-SERVICE-TYPE=Yahoo:ymsgr:yahoo +IMPP;X-SERVICE-TYPE=AIM:aim:aim +IMPP;X-SERVICE-TYPE=Jabber:xmpp:jabber +IMPP;X-SERVICE-TYPE=Other:other:other +IMPP;X-SERVICE-TYPE=CustomTYPE:customtype:custom +CATEGORIES:Tag +PRODID:ez-vcard 0.9.14-fc +END:VCARD diff --git a/resources/vcards/040.vcf b/resources/vcards/040.vcf new file mode 100644 index 0000000..d5e62a2 --- /dev/null +++ b/resources/vcards/040.vcf @@ -0,0 +1,92 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:887ee0fa-fccf-520e-af1a-ec028982989c +FN:VCard Test +N:Test;VCard;;; +NICKNAME:TheNickname +X-PHONETIC-FIRST-NAME:ThePhoneticFirstName +X-PHONETIC-LAST-NAME:ThePhoneticLastName +EMAIL;TYPE=INTERNET:email@example.com +EMAIL;TYPE=INTERNET;TYPE=HOME:homeemail@example.com +EMAIL;TYPE=INTERNET;TYPE=WORK:workemail@example.com +EMAIL;TYPE=INTERNET:otheremail@example.com +item1.EMAIL;TYPE=INTERNET:customcategory@example.com +item1.X-ABLabel:CustomEmailCategory +X-GTALK:IM2 +X-AIM:IM3 +X-YAHOO:IM4 +X-SKYPE:IM5 +X-QQ:IM6 +X-MSN:IM7 +X-ICQ:IM8 +X-JABBER:IM9 +TEL:5555551111 +TEL;TYPE=HOME:5555551112 +TEL;TYPE=WORK:5555551113 +TEL:5555551114 +TEL;TYPE=CELL:5555551115 +TEL;TYPE=MAIN:5555551116 +TEL;TYPE=HOME;TYPE=FAX:5555551117 +TEL;TYPE=WORK;TYPE=FAX:5555551118 +item2.TEL:5555551119 +item2.X-ABLabel:GRAND_CENTRAL +TEL;TYPE=PAGER:5555551120 +item3.TEL:5555551121 +item3.X-ABLabel:CustomePhoneCategory +ADR:;;111 Main St;NY;New York;10011; +ADR;TYPE=HOME:;;112 Main St;NY;New York;10011; +ADR;TYPE=WORK:;;113 Main St;NY;New York;10011; +ADR:;;114 Main St;NY;New York;10011; +item4.ADR:;;115 Main St;NY;New York;10011; +item4.X-ABLabel:CustomAddressCategory +ORG:TheCompany +TITLE:TheJobTitle +BDAY:1912-06-23 +URL:http\://www.example1.com +item5.URL:http\://www.example2.com +item5.X-ABLabel:PROFILE +item6.URL:http\://www.example3.com +item6.X-ABLabel:BLOG +item7.URL:http\://www.example4.com +item7.X-ABLabel:_$!!$_ +URL;TYPE=WORK:http\://www.example5.com +item8.URL:http\://www.example6.com +item8.X-ABLabel:CustomWebsiteCategory +item9.X-ABDATE:1930-03-20 +item9.X-ABLabel:_$!!$_ +X-ABDATE:1776-07-04 +item10.X-ABDATE:2000-09-12 +item10.X-ABLabel:CustomDateCategory +X-ABRELATEDNAMES:Name1 +item11.X-ABRELATEDNAMES:Name2 +item11.X-ABLabel:_$!!$_ +item12.X-ABRELATEDNAMES:Name3 +item12.X-ABLabel:_$!!$_ +item13.X-ABRELATEDNAMES:Name4 +item13.X-ABLabel:_$!!$_ +item14.X-ABRELATEDNAMES:Name5 +item14.X-ABLabel:_$!!$_ +item15.X-ABRELATEDNAMES:Name6 +item15.X-ABLabel:_$!!$_ +item16.X-ABRELATEDNAMES:Name7 +item16.X-ABLabel:_$!!$_ +item17.X-ABRELATEDNAMES:Name8 +item17.X-ABLabel:_$!!$_ +item18.X-ABRELATEDNAMES:Name9 +item18.X-ABLabel:_$!!$_ +item19.X-ABRELATEDNAMES:Name10 +item19.X-ABLabel:RELATIVE +item20.X-ABRELATEDNAMES:Name11 +item20.X-ABLabel:_$!!$_ +item21.X-ABRELATEDNAMES:Name12 +item21.X-ABLabel:_$!!$_ +item22.X-ABRELATEDNAMES:Name13 +item22.X-ABLabel:REFERRED_BY +item23.X-ABRELATEDNAMES:Name14 +item23.X-ABLabel:_$!!$_ +item24.X-ABRELATEDNAMES:Name15 +item24.X-ABLabel:DOMESTIC_PARTNER +item25.X-ABRELATEDNAMES:Name16 +item25.X-ABLabel:CustomRelationCategory +NOTE:note line 1\nnote line 2\nCustomField: field value +END:VCARD diff --git a/resources/vcards/042.vcf b/resources/vcards/042.vcf new file mode 100644 index 0000000..d22e984 --- /dev/null +++ b/resources/vcards/042.vcf @@ -0,0 +1,94 @@ +BEGIN:VCARD +VERSION:2.1 +UID:urn:uuid:e5421786-42af-5192-b326-011c7e68d2e7 +N;LANGUAGE=en-us:Angstadt;Michael;;Mr.;Jr. +FN:Mr. Michael Angstadt Jr. +NICKNAME:Mike +ORG:TheCompany;TheDepartment +TITLE:TheJobTitle +NOTE;CHARSET=us-ascii;ENCODING=QUOTED-PRINTABLE:This is the NOTE field =0D=0A= +I assume it encodes this text inside a NOTE vCard type.=0D=0A= +But I'm not sure because there's text formatting going on here.=0D=0A= +It does not preserve the formatting +TEL;WORK;VOICE:(111) 555-1111 +TEL;HOME;VOICE:(111) 555-2222 +TEL;CELL;VOICE:(111) 555-4444 +TEL;WORK;FAX:(111) 555-3333 +X-MS-TEL;VOICE;CALLBACK:(111) 555-4444 +ADR;WORK;PREF:;TheOffice;222 Broadway;New York;NY;99999;USA +LABEL;WORK;PREF;ENCODING=QUOTED-PRINTABLE:222 Broadway=0D=0A= +New York, NY 99999=0D=0A= +USA +X-MS-OL-DEFAULT-POSTAL-ADDRESS:2 +URL;HOME:http://mikeangstadt.name +URL;WORK:http://mikeangstadt.name +ROLE:TheProfession +BDAY:19220310 +X-MS-ANNIVERSARY:20120801 +KEY;X509;ENCODING=BASE64: + MIIB/jCCAWugAwIBAgIQDdkWkvA2cqtGkw2P4zAoZDAJBgUrDgMCHQUAMBMxETAPBgNVBAMT + CG1hbmdzdGFkMCAXDTExMDYxNzIwMTgwM1oYDzIxMTEwNTI0MjAxODAzWjATMREwDwYDVQQD + EwhtYW5nc3RhZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmfYuhkT693IXf/iSESbw + 9cmeM3p2AWDC8oGDiCDtenNIGdITa8e/kwx9JIFl7a02cMbHtG6BmbGBINzzgepsY6lE+Pcx + ns4FVpgWr1hn3YVg2LKqgRkKLp+ddqL+LWCg3qvcQxnZJoooRLhpurr/T9TaE83nAr6aqoSt + jYh+KtECAwEAAaNZMFcwFQYDVR0lBA4wDAYKKwYBBAGCNwoDBDAzBgNVHREELDAqoCgGCisG + AQQBgjcUAgOgGgwYbWFuZ3N0YWRAcGVubm11dHVhbC5jb20AMAkGA1UdEwQCMAAwCQYFKw4D + Ah0FAAOBgQA9/txKYVIj6/UoDRYAp23NoRjaeZ0cTqcpKtxlUi8z7TtczAROgTr4Pi+VaKR0 + Y+RzgVxPN8wvNoVogCWLTpVxhoe8R5geoS4L1w1O1HYwu2KlG57SOR4R/Lv5bgi6CNTlS1N4 + zIDBOSyypTmzxC+JPmyn/8vZleIz8CYnwmfBEg== + +EMAIL;PREF;INTERNET:mike.angstadt@gmail.com +X-MS-IMADDRESS:im@aim.com +PHOTO;TYPE=JPEG;ENCODING=BASE64: + /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQY + GBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYa + KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAAR + CABWAEgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK + FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG + h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl + 5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREA + AgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYk + NOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE + hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD6pooqlrOpQaTplxe3TBYolLH3PYUAQeIN + e0/QLM3Op3CxJztXqzH0Arx3xN8Zb5ndNEtBDHztlkj3tx04yB/OuM8Y6zqHiXU3ubhyImbC + J6JnGB6cE/lXJT2rbJHaZiioSrZPIBxk/oB7UhXOsvPir4ofJbU2jyeP3axgZHqM96NP+NXi + awZGmuIruI9FnjHzDnJyMfTr2rzq7WdI2wR5YVcg+5yBWTPIjxsI8K27lD0b5hhR/ntSBH1h + 4H+NWg6+Y7fUz/Zt43A8w/u3Ps3b8a9UVg6hlIKkZBHevzp80oeCyyLwc9f/ANXFfS37OnxF + lv2Hh3V53klC/wCjO5zjH8Oadyj6BooopiCvK/jXqjbbTTI2wp/eyDHX0r1SvAvHkp1/4hG0 + hkGwN5e7HQL1xUydlcaTk7I4o2tzds0dnA0rnPygdeO3tk/yqC/8L6skC5s5zBgAsyc4A546 + 5J56ete22NjDp9msVpGqKPTq3uT3qpqjMbYk4/GuZ130R2wwcerPm/Ura4RvKu0MaZ8xw4OC + ewAI5rmNSbICP8owWbjJyc8V7l4kMLRSJLGHX35ryDxHbRRXDeQDtLbwg7npjNXTrc+jM6+F + 9lqndHOM2X8lyBKn3HyMZ5PPrWr4Y1ObR9atL62ZlmgcMoHqOucVjSKZJJA7DzAeGHdj1/Kp + reTcVckZJ2tx39a2OdH6FeGdUTW9AsNSi4W5hWTHoSOlFcH+zrqDX3w3tY3JJtpGiGfTqP0N + FNEs9G1CcWthcTkgeXGzZPsK+TPtc+o+I7q7F41p5KtNNOoyRk19TeK4TP4a1OIAsWt3AAOM + 8V84/B23S+vNaaYCVflRtwzz1rGvOysdOGp80r9iz4E1u/vtbeFNTury0U4Pnw7cfjTfir4g + udPnj06znKTufvDtXfSWVrpQadUVMAscKB/KvINVjj17X5p5XyfM+U1yc2p6Sg+U4+aa/eDc + /iKLzmBIidTyPr0rLjW4uInjumBlzlHXoa9dvtGt722WN4oYpVTZkRDp6Vx17oUGlqQpJ54F + XGomZTotbnnEhJlZnG2VXKsvp9KjdWS6YEYV1zx6itTXjFb6tGUPMq4YD17Gqd0jEcFTIgDj + aMgV1xd1c82ceWVj6q/ZWvDN4T1G2OB5VwGA9mUf4UVl/spQSeTrNwM+QyxIAePm+Y9PoRRV + rYhnumvME0W/YjIED8fga+U/hh4nttA1W5jvTstbnqcdGHevrHVE36bdr6xMP0NfBmqyG3nO + 5mVzJtIC8nk1zYhNtHdg2knc+g/GPjLRYdInkWdLhimUVDkn8K8CsvEbnW1uLaGZWaUHyivB + Gaz55jLIBCHlRDmRxk7eOO3FZ812sLh4pBCeTgZLD0/kaxgu51ylbbY+lNUlgXT0nUAbkz9K + 8c8Ta8vny/NnbnAri7vxJqw06ANf3DIzHhnOMVm3FwTqPlyOxjde/XPvVQptasipWTVkSKzX + DXF1cN+9dsIPat6ztlXT4Ccl8nfk5OD0rmdPR5NqLkoGJGfSvafhR8M9V8YtDcTh7fRww3zN + xuA6hfU9q6I7nBVd0j3H9m3THsPh1HPIuDdzNKOP4R8o/lRXpmmWMGm6fb2Vmgjt4EEaKOwF + Fao5ywyhlKsMgjBFfFXxd8NT6J4vuYpo2gjkuDNGV6MpJIwfSvs37V+7d9nCvsrjfix4Mt/G + WhNHEY01G3+aGQ/+gn2NZ1YcyuuhvQqckrPZnx9pGt32kia4sJ1jEpCyBD1xkc5Hb15qjr2s + 3FyIZbqKAzOx5ZRxjqff/wCtVjxHot5pNpc6dcwNBNHk8qQTg8/596w9ahVoE2BR5JyyA56/ + 5Nc0Y3Z6E5tIg1Zla4tosRgL8xUdvas2QA3UzZLbVwKluiGuY5UUsWHAHOK6Lw7oB8z7RfYA + J3bD3HvWq91HO/ekeofs8eAtK8RXWNajlaPy/ORAQNwVhwfY9/pX15bQRW0EcFvGkUMahVRB + gKB2FfNHwf1iDSPFtm07JFbSKYSTwFyOPwzivpW2ure6TfbTxTL6xuGH6VdF3RhiI2kiaiii + tjnKT27CZhuHkk7yO+aoi4Ky7VXJZWGWPeiikwMPxPp8F/Z2r3ltbyuj4yyA8Y6dK8q8WeBt + LmilNva28A9FX8aKK5anxHbQfuHkV/oiaddOgKlQeMCkB5CrwDRRSZrHc2LNWYRxocMxCgmu + 00nUbzQZ4nsbmRHgQkkH7x9xRRVUu5liHsjv9L+LksMSLq2niUjhpIG2k++Dx/Kiiit7s5Gj + /9k= + +X-MS-OL-DESIGN;CHARSET=utf-8: +X-MS-MANAGER:TheManagerName +X-MS-ASSISTANT:TheAssistantName +X-MS-SPOUSE:TheSpouse +FBURL:http://website.com/mycal +REV:20120801T184631Z +END:VCARD diff --git a/resources/vcards/045.vcf b/resources/vcards/045.vcf new file mode 100644 index 0000000..9d65d23 --- /dev/null +++ b/resources/vcards/045.vcf @@ -0,0 +1,204 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:8c11cf62-42b4-5513-9d35-1318e6cb0540 +N;CHARSET=UTF-8:Doe;John +FN;CHARSET=UTF-8:John Doe +ORG;CHARSET=UTF-8:TheOrganization;TheDepartment +NICKNAME;CHARSET=UTF-8:Johnny +ADR;TYPE=WORK,POSTAL;CHARSET=UTF-8:;222 Broadway;Suite 100;New York;NY;98765;USA +ADR;TYPE=HOME,POSTAL;CHARSET=UTF-8:;123 Main St;Apt 10;Austin;TX;12345;USA +TEL;TYPE=WORK,VOICE:555-555-1111 +TEL;TYPE=HOME,VOICE:555-555-2222 +TEL;TYPE=CELL,VOICE:555-555-5555 +TEL;TYPE=FAX:555-555-3333 +TEL;TYPE=PAGER:555-555-4444 +EMAIL;TYPE=PREF,INTERNET:doe.john@hotmail.com +EMAIL;TYPE=INTERNET:additional-email@company.com +EMAIL;TYPE=INTERNET:additional-email1@company.com +EMAIL;TYPE=INTERNET:additional-email2@company.com +EMAIL;TYPE=INTERNET:additional-email3@company.com +URL;TYPE=HOME:http://www.private-webpage.com +TITLE;CHARSET=UTF-8:TheTitle +URL;TYPE=WORK:http://www.work-webpage.com +CATEGORIES;CHARSET=UTF-8:category1\, category2\, category3 +X-SPOUSE:TheSpouse +BDAY:1970-09-21 +X-ANNIVERSARY:1990-04-30 +NOTE;CHARSET=UTF-8:This is the notes field.\nSecond Line\n\nFourth Line\nYou can put anything in the "note" field\; even curse words. +PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQEAYABgAAD/4QBoRXhpZgAASUk + qAAgAAAAEABoBBQABAAAAPgAAABsBBQABAAAARgAAACgBAwABAAAAAgAAADEBAgASAAA + ATgAAAAAAAABgAAAAAQAAAGAAAAABAAAAUGFpbnQuTkVUIHYzLjUuMTAA/9sAQwACAQE + BAQECAQEBAgICAgIEAwICAgIFBAQDBAYFBgYGBQYGBgcJCAYHCQcGBggLCAkKCgoKCgY + ICwwLCgwJCgoK/9sAQwECAgICAgIFAwMFCgcGBwoKCgoKCgoKCgoKCgoKCgoKCgoKCgo + KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoK/8AAEQgAhABkAwEiAAIRAQMRAf/EAB8 + AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQE + CAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU + 2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpe + YmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fL + z9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQ + EAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDT + hJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoK + DhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19j + Z2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+PNL8D3+jRfZbPxprEUiE/v + EmBHX0AH8605Lj4hWURGlfEO7LjAT7TDvGff5jXvP7I37F+v/ALXN5r/9ieNoNIGjPDv + 8+xMol80PjG1htxs9+orA/ak/Zn8Tfss/Ea3+H3ijX7LUnubKK8huLGJ1XYzsoBDDIOU + J79a/K/ZYqVL2zXu/I+yWNTq+z5te1jjfgH4x8T+NvDd/f+Jb7z5INSaCNhGq4UIp/hA + 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 +END:VCARD diff --git a/resources/vcards/046.vcf b/resources/vcards/046.vcf new file mode 100644 index 0000000..173bff2 --- /dev/null +++ b/resources/vcards/046.vcf @@ -0,0 +1,14 @@ +BEGIN:VCARD +VERSION:4.0 +FN:Dummy\, Dummy +N:Dummy;Dummy;;; +ORG:Dummy GmbH +TEL;TYPE=cell;PREF=1:+49 1234 56789 +TEL;TYPE=work:+49 9876 54321 +EMAIL;TYPE=home:dummy.dummy@dummy.com +ADR;TYPE=work;LABEL=Dummy-Dummy-Strasse 1 61352 Bad Homburg^nGERMANY^': BH + G01:^n61352 Bad Homburg^nGERMANY:61352 Bad Homburg\nGERMANY:;BHG01:;Dumm + y-Dummy-Strasse 1;Bad Homburg;;61352;Germany +REV;VALUE=DATE-AND-OR-TIME:20210314T092838Z +UID:8b574c60-fd7f-4e99-b584-c5db131ae687 +END:VCARD diff --git a/resources/vcards/048.vcf b/resources/vcards/048.vcf new file mode 100644 index 0000000..4d7f4b9 --- /dev/null +++ b/resources/vcards/048.vcf @@ -0,0 +1,55 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:7511bfa7-f1f2-53cf-8b7d-b5362dcc0dd7 +FN:Mr. John Q. Public\, Esq. +N:Public;John;Quinlan;Mr.;Esq. +N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P. +NICKNAME:Robbie +NICKNAME:Jim,Jimmie +PHOTO;VALUE=uri:http://www.abc.com/pub/photos + /jqpublic.gif +PHOTO;ENCODING=b;TYPE=JPEG:SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBp + bWFnZSBqdXN0IGEgdGVzdC4= +BDAY:1996-04-15 +BDAY:1953-10-15T23:10:00Z +BDAY:1987-09-27T08:30:00-06:00 +ADR;TYPE=dom,home,postal,parcel:;;123 Main + Street;Any Town;CA;91921-1234 +LABEL;TYPE=dom,home,postal,parcel:Mr.John Q. Public\, Esq.\n + Mail Drop: TNE QB\n123 Main Street\nAny Town\, CA 91921-1234 + \nU.S.A. +TEL;TYPE=work,voice,pref,msg:+1-213-555-1234 +EMAIL;TYPE=internet:jqpublic@xyz.dom1.com +EMAIL;TYPE=internet:jdoe@isp.net +EMAIL;TYPE=internet,pref:jane_doe@abc.com +MAILER:PigeonMail 2.1 +TZ:-05:00 +TZ;VALUE=text:-05:00\; EST\; Raleigh/North America + \;This example has a single value\, not a structure text value. +GEO:37.386013;-122.082932 +TITLE:Director\, Research and Development +ROLE:Programmer +LOGO;VALUE=uri:http://www.abc.com/pub/logos/abccorp.jpg +LOGO;ENCODING=b;TYPE=JPEG:SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBp + bWFnZSBqdXN0IGEgdGVzdC4= +AGENT;VALUE=uri: + CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com +AGENT:BEGIN:VCARD\nFN:Susan Thomas\nTEL:+1-919-555- + 1234\nEMAIL\;INTERNET:sthomas@host.com\nEND:VCARD\n +ORG:ABC\, Inc.;North American Division;Marketing +CATEGORIES:TRAVEL AGENT +CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY +NOTE:This fax number is operational 0800 to 1715 + EST\, Mon-Fri. +PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN +REV:1995-10-31T22:27:10Z +REV:1997-11-15 +SORT-STRING:Harten +SOUND;TYPE=BASIC;VALUE=uri:CID:JOHNQPUBLIC.part8. + 19960229T080000.xyzMail@host1.com +SOUND;TYPE=BASIC;ENCODING=b:VGhlcmUgaXMgbm8gc291bmQgaW4gc3BhY2U= +CLASS:PUBLIC +CLASS:PRIVATE +CLASS:CONFIDENTIAL +KEY;ENCODING=b:Tm90IHRoZSBrZXkgeW91IGFyZSBsb29raW5nIGZvcg== +END:VCARD diff --git a/resources/vcards/051.vcf b/resources/vcards/051.vcf new file mode 100644 index 0000000..04d7189 --- /dev/null +++ b/resources/vcards/051.vcf @@ -0,0 +1,23 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 4.1.6//EN +UID:ad612c16-fe12-4ec5-abf6-49998ee5ab88 +FN:First Last NextCloud +ADR;TYPE=HOME:PO;Street 2;Street;City;AL;zip;Germany +ADR;TYPE=WORK:PO W;Street 2 W;Street W;City Work;AL work;zip Work;Germany w + ork +EMAIL;TYPE=HOME:home@gmx.de +EMAIL;TYPE=WORK:work@gmx.de +TEL;TYPE="HOME,VOICE":11111111 +TEL;TYPE=CELL:205333 +TEL;TYPE="WORK,FAX":205246\;\;\,\; +TEL;TYPE="WORK,VOICE":222222 +ORG:Firma +BDAY:20190210T000033 +NICKNAME:Hugo +ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20190220T000035 +ITEM1.X-ABLABEL:_$!!$_ +X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20190220T000035 +CATEGORIES:Test-Kontakte +REV:20191008T170514Z +END:VCARD diff --git a/resources/vcards/066.vcf b/resources/vcards/066.vcf new file mode 100644 index 0000000..dd811af --- /dev/null +++ b/resources/vcards/066.vcf @@ -0,0 +1,41 @@ +BEGIN:VCARD +VERSION:3.0 +REV:20080409T095515Z +X-YAHOO;TYPE=HOME:yahoo.john +X-GOOGLE TALK;TYPE=WORK:gtalk.john +X-SAMETIME;TYPE=WORK:sametime.john +X-SKYPE;TYPE=WORK:skype.john +X-MSN;TYPE=WORK:msn.john +X-JABBER;TYPE=WORK:jabber.john +N:Doe;John;;; +ADR;TYPE=WORK:;;456 Grandview Building\, Wide Street;San Diego;CA;90204; + United States +ADR;TYPE=HOME:;;123 Sweet Home\, Narrow Street;New York;NY;91102;United + States +URL;TYPE=OTHER:http\://www.homepage.com +URL;TYPE=HOME:http\://www.home.com +URL;TYPE=WORK:http\://www.work.com +URL;TYPE=OTHER:http\://www.other.com +URL;TYPE=OTHER:http\://www.custom.com +ORG:John Doe & Partners Limited;; +TEL;TYPE=WORK:11111111 +TEL;TYPE=CELL:22222222 +TEL;TYPE=HOME:33333333 +TEL;TYPE=OTHER:44444444 +TEL;TYPE=FAX:55555555 +TEL;TYPE=FAX:66666666 +TEL;TYPE=PAGER:77777777 +TEL;TYPE=OTHER:88888888 +TEL;TYPE=OTHER:99999999 +UID:cc548e11-569e-3bf5-a9aa-722de4571f4a +X-ICQ;TYPE=HOME:icq.john +EMAIL;TYPE=WORK,INTERNET:john.doe@work.com +EMAIL;TYPE=HOME,INTERNET:john.doe@home.com +EMAIL;TYPE=OTHER,INTERNET:john.doe@other.com +EMAIL;TYPE=OTHER,INTERNET:john.doe@custom.com +TITLE:Sales Manager +X-OTHER;TYPE=WORK:other.john +X-AIM;TYPE=WORK:aim.john +X-QQ;TYPE=WORK:qq.john +FN:John Doe +END:VCARD diff --git a/resources/vcards/rfc-a.vcf b/resources/vcards/rfc-a.vcf new file mode 100644 index 0000000..484efc3 --- /dev/null +++ b/resources/vcards/rfc-a.vcf @@ -0,0 +1,62 @@ +BEGIN:VCARD +VERSION:4.0 +NOTE:Mythical Manager\nHyjinx Software Division\n + BabsCo\, Inc.\n +ROLE;LANGUAGE=tr:hoca +TITLE;ALTID=2;LANGUAGE=en:Chief vCard Evangelist +N;ALTID=1;LANGUAGE=en:Yamada;Taro;;; +N;SORT-AS="Harten,Rene":van der Harten;Rene,J.;Sir;R.D.O.N. +N;SORT-AS="Pozo,Oscar":del Pozo Triscon;Oscar;; +SOURCE:ldap://ldap.example.com/cn=Babs%20Jensen,%20o=Babsco,%20c=US +SOURCE:http://directory.example.com/addressbooks/jdoe/ + Jean%20Dupont.vcf +KIND:individual +N:Mr. John Q. Public\, Esq. +N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P. +NICKNAME;TYPE=work:Boss +PHOTO:data:image/jpeg;base64,MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhv + AQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bm + ljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0 +BDAY:19960415 +BDAY:--0415 +BDAY:19531015T231000Z +BDAY;VALUE=text:circa 1800 +GENDER:M +GENDER:F +GENDER:M;Fellow +GENDER:F;grrrl +GENDER:O;intersex +GENDER:;it's complicated +ADR;GEO="geo:12.3457,78.910";LABEL="Mr. John Q. Public, Esq.\n + Mail Drop: TNE QB\n123 Main Street\nAny Town, CA 91921-1234\n + U.S.A.":;;123 Main Street;Any Town;CA;91921-1234;U.S.A. +TEL;VALUE=uri;PREF=1;TYPE="voice,home":tel:+1-555-555-5555;ext=5555 +TEL;VALUE=uri;TYPE=home:tel:+33-01-23-45-67 +EMAIL;TYPE=work:jqpublic@xyz.example.com +IMPP;PREF=1:xmpp:alice@example.com +LANG;TYPE=work;PREF=1:en +LANG;TYPE=work;PREF=2:fr +TZ;VALUE=utc-offset:-0500 +GEO:geo:37.386013,-122.082932 +LOGO:http://www.example.com/pub/logos/abccorp.jpg +ORG:ABC\, Inc.;North American Division;Marketing +RELATED;TYPE=friend:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 +RELATED;TYPE=contact:http://example.com/directory/jdoe.vcf +RELATED;TYPE=co-worker;VALUE=text:Please contact my assistant Jane + Doe for any inquiries. +CATEGORIES:TRAVEL AGENT +CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY +NOTE:This fax number is operational 0800 to 1715 + EST\, Mon-Fri. +PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN +REV:19951031T222710Z +SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com +UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 +TEL;PID=3.1,4.2;VALUE=uri:tel:+1-555-555-5555 +EMAIL;PID=4.1,5.2:jdoe@example.com +CLIENTPIDMAP:1;urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b +CLIENTPIDMAP:2;urn:uuid:d89c9c7a-2e1b-4832-82de-7e992d95faa5 +KEY;MEDIATYPE=application/pgp-keys:ftp://example.com/keys/jdoe +FBURL;MEDIATYPE=text/calendar:ftp://example.com/busy/project-a.ifb +CALADRURI;PREF=1:mailto:janedoe@example.com +END:VCARD diff --git a/resources/vcards/rfc-b.vcf b/resources/vcards/rfc-b.vcf new file mode 100644 index 0000000..10c2ccc --- /dev/null +++ b/resources/vcards/rfc-b.vcf @@ -0,0 +1,7 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:3c380a90-9a23-59bc-b228-64e45cc23475 +EMAIL;PID=4.2,5.1:jdoe@example.com +CLIENTPIDMAP:1;urn:uuid:3eef374e-7179-4196-a914-27358c3e6527 +CLIENTPIDMAP:2;urn:uuid:42bcd5a7-1699-4514-87b4-056edf68e9cc +END:VCARD diff --git a/resources/vcards/rfc-c.vcf b/resources/vcards/rfc-c.vcf new file mode 100644 index 0000000..3145a9b --- /dev/null +++ b/resources/vcards/rfc-c.vcf @@ -0,0 +1,7 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:9f501a4e-a0df-5d90-8ff9-64e811b8ec15 +EMAIL;PID=5.1,5.2:john@example.com +CLIENTPIDMAP:1;urn:uuid:0c75c629-6a8d-4d5e-a07f-1bb35846854d +CLIENTPIDMAP:2;urn:uuid:3eef374e-7179-4196-a914-27358c3e6527 +END:VCARD diff --git a/resources/vcards/rfc-d.vcf b/resources/vcards/rfc-d.vcf new file mode 100644 index 0000000..361a4fe --- /dev/null +++ b/resources/vcards/rfc-d.vcf @@ -0,0 +1,9 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1 +FN;PID=1.1:J. Doe +N:Doe;J.;;; +EMAIL;PID=1.1:jdoe@example.com +TEL;PID=1.1;VALUE=uri:tel:+1-555-555-5555 +CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0556 +END:VCARD diff --git a/resources/vcards/rfc-e.vcf b/resources/vcards/rfc-e.vcf new file mode 100644 index 0000000..daf92bf --- /dev/null +++ b/resources/vcards/rfc-e.vcf @@ -0,0 +1,12 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:d133e5b7-baa2-5520-9766-965ca3835985 +FN;PID=1.1:J. Doe +N:Doe;J.;;; +EMAIL;PID=1.1:jdoe@example.com +EMAIL;PID=2.2:ceo@example.com +TEL;PID=1.1;VALUE=uri:tel:+1-555-555-5555 +TEL;PID=2.2;VALUE=uri:tel:+1-666-666-6666 +CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0556 +CLIENTPIDMAP:2;urn:uuid:1f762d2b-03c4-4a83-9a03-75ff658a6eee +END:VCARD diff --git a/resources/vcards/rfc-f.vcf b/resources/vcards/rfc-f.vcf new file mode 100644 index 0000000..6921898 --- /dev/null +++ b/resources/vcards/rfc-f.vcf @@ -0,0 +1,8 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:7c9ba2ad-f8ab-5747-9738-ed156bad9270 +KIND:group +FN:The Doe family +MEMBER:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af +MEMBER:urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519 +END:VCARD diff --git a/resources/vcards/rfc-g.vcf b/resources/vcards/rfc-g.vcf new file mode 100644 index 0000000..7988f42 --- /dev/null +++ b/resources/vcards/rfc-g.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +UID:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af +END:VCARD diff --git a/resources/vcards/rfc-h.vcf b/resources/vcards/rfc-h.vcf new file mode 100644 index 0000000..e5f9dbe --- /dev/null +++ b/resources/vcards/rfc-h.vcf @@ -0,0 +1,10 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:e5b9689c-02bd-5c95-a857-bad3b5bd8cf4 +KIND:group +FN:Funky distribution list +MEMBER:mailto:subscriber1@example.com +MEMBER:xmpp:subscriber2@example.com +MEMBER:sip:subscriber3@example.com +MEMBER:tel:+1-418-555-5555 +END:VCARD diff --git a/resources/vcards/rfc-i.vcf b/resources/vcards/rfc-i.vcf new file mode 100644 index 0000000..ed4290c --- /dev/null +++ b/resources/vcards/rfc-i.vcf @@ -0,0 +1,22 @@ +BEGIN:VCARD +VERSION:4.0 +UID:urn:uuid:61bbee4f-102c-5d99-8ed2-5543c8f44b8d +FN:Simon Perreault +N:Perreault;Simon;;;ing. jr,M.Sc. +BDAY:--0203 +ANNIVERSARY:20090808T1430-0500 +GENDER:M +LANG;PREF=1:fr +LANG;PREF=2:en +ORG;TYPE=work:Viagenie +ADR;TYPE=work:;Suite D2-630;2875 Laurier; + Quebec;QC;G1V 2M2;Canada +TEL;VALUE=uri;TYPE=work,voice;PREF=1:tel:+1-418-656-9254;ext=102 +TEL;VALUE=uri;TYPE=work,cell,voice,video,text:tel:+1-418-262-6501 +EMAIL;TYPE=work:simon.perreault@viagenie.ca +GEO;TYPE=work:geo:46.772673,-71.282945 +KEY;TYPE=work;VALUE=uri: + http://www.viagenie.ca/simon.perreault/simon.asc +TZ:-0500 +URL;TYPE=home:http://nomis80.org +END:VCARD diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..ccccf87 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,1364 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::path::PathBuf; + +use clap::{ArgAction, Args, Parser, Subcommand}; +use regex::Regex; + +use crate::error::Error; +use crate::exchange_ews::oauth::OAuthFlow; +use crate::exchange_ews::types::MailboxKind; +use crate::exchange_graph::types::{EventBodyFormat, MailboxKind as GraphMailboxKind}; +use crate::inspect::InspectConfig; +use crate::jmap::account::AccountSelector; +use crate::jmap::http::Auth; +use crate::logging::Logger; +use crate::secret; +use crate::sync::import_dav::{DavAuth, DavImportConfig, DavKindArg}; +use crate::sync::import_exchange_ews::{EwsAuth, EwsImportConfig}; +use crate::sync::import_exchange_graph::{GraphAuth, GraphImportConfig}; +use crate::sync::import_imap::{ImapAuth, ImapImportConfig}; +use crate::sync::import_maildir::MaildirImportConfig; +use crate::sync::import_managesieve::{ManageSieveAuth, ManageSieveImportConfig}; +use crate::sync::import_takeout::TakeoutImportConfig; +use crate::sync::{CommonConfig, ConnectConfig, ExportConfig, ImportConfig}; +use crate::types::{ObjectType, parse_object_list}; + +#[derive(Parser)] +#[command( + name = "vandelay", + version, + about = "Vandelay: the JMAP importer-exporter" +)] +pub struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + #[command(about = "Read a source account into a local archive")] + Import { + #[command(subcommand)] + source: Source, + }, + #[command(about = "Push a local archive into a target JMAP server")] + Export(Box), + #[command(about = "Show the contents of a local archive (read-only)")] + Inspect(Box), +} + +#[derive(Subcommand)] +enum Source { + #[command(about = "JMAP server")] + Jmap(Box), + #[command(about = "IMAP server")] + Imap(Box), + #[command(about = "CalDAV server")] + Caldav(Box), + #[command(about = "CardDAV server")] + Carddav(Box), + #[command(about = "WebDAV server (plain files)")] + Webdav(Box), + #[command(about = "ManageSieve server (Sieve scripts only)")] + Managesieve(Box), + #[command(about = "Local Maildir++ tree")] + Maildir(Box), + #[command(about = "Google Takeout (or any mbox/ics/vcf directory tree)")] + Takeout(Box), + #[command(about = "MS Exchange via EWS (SOAP/XML)", name = "exchange-ews")] + ExchangeEws(Box), + #[command( + about = "MS Exchange Online via Microsoft Graph", + name = "exchange-graph" + )] + ExchangeGraph(Box), +} + +#[derive(Args)] +struct GlobalArgs { + #[arg( + short = 'j', + long, + value_name = "N", + help = "Worker pool size (default: logical CPUs)" + )] + threads: Option, + + #[arg(long, help = "Compute and report the full plan; perform no writes")] + dry_run: bool, + + #[arg( + short = 'v', + long, + action = ArgAction::Count, + conflicts_with = "quiet", + help = "Increase log verbosity (repeatable, max -vvv)", + long_help = "Increase log verbosity (repeatable, max -vvv).\n \ + (default) per-type start/finish lines and a final summary\n \ + -v per-chunk progress, account resolution, retry notices\n \ + -vv every JMAP call (method, accountId, args, status, timing)\n \ + -vvv full request/response bodies and backoff delays (stderr)" + )] + verbose: u8, + + #[arg( + short = 'q', + long, + help = "Quiet: warnings and errors only (verbosity level 0)" + )] + quiet: bool, + + #[arg( + long, + value_name = "N", + default_value_t = 5, + help = "Max retry attempts per request on transient failures" + )] + max_retries: u32, + + #[arg(long, help = "Accept self-signed / invalid TLS certificates")] + allow_invalid_certs: bool, +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("auth").required(true).args(["auth_basic", "auth_bearer"])))] +#[command(group(clap::ArgGroup::new("account").required(true).args(["account_id", "account_name"])))] +struct JmapImportArgs { + #[arg(long, value_name = "URL", help = "Base URL or full JMAP session URL")] + url: String, + + #[arg(long, value_name = "USER", help = "HTTP Basic user")] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token (value optional: falls back to $VANDELAY_TOKEN or prompt)" + )] + auth_bearer: Option>, + + #[arg(long, value_name = "ID", help = "Source account id")] + account_id: Option, + + #[arg(long, value_name = "NAME", help = "Source account name")] + account_name: Option, + + #[arg( + long, + value_name = "LIST", + help = "Comma-separated type list (default: all supported)" + )] + objects: Option, + + #[arg( + long, + help = "Permit importing when the archive records a different JMAP source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("auth").required(true).args(["auth_basic", "auth_bearer"])))] +struct ImapImportArgs { + #[arg( + long, + value_name = "URL", + help = "imap://host[:port] or imaps://host[:port]" + )] + url: String, + + #[arg( + long, + value_name = "USER", + help = "PLAIN (preferred) with LOGIN fallback" + )] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token for XOAUTH2 (value optional: falls back to $VANDELAY_TOKEN or prompt)" + )] + auth_bearer: Option>, + + #[arg( + long, + value_name = "USER", + help = "XOAUTH2 identity (required with --auth-bearer)" + )] + auth_user: Option, + + #[arg(long, help = "Permit credentials on imap:// without STARTTLS")] + allow_cleartext: bool, + + #[arg(long, help = "Enable COMPRESS=DEFLATE post-auth (off by default)")] + compress: bool, + + #[arg( + long, + value_name = "REGEX", + action = ArgAction::Append, + help = "Folder include filter (repeatable)" + )] + include: Vec, + + #[arg( + long, + value_name = "REGEX", + action = ArgAction::Append, + help = "Folder exclude filter (repeatable)" + )] + exclude: Vec, + + #[arg( + long, + value_name = "ROLE", + action = ArgAction::Append, + help = "Drop folders matching a role (repeatable)" + )] + exclude_special: Vec, + + #[arg( + long, + value_name = "NAME", + action = ArgAction::Append, + help = "Exact include; mutually exclusive with --include/--exclude" + )] + folder: Vec, + + #[arg(long, help = "Keep only folders returned by LSUB")] + subscribed_only: bool, + + #[arg(long, help = "Disable SPECIAL-USE name-heuristic fallback")] + noautomap: bool, + + #[arg(long, help = "Import \\Deleted messages with the $deleted keyword")] + include_deleted: bool, + + #[arg( + long, + value_name = "N", + default_value_t = 256, + help = "UIDs per metadata FETCH chunk" + )] + fetch_batch: usize, + + #[arg( + long, + value_name = "N", + help = "Parallel IMAP connections for body fetch (1..=8). Default: min(--threads, 8)" + )] + imap_connections: Option, + + #[arg( + long, + help = "Permit importing when the archive records a different IMAP source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("auth").required(true).args(["auth_basic", "auth_bearer"])))] +#[command(group(clap::ArgGroup::new("account").required(true).args(["account_id", "account_name"])))] +struct ExportArgs { + #[arg(long, value_name = "URL", help = "Base URL or full JMAP session URL")] + url: String, + + #[arg(long, value_name = "USER", help = "HTTP Basic user")] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token (value optional: falls back to $VANDELAY_TOKEN or prompt)" + )] + auth_bearer: Option>, + + #[arg(long, value_name = "ID", help = "Target account id")] + account_id: Option, + + #[arg(long, value_name = "NAME", help = "Target account name")] + account_name: Option, + + #[arg( + long, + value_name = "LIST", + help = "Types to export (default: all present in archive)" + )] + objects: Option, + + #[arg(long, help = "Delete target objects that match nothing in the archive")] + prune: bool, + + #[arg(long, help = "Skip the interactive --prune confirmation")] + yes: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg(value_name = "ARCHIVE", help = "Local SQLite archive")] + archive: PathBuf, +} + +#[derive(Args)] +struct InspectArgs { + #[arg(value_name = "ARCHIVE", help = "Local SQLite archive")] + archive: PathBuf, + + #[arg( + value_name = "TYPE", + help = "Object type to dump (omit for a summary of all types)" + )] + target: Option, + + #[arg( + long, + value_name = "N", + help = "Maximum rows to show (omit to dump every row)" + )] + limit: Option, + + #[arg( + long, + value_name = "N", + default_value_t = 0, + help = "Skip the first N rows" + )] + offset: usize, +} + +pub enum Action { + Import(CommonConfig, ImportConfig), + ImportImap(CommonConfig, ImapImportConfig), + ImportDav(CommonConfig, DavImportConfig), + ImportManageSieve(CommonConfig, ManageSieveImportConfig), + ImportMaildir(CommonConfig, MaildirImportConfig), + ImportTakeout(CommonConfig, TakeoutImportConfig), + ImportExchangeEws(CommonConfig, EwsImportConfig), + ImportExchangeGraph(CommonConfig, GraphImportConfig), + Export(CommonConfig, ExportConfig), + Inspect(InspectConfig), +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("auth").required(true).args(["auth_basic", "auth_bearer"])))] +pub struct ManageSieveImportArgs { + #[arg( + long, + value_name = "URL", + help = "sieve://host[:port] or sieves://host[:port]" + )] + url: String, + + #[arg( + long, + value_name = "USER", + help = "SASL PLAIN (preferred) with LOGIN fallback" + )] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token for OAUTHBEARER (value optional: $VANDELAY_TOKEN or prompt)" + )] + auth_bearer: Option>, + + #[arg( + long, + value_name = "USER", + help = "OAUTHBEARER identity (required with --auth-bearer)" + )] + auth_user: Option, + + #[arg(long, help = "Permit credentials on sieve:// without STARTTLS")] + allow_cleartext: bool, + + #[arg( + long, + help = "Permit importing when the archive records a different ManageSieve source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("auth").required(true).args(["auth_basic", "auth_bearer"])))] +pub struct DavImportArgs { + #[arg(long, value_name = "URL", help = "http(s)://host[:port][/path]")] + url: String, + + #[arg(long, value_name = "USER", help = "HTTP Basic user")] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token (value optional: $VANDELAY_TOKEN or prompt)" + )] + auth_bearer: Option>, + + #[arg(long, help = "Permit credentials on http:// (no TLS)")] + allow_cleartext: bool, + + #[arg( + long, + value_name = "N", + default_value_t = 4, + help = "Parallel DAV worker connections (hard cap 8)" + )] + dav_connections: usize, + + #[arg( + long, + value_name = "N", + default_value_t = 50, + help = "Hrefs per calendar-/addressbook-multiget REPORT batch" + )] + multiget_batch: usize, + + #[arg( + long, + help = "Permit importing when the archive records a different DAV source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +impl Cli { + pub fn resolve(self) -> Result { + match self.command { + Command::Import { source } => resolve_import(source), + Command::Export(args) => resolve_export(*args), + Command::Inspect(args) => resolve_inspect(*args), + } + } +} + +fn resolve_import(source: Source) -> Result { + match source { + Source::Jmap(args) => resolve_jmap_import(*args), + Source::Imap(args) => resolve_imap_import(*args), + Source::Caldav(args) => resolve_dav_import(*args, DavKindArg::Caldav), + Source::Carddav(args) => resolve_dav_import(*args, DavKindArg::Carddav), + Source::Webdav(args) => resolve_dav_import(*args, DavKindArg::Webdav), + Source::Managesieve(args) => resolve_managesieve_import(*args), + Source::Maildir(args) => resolve_maildir_import(*args), + Source::Takeout(args) => resolve_takeout_import(*args), + Source::ExchangeEws(args) => resolve_exchange_ews_import(*args), + Source::ExchangeGraph(args) => resolve_exchange_graph_import(*args), + } +} + +fn resolve_managesieve_import(args: ManageSieveImportArgs) -> Result { + let common = common_config(&args.global, args.archive); + let auth = resolve_managesieve_auth( + args.auth_basic.as_deref(), + args.auth_password.as_deref(), + args.auth_bearer.as_ref(), + args.auth_user.as_deref(), + )?; + Ok(Action::ImportManageSieve( + common, + ManageSieveImportConfig { + url: args.url, + auth, + allow_cleartext: args.allow_cleartext, + allow_source_change: args.allow_source_change, + }, + )) +} + +fn resolve_managesieve_auth( + auth_basic: Option<&str>, + auth_password: Option<&str>, + auth_bearer: Option<&Option>, + auth_user: Option<&str>, +) -> Result { + if let Some(user) = auth_basic { + if auth_user.is_some() { + return Err(Error::Usage( + "--auth-user is only valid with --auth-bearer".to_owned(), + )); + } + let password = secret::resolve(auth_password, "VANDELAY_PASSWORD", "password")?; + return Ok(ManageSieveAuth::Basic { + user: user.to_owned(), + password, + }); + } + if auth_password.is_some() { + return Err(Error::Usage( + "--auth-password is only valid together with --auth-basic".to_owned(), + )); + } + let bearer = auth_bearer.ok_or_else(|| { + Error::Usage("exactly one of --auth-basic / --auth-bearer is required".to_owned()) + })?; + let user = auth_user + .ok_or_else(|| Error::Usage("--auth-user is required with --auth-bearer".to_owned()))? + .to_owned(); + let token = secret::resolve(bearer.as_deref(), "VANDELAY_TOKEN", "bearer token")?; + Ok(ManageSieveAuth::Bearer { user, token }) +} + +#[derive(Args)] +pub struct MaildirImportArgs { + #[arg( + value_name = "MAILDIR", + help = "Path to the Maildir++ root (a directory containing cur/ new/ tmp/)" + )] + maildir: PathBuf, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, + + #[arg( + long, + value_name = "REGEX", + action = ArgAction::Append, + help = "Folder include filter against canonical name (repeatable)" + )] + include: Vec, + + #[arg( + long, + value_name = "REGEX", + action = ArgAction::Append, + help = "Folder exclude filter against canonical name (repeatable)" + )] + exclude: Vec, + + #[arg( + long, + value_name = "NAME", + action = ArgAction::Append, + help = "Exact include; mutually exclusive with --include/--exclude" + )] + folder: Vec, + + #[arg(long, help = "Disable the name-heuristic role assignment")] + noautomap: bool, + + #[arg(long, help = "Import T-flagged messages with the $deleted keyword")] + include_deleted: bool, + + #[arg( + long, + help = "Permit importing when the archive records a different maildir path" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, +} + +fn resolve_maildir_import(args: MaildirImportArgs) -> Result { + let common = common_config(&args.global, args.archive); + if !args.folder.is_empty() && (!args.include.is_empty() || !args.exclude.is_empty()) { + return Err(Error::Usage( + "--folder is mutually exclusive with --include/--exclude".to_owned(), + )); + } + let include = compile_regexes(&args.include, "--include")?; + let exclude = compile_regexes(&args.exclude, "--exclude")?; + Ok(Action::ImportMaildir( + common, + MaildirImportConfig { + maildir: args.maildir, + include, + exclude, + folder: args.folder, + automap: !args.noautomap, + include_deleted: args.include_deleted, + allow_source_change: args.allow_source_change, + }, + )) +} + +#[derive(Args)] +pub struct TakeoutImportArgs { + #[arg( + value_name = "PATH", + help = "Directory tree to scan recursively for .mbox / .ics / .vcf files" + )] + path: PathBuf, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, + + #[arg(long, help = "Disable the system-label role assignment")] + noautomap: bool, + + #[arg( + long, + help = "Permit importing when the archive records a takeout source at a different path" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, +} + +fn resolve_takeout_import(args: TakeoutImportArgs) -> Result { + let common = common_config(&args.global, args.archive); + Ok(Action::ImportTakeout( + common, + TakeoutImportConfig { + takeout_root: args.path, + allow_source_change: args.allow_source_change, + automap: !args.noautomap, + }, + )) +} + +fn resolve_dav_import(args: DavImportArgs, kind: DavKindArg) -> Result { + let common = common_config(&args.global, args.archive); + let auth = resolve_dav_auth( + args.auth_basic.as_deref(), + args.auth_password.as_deref(), + args.auth_bearer.as_ref(), + )?; + let dav_connections = args.dav_connections.clamp(1, 8).min(common.threads.max(1)); + let multiget_batch = args.multiget_batch.max(1); + Ok(Action::ImportDav( + common, + DavImportConfig { + kind, + url: args.url, + auth, + allow_cleartext: args.allow_cleartext, + dav_connections, + multiget_batch, + allow_source_change: args.allow_source_change, + }, + )) +} + +fn resolve_dav_auth( + auth_basic: Option<&str>, + auth_password: Option<&str>, + auth_bearer: Option<&Option>, +) -> Result { + if let Some(user) = auth_basic { + let password = secret::resolve(auth_password, "VANDELAY_PASSWORD", "password")?; + return Ok(DavAuth::Basic { + user: user.to_owned(), + password, + }); + } + if auth_password.is_some() { + return Err(Error::Usage( + "--auth-password is only valid together with --auth-basic".to_owned(), + )); + } + let bearer = auth_bearer.ok_or_else(|| { + Error::Usage("exactly one of --auth-basic / --auth-bearer is required".to_owned()) + })?; + let token = secret::resolve(bearer.as_deref(), "VANDELAY_TOKEN", "bearer token")?; + Ok(DavAuth::Bearer { token }) +} + +fn resolve_jmap_import(args: JmapImportArgs) -> Result { + let common = common_config(&args.global, args.archive); + let auth = resolve_auth( + args.auth_basic.as_deref(), + args.auth_password.as_deref(), + args.auth_bearer.as_ref(), + )?; + let account = resolve_account(args.account_id, args.account_name)?; + let objects = args.objects.as_deref().map(parse_object_list).transpose()?; + + Ok(Action::Import( + common, + ImportConfig { + connect: ConnectConfig { + url: args.url, + auth, + account, + }, + objects, + allow_source_change: args.allow_source_change, + }, + )) +} + +fn resolve_imap_import(args: ImapImportArgs) -> Result { + let common = common_config(&args.global, args.archive); + let auth = resolve_imap_auth( + args.auth_basic.as_deref(), + args.auth_password.as_deref(), + args.auth_bearer.as_ref(), + args.auth_user.as_deref(), + )?; + if !args.folder.is_empty() && (!args.include.is_empty() || !args.exclude.is_empty()) { + return Err(Error::Usage( + "--folder is mutually exclusive with --include/--exclude".to_owned(), + )); + } + let include = compile_regexes(&args.include, "--include")?; + let exclude = compile_regexes(&args.exclude, "--exclude")?; + let imap_connections = args.imap_connections.unwrap_or(common.threads).clamp(1, 8); + + Ok(Action::ImportImap( + common, + ImapImportConfig { + url: args.url, + auth, + allow_cleartext: args.allow_cleartext, + compress: args.compress, + include, + exclude, + exclude_special: args.exclude_special, + folder: args.folder, + subscribed_only: args.subscribed_only, + automap: !args.noautomap, + include_deleted: args.include_deleted, + fetch_batch: args.fetch_batch, + imap_connections, + allow_source_change: args.allow_source_change, + }, + )) +} + +fn compile_regexes(raw: &[String], flag: &str) -> Result, Error> { + let mut out = Vec::with_capacity(raw.len()); + for r in raw { + let re = + Regex::new(r).map_err(|e| Error::Usage(format!("{flag} {r:?}: invalid regex: {e}")))?; + out.push(re); + } + Ok(out) +} + +fn resolve_imap_auth( + auth_basic: Option<&str>, + auth_password: Option<&str>, + auth_bearer: Option<&Option>, + auth_user: Option<&str>, +) -> Result { + if let Some(user) = auth_basic { + if auth_user.is_some() { + return Err(Error::Usage( + "--auth-user is only valid with --auth-bearer".to_owned(), + )); + } + let password = secret::resolve(auth_password, "VANDELAY_PASSWORD", "password")?; + return Ok(ImapAuth::Basic { + user: user.to_owned(), + password, + }); + } + if auth_password.is_some() { + return Err(Error::Usage( + "--auth-password is only valid together with --auth-basic".to_owned(), + )); + } + let bearer = auth_bearer.ok_or_else(|| { + Error::Usage("exactly one of --auth-basic / --auth-bearer is required".to_owned()) + })?; + let user = auth_user + .ok_or_else(|| Error::Usage("--auth-user is required with --auth-bearer".to_owned()))? + .to_owned(); + let token = secret::resolve(bearer.as_deref(), "VANDELAY_TOKEN", "bearer token")?; + Ok(ImapAuth::Bearer { user, token }) +} + +fn resolve_export(args: ExportArgs) -> Result { + let common = common_config(&args.global, args.archive); + let auth = resolve_auth( + args.auth_basic.as_deref(), + args.auth_password.as_deref(), + args.auth_bearer.as_ref(), + )?; + let account = resolve_account(args.account_id, args.account_name)?; + let objects = args.objects.as_deref().map(parse_object_list).transpose()?; + + Ok(Action::Export( + common, + ExportConfig { + connect: ConnectConfig { + url: args.url, + auth, + account, + }, + objects, + prune: args.prune, + yes: args.yes, + }, + )) +} + +fn resolve_inspect(args: InspectArgs) -> Result { + let target = match args.target.as_deref() { + Some(token) => Some(ObjectType::parse(token)?), + None => None, + }; + if let Some(limit) = args.limit + && limit == 0 + { + return Err(Error::Usage("--limit must be greater than zero".to_owned())); + } + Ok(Action::Inspect(InspectConfig { + archive: args.archive, + target, + limit: args.limit, + offset: args.offset, + })) +} + +#[derive(Args)] +#[command(group(clap::ArgGroup::new("ews_auth").required(true).args([ + "auth_basic", "auth_bearer", +])))] +pub struct ExchangeEwsImportArgs { + #[arg(long, value_name = "URL", help = "Fully-qualified EWS endpoint URL")] + url: Option, + + #[arg( + long, + value_name = "EMAIL", + help = "Target mailbox SMTP address (required for autodiscover and app-only OAuth)" + )] + mailbox: Option, + + #[arg( + long, + value_name = "KIND", + default_value = "primary", + help = "Mailbox surface: primary | archive | public-folders" + )] + mailbox_kind: String, + + #[arg(long, value_name = "USER", help = "Basic auth user (on-prem only)")] + auth_basic: Option, + + #[arg( + long, + value_name = "PASS", + help = "Basic password (prefer $VANDELAY_PASSWORD or prompt)" + )] + auth_password: Option, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Bearer token (value optional: $VANDELAY_TOKEN or device-code flow)" + )] + auth_bearer: Option>, + + #[arg( + long, + value_name = "TENANT", + help = "Entra tenant id or .onmicrosoft.com short name" + )] + ews_tenant: Option, + + #[arg(long, value_name = "ID", help = "Entra app (client) id")] + ews_client_id: Option, + + #[arg( + long, + value_name = "SECRET", + help = "OAuth client secret (app-only flow; prefer $VANDELAY_EWS_CLIENT_SECRET)" + )] + ews_client_secret: Option, + + #[arg(long, help = "Use the OAuth device-code interactive flow")] + ews_device_code: bool, + + #[arg( + long, + value_name = "N", + default_value_t = 4, + help = "Parallel EWS worker connections (hard cap 8)" + )] + ews_connections: usize, + + #[arg( + long, + value_name = "N", + default_value_t = 20, + help = "ItemIds per GetItem batch" + )] + ews_getitem_batch: usize, + + #[arg( + long, + value_name = "N", + default_value_t = 5, + help = "AttachmentIds per GetAttachment batch" + )] + ews_attachment_batch: usize, + + #[arg(long, help = "Force FindItem even when SyncFolderItems would work")] + ews_no_syncfolderitems: bool, + + #[arg( + long, + help = "Permit importing when the archive records a different EWS source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +fn resolve_exchange_ews_import(args: ExchangeEwsImportArgs) -> Result { + let archive = args.archive.clone(); + let common = common_config(&args.global, archive); + let mailbox_kind = match args.mailbox_kind.as_str() { + "primary" => MailboxKind::Primary, + "archive" => MailboxKind::Archive, + "public-folders" => MailboxKind::PublicFolders, + other => { + return Err(Error::Usage(format!( + "--mailbox-kind must be primary | archive | public-folders, got {other:?}" + ))); + } + }; + let auth = resolve_exchange_ews_auth(&args)?; + let ews_connections = args.ews_connections.clamp(1, 8).min(common.threads.max(1)); + let config = EwsImportConfig { + url: args.url, + mailbox: args.mailbox, + mailbox_kind, + auth, + ews_connections, + getitem_batch: args.ews_getitem_batch.max(1), + attachment_batch: args.ews_attachment_batch.max(1), + use_syncfolderitems: !args.ews_no_syncfolderitems, + allow_source_change: args.allow_source_change, + }; + Ok(Action::ImportExchangeEws(common, config)) +} + +fn resolve_exchange_ews_auth(args: &ExchangeEwsImportArgs) -> Result { + if let Some(user) = args.auth_basic.as_deref() { + let password = secret::resolve( + args.auth_password.as_deref(), + "VANDELAY_PASSWORD", + "password", + )?; + return Ok(EwsAuth::Basic { + user: user.to_owned(), + password, + }); + } + if args.auth_password.is_some() { + return Err(Error::Usage( + "--auth-password is only valid with --auth-basic".to_owned(), + )); + } + let Some(bearer) = args.auth_bearer.as_ref() else { + return Err(Error::Usage( + "exactly one of --auth-basic / --auth-bearer is required".to_owned(), + )); + }; + if let Some(token) = bearer.clone() { + return Ok(EwsAuth::Bearer { token }); + } + let tenant = args + .ews_tenant + .clone() + .unwrap_or_else(|| "common".to_owned()); + let client_id = args.ews_client_id.clone().ok_or_else(|| { + Error::Usage( + "--ews-client-id is required when --auth-bearer is used without an inline token" + .to_owned(), + ) + })?; + if args.ews_device_code { + return Ok(EwsAuth::OAuth(OAuthFlow::DeviceCode { tenant, client_id })); + } + if let Some(secret) = args.ews_client_secret.clone() { + let resolved = secret::resolve( + Some(secret.as_str()), + "VANDELAY_EWS_CLIENT_SECRET", + "client secret", + )?; + return Ok(EwsAuth::OAuth(OAuthFlow::ClientCredentials { + tenant, + client_id, + client_secret: resolved, + })); + } + if let Ok(env_secret) = std::env::var("VANDELAY_EWS_CLIENT_SECRET") + && !env_secret.is_empty() + { + return Ok(EwsAuth::OAuth(OAuthFlow::ClientCredentials { + tenant, + client_id, + client_secret: env_secret, + })); + } + let token = secret::resolve(None, "VANDELAY_TOKEN", "bearer token")?; + Ok(EwsAuth::Bearer { token }) +} + +#[derive(Args)] +pub struct ExchangeGraphImportArgs { + #[arg( + long, + value_name = "UUID", + help = "Entra app (client) id (required unless --access-token is given)" + )] + client_id: Option, + + #[arg( + long, + value_name = "ID", + default_value = "common", + help = "Entra authority tenant" + )] + tenant: String, + + #[arg( + long, + value_name = "UPN|UUID", + help = "Target a different mailbox the signed-in account can read" + )] + user: Option, + + #[arg( + long, + value_name = "KIND", + default_value = "primary", + value_parser = parse_graph_mailbox_kind, + help = "Mailbox surface: primary | archive (public-folders is rejected: use exchange-ews)" + )] + mailbox_kind: GraphMailboxKind, + + #[arg( + long, + value_name = "TOKEN", + num_args = 0..=1, + help = "Pre-acquired bearer token (skip device-code flow). Resolves: flag value -> $VANDELAY_GRAPH_TOKEN -> prompt." + )] + access_token: Option>, + + #[arg( + long, + value_name = "LIST", + help = "Comma-separated surface list (mail,calendar,contacts; default all)" + )] + objects: Option, + + #[arg( + long, + value_name = "FMT", + default_value = "text", + value_parser = parse_event_body_format, + help = "outlook.body-content-type for event GETs: text | html" + )] + event_body_format: EventBodyFormat, + + #[arg( + long, + value_name = "N", + default_value_t = 4, + help = "Parallel Graph worker connections (hard cap 16)" + )] + graph_connections: usize, + + #[arg( + long, + value_name = "N", + default_value_t = 100, + help = "Per-page size ($top query parameter, range [1, 1000])" + )] + top: usize, + + #[arg( + long, + value_name = "URL", + default_value = "https://graph.microsoft.com/v1.0", + help = "Microsoft Graph API base URL (override for national clouds or tests)", + hide = true + )] + api_base: String, + + #[arg( + long, + help = "Permit importing when the archive records a different exchange_graph source" + )] + allow_source_change: bool, + + #[command(flatten)] + global: GlobalArgs, + + #[arg( + value_name = "ARCHIVE", + help = "Local SQLite archive (created if absent)" + )] + archive: PathBuf, +} + +fn parse_graph_mailbox_kind(s: &str) -> Result { + GraphMailboxKind::parse(s).map_err(|e| e.to_string()) +} + +fn parse_event_body_format(s: &str) -> Result { + EventBodyFormat::parse(s).map_err(|e| e.to_string()) +} + +fn resolve_exchange_graph_import(args: ExchangeGraphImportArgs) -> Result { + let common = common_config(&args.global, args.archive.clone()); + let mailbox_kind = args.mailbox_kind; + let event_body_format = args.event_body_format; + let auth = resolve_graph_auth( + args.access_token.as_ref(), + args.client_id.as_deref(), + &args.tenant, + )?; + let objects = args + .objects + .as_deref() + .map(crate::types::parse_object_list) + .transpose()?; + let top = args.top.clamp(1, 1000); + let graph_connections = args + .graph_connections + .clamp(1, 16) + .min(common.threads.max(1)); + Ok(Action::ImportExchangeGraph( + common, + GraphImportConfig { + auth, + api_base: args.api_base, + user_target: args.user, + mailbox_kind, + objects, + event_body_format, + graph_connections, + top, + allow_source_change: args.allow_source_change, + }, + )) +} + +fn resolve_graph_auth( + access_token: Option<&Option>, + client_id: Option<&str>, + tenant: &str, +) -> Result { + if let Some(slot) = access_token { + let token = secret::resolve(slot.as_deref(), "VANDELAY_GRAPH_TOKEN", "bearer token")?; + return Ok(GraphAuth::PreAcquired { token }); + } + let client_id = client_id + .ok_or_else(|| { + Error::Usage( + "--client-id is required for exchange-graph (unless --access-token is given)" + .to_owned(), + ) + })? + .to_owned(); + let authority = crate::exchange_graph::oauth::default_authority(tenant); + Ok(GraphAuth::DeviceCode { + authority, + client_id, + }) +} + +fn common_config(global: &GlobalArgs, archive: PathBuf) -> CommonConfig { + let threads = global + .threads + .filter(|n| *n > 0) + .unwrap_or_else(num_cpus::get) + .max(1); + CommonConfig { + archive, + threads, + dry_run: global.dry_run, + max_retries: global.max_retries, + allow_invalid_certs: global.allow_invalid_certs, + logger: Logger::from_flags(global.quiet, global.verbose), + } +} + +fn resolve_auth( + auth_basic: Option<&str>, + auth_password: Option<&str>, + auth_bearer: Option<&Option>, +) -> Result { + if let Some(user) = auth_basic { + let password = secret::resolve(auth_password, "VANDELAY_PASSWORD", "password")?; + return Ok(Auth::Basic { + user: user.to_owned(), + password, + }); + } + if auth_password.is_some() { + return Err(Error::Usage( + "--auth-password is only valid together with --auth-basic".to_owned(), + )); + } + let bearer = auth_bearer.ok_or_else(|| { + Error::Usage("exactly one of --auth-basic / --auth-bearer is required".to_owned()) + })?; + let token = secret::resolve(bearer.as_deref(), "VANDELAY_TOKEN", "bearer token")?; + Ok(Auth::Bearer { token }) +} + +fn resolve_account( + account_id: Option, + account_name: Option, +) -> Result { + match (account_id, account_name) { + (Some(id), None) => Ok(AccountSelector::Id(id)), + (None, Some(name)) => Ok(AccountSelector::Name(name)), + _ => Err(Error::Usage( + "exactly one of --account-id / --account-name is required".to_owned(), + )), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + fn lock() -> std::sync::MutexGuard<'static, ()> { + static M: std::sync::Mutex<()> = std::sync::Mutex::new(()); + M.lock().unwrap_or_else(|e| e.into_inner()) + } + + #[test] + fn auth_password_without_basic_is_usage_error() { + let _g = lock(); + unsafe { + env::remove_var("VANDELAY_TOKEN"); + env::remove_var("VANDELAY_PASSWORD"); + } + let bearer: Option = Some("t".to_owned()); + let r = resolve_auth(None, Some("hunter2"), Some(&bearer)); + match r { + Err(Error::Usage(m)) => { + assert!(m.contains("auth-password"), "msg was: {m}"); + } + other => panic!("expected Usage error, got {other:?}"), + } + } + + #[test] + fn auth_basic_with_password_resolves() { + let _g = lock(); + unsafe { + env::remove_var("VANDELAY_TOKEN"); + env::remove_var("VANDELAY_PASSWORD"); + } + let auth = resolve_auth(Some("alice"), Some("pw"), None).unwrap(); + match auth { + Auth::Basic { user, password } => { + assert_eq!(user, "alice"); + assert_eq!(password, "pw"); + } + _ => panic!("expected Basic"), + } + } + + #[test] + fn auth_bearer_inline_token_resolves() { + let _g = lock(); + unsafe { + env::remove_var("VANDELAY_TOKEN"); + env::remove_var("VANDELAY_PASSWORD"); + } + let bearer: Option = Some("t".to_owned()); + let auth = resolve_auth(None, None, Some(&bearer)).unwrap(); + assert!(matches!(auth, Auth::Bearer { token } if token == "t")); + } +} diff --git a/src/dav/client.rs b/src/dav/client.rs new file mode 100644 index 0000000..290cc68 --- /dev/null +++ b/src/dav/client.rs @@ -0,0 +1,898 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufReader, Read}; +use std::sync::Arc; +use std::sync::atomic::{AtomicU8, AtomicU64, Ordering}; +use std::time::Duration; + +use ureq::Agent; +use ureq::Body; +use ureq::config::{Config, RedirectAuthHeaders}; +use ureq::http::{Method, Request, Response}; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::dav::parse::{ControlStrippingReader, DavResponse, parse_multistatus}; +use crate::dav::retry::{DavOutcome, classify}; +use crate::jmap::error::JmapError; +use crate::jmap::http::{Auth, RetryPolicy, retry_after_header}; +use crate::jmap::retry::{self, RateLimitState}; +use crate::logging::{LEVEL_BODIES, LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; + +const MAX_BODY: u64 = 512 * 1024 * 1024; +const LONG_RETRY_THRESHOLD: Duration = Duration::from_secs(10); +const MAX_DAV_REDIRECTS: u32 = 5; + +const ACCEPT_DAV: &str = "application/xml, text/xml;q=0.9, */*;q=0.5"; +const ACCEPT_BINARY: &str = "*/*"; + +#[derive(Debug)] +pub struct DavHttpResponse { + pub status: u16, + pub body: Vec, + pub etag: Option, + pub content_type: Option, + pub last_modified: Option, +} + +#[derive(Debug)] +pub struct MultiStatus { + pub status: u16, + pub responses: Vec, + pub final_url: String, +} + +struct Inner { + agent: Agent, + auth: Auth, + retry: RetryPolicy, + rate_limit: RateLimitState, + log_level: AtomicU8, + retries_total: AtomicU64, + retry_after_sleeps: AtomicU64, + user_agent: String, +} + +#[derive(Clone)] +pub struct DavClient { + inner: Arc, +} + +impl DavClient { + pub fn new(auth: Auth, retry: RetryPolicy, allow_invalid_certs: bool) -> Self { + let config: Config = Config::builder() + .http_status_as_error(false) + .allow_non_standard_methods(true) + .max_redirects(0) + .redirect_auth_headers(RedirectAuthHeaders::SameHost) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + DavClient { + inner: Arc::new(Inner { + agent: config.new_agent(), + auth, + retry, + rate_limit: RateLimitState::new(), + log_level: AtomicU8::new(LEVEL_DEFAULT), + retries_total: AtomicU64::new(0), + retry_after_sleeps: AtomicU64::new(0), + user_agent: format!("vandelay/{}", env!("CARGO_PKG_VERSION")), + }), + } + } + + pub fn set_logger(&self, logger: Logger) { + self.inner + .log_level + .store(logger.level(), Ordering::Relaxed); + } + + pub fn logger(&self) -> Logger { + Logger::new(self.inner.log_level.load(Ordering::Relaxed)) + } + + pub fn rate_limit(&self) -> &RateLimitState { + &self.inner.rate_limit + } + + pub fn retries_observed(&self) -> u64 { + self.inner.retries_total.load(Ordering::Relaxed) + } + + pub fn retry_after_sleeps(&self) -> u64 { + self.inner.retry_after_sleeps.load(Ordering::Relaxed) + } + + pub fn propfind(&self, url: &str, depth: u8, body: &str) -> Result { + self.execute( + "PROPFIND", + url, + Some(body.as_bytes()), + Some("application/xml; charset=utf-8"), + Some(depth), + ACCEPT_DAV, + ) + } + + pub fn report(&self, url: &str, depth: u8, body: &str) -> Result { + self.execute( + "REPORT", + url, + Some(body.as_bytes()), + Some("application/xml; charset=utf-8"), + Some(depth), + ACCEPT_DAV, + ) + } + + pub fn get(&self, url: &str) -> Result { + self.execute("GET", url, None, None, None, ACCEPT_BINARY) + } + + pub fn propfind_responses( + &self, + url: &str, + depth: u8, + body: &str, + base_url: &str, + ) -> Result { + self.stream_multistatus("PROPFIND", url, Some(body.as_bytes()), depth, base_url) + } + + pub fn report_responses( + &self, + url: &str, + depth: u8, + body: &str, + base_url: &str, + ) -> Result { + self.stream_multistatus("REPORT", url, Some(body.as_bytes()), depth, base_url) + } + + pub fn get_stream(&self, url: &str) -> Result { + let logger = self.logger(); + let policy = self.inner.retry; + let mut attempt: u32 = 0; + let original_url = url.to_owned(); + let mut current_url = url.to_owned(); + let mut redirects: u32 = 0; + let mut include_auth = true; + loop { + self.inner.rate_limit.cooldown().wait(); + let outcome = self.one_attempt_stream(¤t_url, include_auth); + match outcome { + AttemptStream::Ok { + status, + body_reader, + etag, + content_type, + last_modified, + .. + } if (200..300).contains(&status) => { + self.inner.rate_limit.on_success(); + return Ok(DavStream { + status, + body_reader, + etag, + content_type, + last_modified, + }); + } + AttemptStream::Ok { status, .. } if status == 404 || status == 410 => { + self.inner.rate_limit.on_success(); + return Ok(DavStream { + status, + body_reader: Box::new(std::io::empty()), + etag: None, + content_type: None, + last_modified: None, + }); + } + AttemptStream::Ok { + status, + location: Some(loc), + .. + } if is_redirect_status(status) => { + if redirects >= MAX_DAV_REDIRECTS { + return Err(JmapError::Connect(format!( + "too many redirects ({redirects}) starting from {url}" + ))); + } + redirects += 1; + let next = resolve_redirect(¤t_url, &loc)?; + if include_auth && !same_host(&original_url, &next) { + include_auth = false; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "DAV redirect leaves origin host {original_url}; dropping Authorization for {next}" + ); + } + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("DAV {status}: {current_url} -> {next}"); + } + current_url = next; + } + AttemptStream::Ok { + status, + body_reader, + retry_after, + .. + } => { + let mut body_bytes = Vec::new(); + let mut limited = body_reader.take(64 * 1024); + let _ = limited.read_to_end(&mut body_bytes); + self.handle_status( + &logger, + status, + &body_bytes, + retry_after, + &mut attempt, + &policy, + )?; + } + AttemptStream::Transport(err) => { + self.handle_transport(&logger, err, &mut attempt, &policy)?; + } + } + } + } + + fn execute( + &self, + method: &str, + url: &str, + body: Option<&[u8]>, + content_type: Option<&str>, + depth: Option, + accept: &str, + ) -> Result { + let logger = self.logger(); + let policy = self.inner.retry; + let mut attempt: u32 = 0; + let original_url = url.to_owned(); + let mut current_url = url.to_owned(); + let mut redirects: u32 = 0; + let mut include_auth = true; + loop { + self.inner.rate_limit.cooldown().wait(); + let outcome = self.one_attempt(WireRequest { + method, + url: ¤t_url, + body, + content_type, + depth, + accept, + include_auth, + }); + match outcome { + Attempt::Ok { + status, + body, + etag, + content_type, + last_modified, + .. + } if (200..300).contains(&status) => { + self.inner.rate_limit.on_success(); + return Ok(DavHttpResponse { + status, + body, + etag, + content_type, + last_modified, + }); + } + Attempt::Ok { + status, + location: Some(loc), + .. + } if is_redirect_status(status) => { + if redirects >= MAX_DAV_REDIRECTS { + return Err(JmapError::Connect(format!( + "too many redirects ({redirects}) starting from {url}" + ))); + } + redirects += 1; + let next = resolve_redirect(¤t_url, &loc)?; + if include_auth && !same_host(&original_url, &next) { + include_auth = false; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "DAV redirect leaves origin host {original_url}; dropping Authorization for {next}" + ); + } + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("DAV {status}: {current_url} -> {next}"); + } + current_url = next; + } + Attempt::Ok { status, body, .. } if is_redirect_status(status) => { + return Err(JmapError::Connect(format!( + "{method} {current_url} returned {status} without Location: {}", + truncate(&body) + ))); + } + Attempt::Ok { + status, + body, + retry_after, + etag: _, + content_type: _, + last_modified: _, + location: _, + } => { + let disposition = classify(status, &body); + match disposition { + DavOutcome::Vanished => { + return Ok(DavHttpResponse { + status, + body, + etag: None, + content_type: None, + last_modified: None, + }); + } + DavOutcome::Auth => { + return Err(JmapError::Auth(format!( + "server returned {status}: {}", + truncate(&body) + ))); + } + DavOutcome::Fatal => { + return Err(JmapError::HttpStatus { + status, + body: truncate(&body), + }); + } + DavOutcome::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!( + "{method} {current_url} kept returning {status}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let delay = self.inner.rate_limit.on_throttle(&policy, retry_after); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "DAV {method} rate-limited (http {status}); waiting {}s before retry {}/{}", + delay.as_secs(), + attempt, + policy.max_retries, + )); + } + if logger.enabled(LEVEL_BODIES) { + eprintln!( + "retry {}/{} {method} {current_url} after {:?} (http {status}) body={}", + attempt, + policy.max_retries, + delay, + truncate(&body), + ); + } else if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "retry {}/{} (http {status})", + attempt, policy.max_retries + ); + } + std::thread::sleep(delay); + } + DavOutcome::Success => unreachable!(), + } + } + Attempt::Transport(err) => { + self.handle_transport(&logger, err, &mut attempt, &policy)?; + } + } + } + } + + fn stream_multistatus( + &self, + method: &str, + url: &str, + body: Option<&[u8]>, + depth: u8, + base_url: &str, + ) -> Result { + let logger = self.logger(); + let policy = self.inner.retry; + let mut attempt: u32 = 0; + let original_url = url.to_owned(); + let mut current_url = url.to_owned(); + let mut redirects: u32 = 0; + let mut include_auth = true; + loop { + self.inner.rate_limit.cooldown().wait(); + let send = self.build_and_send(WireRequest { + method, + url: ¤t_url, + body, + content_type: Some("application/xml; charset=utf-8"), + depth: Some(depth), + accept: ACCEPT_DAV, + include_auth, + }); + match send { + Ok(mut resp) => { + let status = resp.status().as_u16(); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + let location = resp + .headers() + .get("location") + .and_then(|v| v.to_str().ok()) + .map(str::to_owned); + if is_redirect_status(status) { + let Some(loc) = location else { + return Err(JmapError::Connect(format!( + "{method} {current_url} returned {status} without Location" + ))); + }; + if redirects >= MAX_DAV_REDIRECTS { + return Err(JmapError::Connect(format!( + "too many redirects ({redirects}) starting from {url}" + ))); + } + redirects += 1; + let next = resolve_redirect(¤t_url, &loc)?; + if include_auth && !same_host(&original_url, &next) { + include_auth = false; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "DAV redirect leaves origin host {original_url}; dropping Authorization for {next}" + ); + } + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("DAV {status}: {current_url} -> {next}"); + } + current_url = next; + continue; + } + if (200..300).contains(&status) { + self.inner.rate_limit.on_success(); + let parse_base = if current_url == url { + base_url + } else { + ¤t_url + }; + let raw = resp.into_body().into_reader(); + let stripped = ControlStrippingReader::new(raw); + let reader = BufReader::new(stripped); + let responses = parse_multistatus(reader, parse_base) + .map_err(|e| JmapError::Malformed(format!("multistatus parse: {e}")))?; + return Ok(MultiStatus { + status, + responses, + final_url: current_url.clone(), + }); + } + let body_bytes = + match resp.body_mut().with_config().limit(64 * 1024).read_to_vec() { + Ok(b) => b, + Err(e) => { + return Err(JmapError::Transport(format!( + "reading error body: {e}" + ))); + } + }; + let disposition = classify(status, &body_bytes); + match disposition { + DavOutcome::Vanished => { + return Ok(MultiStatus { + status, + responses: Vec::new(), + final_url: current_url.clone(), + }); + } + DavOutcome::Auth => { + return Err(JmapError::Auth(format!( + "server returned {status}: {}", + truncate(&body_bytes) + ))); + } + DavOutcome::Fatal => { + return Err(JmapError::HttpStatus { + status, + body: truncate(&body_bytes), + }); + } + DavOutcome::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!( + "{method} {url} kept returning {status}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let delay = self.inner.rate_limit.on_throttle(&policy, retry_after); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "DAV {method} rate-limited (http {status}); waiting {}s before retry {}/{}", + delay.as_secs(), + attempt, + policy.max_retries, + )); + } + std::thread::sleep(delay); + } + DavOutcome::Success => unreachable!(), + } + } + Err(e) => { + let err = map_ureq_error(e); + self.handle_transport(&logger, err, &mut attempt, &policy)?; + } + } + } + } + + fn handle_status( + &self, + logger: &Logger, + status: u16, + body: &[u8], + retry_after: Option, + attempt: &mut u32, + policy: &RetryPolicy, + ) -> Result<(), JmapError> { + let disposition = classify(status, body); + match disposition { + DavOutcome::Vanished => Err(JmapError::HttpStatus { + status, + body: truncate(body), + }), + DavOutcome::Auth => Err(JmapError::Auth(format!("server returned {status}"))), + DavOutcome::Fatal => Err(JmapError::HttpStatus { + status, + body: truncate(body), + }), + DavOutcome::Retryable => { + *attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if *attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!( + "GET stream kept returning {status}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let delay = self.inner.rate_limit.on_throttle(policy, retry_after); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "DAV stream rate-limited (http {status}); waiting {}s", + delay.as_secs() + )); + } + std::thread::sleep(delay); + Ok(()) + } + DavOutcome::Success => Ok(()), + } + } + + fn handle_transport( + &self, + logger: &Logger, + err: JmapError, + attempt: &mut u32, + policy: &RetryPolicy, + ) -> Result<(), JmapError> { + match transport_disposition(&err) { + retry::Disposition::Fatal => Err(err), + retry::Disposition::Retryable => { + *attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if *attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!("{err}"))); + } + let delay = retry::backoff_delay(policy, *attempt); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "transient transport failure ({err}); waiting {}s before retry {}/{}", + delay.as_secs(), + attempt, + policy.max_retries, + )); + } + std::thread::sleep(delay); + Ok(()) + } + } + } + + fn one_attempt(&self, req: WireRequest<'_>) -> Attempt { + match self.build_and_send(req) { + Ok(mut resp) => { + let status = resp.status().as_u16(); + let etag = header_str(&resp, "etag"); + let content_type_header = header_str(&resp, "content-type"); + let last_modified = header_str(&resp, "last-modified"); + let location = header_str(&resp, "location"); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + if is_redirect_status(status) && location.is_some() { + return Attempt::Ok { + status, + body: Vec::new(), + retry_after, + etag, + content_type: content_type_header, + last_modified, + location, + }; + } + let body_limit = if (200..300).contains(&status) { + MAX_BODY + } else { + 64 * 1024 + }; + match resp + .body_mut() + .with_config() + .limit(body_limit) + .read_to_vec() + { + Ok(bytes) => Attempt::Ok { + status, + body: bytes, + retry_after, + etag, + content_type: content_type_header, + last_modified, + location, + }, + Err(e) => Attempt::Transport(JmapError::Transport(format!( + "reading response body: {e}" + ))), + } + } + Err(e) => Attempt::Transport(map_ureq_error(e)), + } + } + + fn one_attempt_stream(&self, url: &str, include_auth: bool) -> AttemptStream { + let req = WireRequest { + method: "GET", + url, + body: None, + content_type: None, + depth: None, + accept: ACCEPT_BINARY, + include_auth, + }; + match self.build_and_send(req) { + Ok(resp) => { + let status = resp.status().as_u16(); + let etag = header_str(&resp, "etag"); + let content_type = header_str(&resp, "content-type"); + let last_modified = header_str(&resp, "last-modified"); + let location = header_str(&resp, "location"); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + let body_reader = resp.into_body().into_reader(); + AttemptStream::Ok { + status, + body_reader: Box::new(body_reader), + etag, + content_type, + last_modified, + location, + retry_after, + } + } + Err(e) => AttemptStream::Transport(map_ureq_error(e)), + } + } + + fn build_and_send(&self, req: WireRequest<'_>) -> Result, ureq::Error> { + let method_parsed = Method::from_bytes(req.method.as_bytes()) + .map_err(|e| ureq::Error::Other(Box::new(std::io::Error::other(e))))?; + let mut builder = Request::builder() + .method(method_parsed) + .uri(req.url) + .header("Accept", req.accept) + .header("User-Agent", self.inner.user_agent.as_str()); + if req.include_auth { + builder = builder.header("Authorization", self.inner.auth.header_value()); + } + if let Some(d) = req.depth { + builder = builder.header("Depth", d.to_string()); + } + if let Some(ct) = req.content_type { + builder = builder.header("Content-Type", ct); + } + let payload: &[u8] = req.body.unwrap_or(&[]); + let request = builder + .body(payload) + .map_err(|e| ureq::Error::Other(Box::new(std::io::Error::other(e))))?; + self.inner.agent.run(request) + } +} + +struct WireRequest<'a> { + method: &'a str, + url: &'a str, + body: Option<&'a [u8]>, + content_type: Option<&'a str>, + depth: Option, + accept: &'a str, + include_auth: bool, +} + +pub struct DavStream { + pub status: u16, + pub body_reader: Box, + pub etag: Option, + pub content_type: Option, + pub last_modified: Option, +} + +impl Read for DavStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.body_reader.read(buf) + } +} + +enum Attempt { + Ok { + status: u16, + body: Vec, + retry_after: Option, + etag: Option, + content_type: Option, + last_modified: Option, + location: Option, + }, + Transport(JmapError), +} + +enum AttemptStream { + Ok { + status: u16, + body_reader: Box, + etag: Option, + content_type: Option, + last_modified: Option, + location: Option, + retry_after: Option, + }, + Transport(JmapError), +} + +fn header_str(resp: &Response, name: &str) -> Option { + resp.headers() + .get(name) + .and_then(|v| v.to_str().ok()) + .map(str::to_owned) +} + +fn is_redirect_status(status: u16) -> bool { + matches!(status, 301 | 302 | 303 | 307 | 308) +} + +fn resolve_redirect(current: &str, location: &str) -> Result { + crate::dav::href::join_absolute(current, location) + .map_err(|e| JmapError::Connect(format!("redirect: {e}"))) +} + +fn url_host_and_port(url: &str) -> Option<(String, Option)> { + let parsed = url::Url::parse(url).ok()?; + let host = parsed.host_str()?.to_owned(); + Some((host, parsed.port_or_known_default())) +} + +fn same_host(a: &str, b: &str) -> bool { + match (url_host_and_port(a), url_host_and_port(b)) { + (Some(x), Some(y)) => x == y, + _ => false, + } +} + +fn transport_disposition(err: &JmapError) -> retry::Disposition { + match err { + JmapError::Connect(_) => retry::Disposition::Fatal, + _ => retry::Disposition::Retryable, + } +} + +fn map_ureq_error(err: ureq::Error) -> JmapError { + match err { + ureq::Error::Io(e) => JmapError::Transport(format!("io: {e}")), + ureq::Error::Timeout(t) => JmapError::Transport(format!("timeout: {t}")), + ureq::Error::HostNotFound => JmapError::Transport("host not found".to_owned()), + ureq::Error::ConnectionFailed => JmapError::Transport("connection failed".to_owned()), + ureq::Error::BodyStalled => JmapError::Transport("body stalled".to_owned()), + ureq::Error::Tls(m) => JmapError::Transport(format!("tls: {m}")), + ureq::Error::TooManyRedirects => JmapError::Connect("too many redirects".to_owned()), + ureq::Error::RedirectFailed => JmapError::Connect("redirect failed".to_owned()), + ureq::Error::TlsRequired => { + JmapError::Connect("server requires TLS but transport is unsecured".to_owned()) + } + other => JmapError::Connect(other.to_string()), + } +} + +fn truncate(body: &[u8]) -> String { + let text = String::from_utf8_lossy(body); + if text.len() <= 512 { + return text.into_owned(); + } + let end = text.floor_char_boundary(512); + format!("{}...", &text[..end]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn client_constructs_cleanly() { + let c = DavClient::new( + Auth::Basic { + user: "u".into(), + password: "p".into(), + }, + RetryPolicy::new(3), + false, + ); + assert_eq!(c.retries_observed(), 0); + assert_eq!(c.retry_after_sleeps(), 0); + } + + #[test] + fn auth_header_appears_via_builder() { + let c = DavClient::new( + Auth::Bearer { + token: "abc".into(), + }, + RetryPolicy::new(0), + false, + ); + let logger = c.logger(); + assert_eq!(logger.level(), LEVEL_DEFAULT); + } + + #[test] + fn truncate_preserves_char_boundaries() { + let mut body = vec![b'a'; 510]; + body.extend_from_slice("\u{1F4A9}".as_bytes()); + body.extend_from_slice(&[b'b'; 600]); + let s = truncate(&body); + assert!(s.ends_with("...")); + assert!(s.is_char_boundary(s.len())); + } +} diff --git a/src/dav/discover.rs b/src/dav/discover.rs new file mode 100644 index 0000000..f6620fb --- /dev/null +++ b/src/dav/discover.rs @@ -0,0 +1,327 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::dav::client::DavClient; +use crate::dav::href::{Href, absolute_url, normalise}; +use crate::dav::parse::DavResponse; +use crate::dav::xml; +use crate::jmap::error::JmapError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DavKind { + Caldav, + Carddav, + Webdav, +} + +impl DavKind { + pub fn well_known_uri(self) -> Option<&'static str> { + match self { + DavKind::Caldav => Some("/.well-known/caldav"), + DavKind::Carddav => Some("/.well-known/carddav"), + DavKind::Webdav => None, + } + } + + pub fn home_set_body(self) -> Option { + match self { + DavKind::Caldav => Some(xml::propfind_calendar_home_set()), + DavKind::Carddav => Some(xml::propfind_addressbook_home_set()), + DavKind::Webdav => None, + } + } + + pub fn principal_and_home_set_body(self) -> Option { + match self { + DavKind::Caldav => Some(xml::propfind_principal_and_calendar_home_set()), + DavKind::Carddav => Some(xml::propfind_principal_and_addressbook_home_set()), + DavKind::Webdav => None, + } + } + + pub fn collection_listing_body(self) -> String { + match self { + DavKind::Caldav => xml::propfind_calendar_collections(), + DavKind::Carddav => xml::propfind_addressbook_collections(), + DavKind::Webdav => xml::propfind_webdav_listing(), + } + } + + pub fn extract_home_set(self, props: &crate::dav::parse::ResourceProps) -> Option<&str> { + match self { + DavKind::Caldav => props.calendar_home_set.as_deref(), + DavKind::Carddav => props.addressbook_home_set.as_deref(), + DavKind::Webdav => None, + } + } + + pub fn item_resourcetype_matches(self, props: &crate::dav::parse::ResourceProps) -> bool { + match self { + DavKind::Caldav => props.is_calendar, + DavKind::Carddav => props.is_addressbook, + DavKind::Webdav => props.is_collection, + } + } +} + +#[derive(Debug, Clone)] +pub struct DiscoveredCollection { + pub url: String, + pub href: Href, + pub props: crate::dav::parse::ResourceProps, +} + +#[derive(Debug, thiserror::Error)] +pub enum DiscoveryError { + #[error("transport: {0}")] + Transport(#[from] JmapError), + #[error("xml: {0}")] + Parse(#[from] crate::dav::parse::ParseError), + #[error("href: {0}")] + Href(#[from] crate::dav::href::HrefError), + #[error("no DAV collections found under {url}")] + NotFound { url: String }, +} + +#[derive(Debug, Clone)] +pub struct Discovery { + pub principal_url: Option, + pub home_set_url: String, + pub collections: Vec, +} + +pub fn discover( + client: &DavClient, + kind: DavKind, + user_url: &str, +) -> Result { + if let Some(disc) = try_treat_url_as_home_or_collection(client, kind, user_url)? { + return Ok(disc); + } + if let Some(disc) = try_via_principal(client, kind, user_url)? { + return Ok(disc); + } + if let Some(well_known) = kind.well_known_uri() { + let absolute = absolute_url(user_url, &Href::from_normalised(well_known.to_owned()))?; + if let Some(disc) = try_via_principal(client, kind, &absolute)? { + return Ok(disc); + } + if let Some(disc) = try_treat_url_as_home_or_collection(client, kind, &absolute)? { + return Ok(disc); + } + } + Err(DiscoveryError::NotFound { + url: user_url.to_owned(), + }) +} + +fn try_treat_url_as_home_or_collection( + client: &DavClient, + kind: DavKind, + url: &str, +) -> Result, DiscoveryError> { + let body = kind.collection_listing_body(); + let collections = match propfind_collections(client, kind, url, &body) { + Ok(c) => c, + Err(DiscoveryError::Transport(JmapError::HttpStatus { status, .. })) + if (400..500).contains(&status) => + { + return Ok(None); + } + Err(e) => return Err(e), + }; + if collections.is_empty() { + return Ok(None); + } + Ok(Some(Discovery { + principal_url: None, + home_set_url: url.to_owned(), + collections, + })) +} + +fn try_via_principal( + client: &DavClient, + kind: DavKind, + url: &str, +) -> Result, DiscoveryError> { + let Some(combined_body) = kind.principal_and_home_set_body() else { + return Ok(None); + }; + let first_ms = match client.propfind_responses(url, 0, &combined_body, url) { + Ok(ms) => ms, + Err(JmapError::HttpStatus { status, .. }) if (400..600).contains(&status) => { + return Ok(None); + } + Err(JmapError::RetriesExhausted(_)) => return Ok(None), + Err(e) => return Err(DiscoveryError::Transport(e)), + }; + let first_final_url = first_ms.final_url.clone(); + let direct_home_set = first_ms + .responses + .iter() + .find_map(|r| kind.extract_home_set(&r.props)) + .map(|h| h.to_owned()); + let (principal_url, home_set_url) = if let Some(home) = direct_home_set { + let resolved = absolute_url(&first_final_url, &normalise(&first_final_url, &home)?)?; + (first_final_url.clone(), resolved) + } else { + let principal_href = first_ms + .responses + .iter() + .find_map(|r| r.props.current_user_principal.as_deref()) + .map(|h| h.to_owned()); + let principal_url = match principal_href { + Some(rel) => absolute_url(&first_final_url, &normalise(&first_final_url, &rel)?)?, + None => first_final_url.clone(), + }; + let Some(home_set_body) = kind.home_set_body() else { + return Ok(None); + }; + let home_ms = + match client.propfind_responses(&principal_url, 0, &home_set_body, &principal_url) { + Ok(ms) => ms, + Err(JmapError::HttpStatus { status, .. }) if (400..600).contains(&status) => { + return Ok(None); + } + Err(JmapError::RetriesExhausted(_)) => return Ok(None), + Err(e) => return Err(DiscoveryError::Transport(e)), + }; + let principal_after_redirect = home_ms.final_url.clone(); + let home_set_href = home_ms + .responses + .iter() + .find_map(|r| kind.extract_home_set(&r.props)) + .map(|h| h.to_owned()); + let Some(home_set_rel) = home_set_href else { + return Ok(None); + }; + let resolved = absolute_url( + &principal_after_redirect, + &normalise(&principal_after_redirect, &home_set_rel)?, + )?; + (principal_url, resolved) + }; + let listing_body = kind.collection_listing_body(); + let collections = propfind_collections(client, kind, &home_set_url, &listing_body)?; + if collections.is_empty() { + return Ok(None); + } + Ok(Some(Discovery { + principal_url: Some(principal_url), + home_set_url, + collections, + })) +} + +fn propfind_collections( + client: &DavClient, + kind: DavKind, + url: &str, + body: &str, +) -> Result, DiscoveryError> { + let responses = propfind_depth(client, url, 1, body)?; + let self_href = normalise(url, "").map(|h| h.into_string()).ok(); + let mut out: Vec = Vec::new(); + for r in responses { + let is_self = self_href.as_deref() == Some(r.href.as_str()); + if matches!(kind, DavKind::Webdav) { + if !r.props.is_collection { + continue; + } + if !is_self { + continue; + } + } else { + if !kind.item_resourcetype_matches(&r.props) { + continue; + } + if calendar_supports_only_vfreebusy(&r.props) { + client.logger().warn(&format!( + "calendar {} supports only {:?}; skipping", + r.href.as_str(), + r.props.supported_components, + )); + continue; + } + } + let abs = absolute_url(url, &r.href)?; + out.push(DiscoveredCollection { + url: abs, + href: r.href.clone(), + props: r.props, + }); + } + Ok(out) +} + +fn calendar_supports_only_vfreebusy(props: &crate::dav::parse::ResourceProps) -> bool { + if !props.is_calendar { + return false; + } + if props.supported_components.is_empty() { + return false; + } + let has_useful = props + .supported_components + .iter() + .any(|c| matches!(c.as_str(), "VEVENT" | "VTODO" | "VJOURNAL")); + !has_useful +} + +fn propfind_depth( + client: &DavClient, + url: &str, + depth: u8, + body: &str, +) -> Result, DiscoveryError> { + let ms = client.propfind_responses(url, depth, body, url)?; + Ok(ms.responses) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn well_known_uris_match_rfc_6764() { + assert_eq!( + DavKind::Caldav.well_known_uri(), + Some("/.well-known/caldav") + ); + assert_eq!( + DavKind::Carddav.well_known_uri(), + Some("/.well-known/carddav") + ); + assert_eq!(DavKind::Webdav.well_known_uri(), None); + } + + #[test] + fn home_set_body_only_for_calendar_addressbook() { + assert!(DavKind::Caldav.home_set_body().is_some()); + assert!(DavKind::Carddav.home_set_body().is_some()); + assert!(DavKind::Webdav.home_set_body().is_none()); + } + + #[test] + fn item_resourcetype_matches_calendar_for_caldav() { + let p = crate::dav::parse::ResourceProps { + is_calendar: true, + ..Default::default() + }; + assert!(DavKind::Caldav.item_resourcetype_matches(&p)); + assert!(!DavKind::Carddav.item_resourcetype_matches(&p)); + } + + #[test] + fn item_resourcetype_matches_collection_for_webdav() { + let p = crate::dav::parse::ResourceProps { + is_collection: true, + ..Default::default() + }; + assert!(DavKind::Webdav.item_resourcetype_matches(&p)); + } +} diff --git a/src/dav/href.rs b/src/dav/href.rs new file mode 100644 index 0000000..8ad1b23 --- /dev/null +++ b/src/dav/href.rs @@ -0,0 +1,260 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Href(String); + +impl Href { + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn into_string(self) -> String { + self.0 + } + + pub fn from_normalised(s: String) -> Href { + Href(s) + } +} + +impl AsRef for Href { + fn as_ref(&self) -> &str { + &self.0 + } +} + +#[derive(Debug, thiserror::Error)] +pub enum HrefError { + #[error("invalid base URL {url:?}: {source}")] + InvalidBase { + url: String, + #[source] + source: url::ParseError, + }, + #[error("invalid href {href:?} relative to {base:?}: {source}")] + InvalidHref { + href: String, + base: String, + #[source] + source: url::ParseError, + }, +} + +pub fn normalise(base: &str, raw: &str) -> Result { + let base_url = Url::parse(base).map_err(|e| HrefError::InvalidBase { + url: base.to_owned(), + source: e, + })?; + let resolved = base_url + .join(raw.trim()) + .map_err(|e| HrefError::InvalidHref { + href: raw.to_owned(), + base: base.to_owned(), + source: e, + })?; + Ok(normalise_path(&resolved)) +} + +pub fn normalise_path(url: &Url) -> Href { + let mut path = url.path().to_owned(); + if path.is_empty() { + path.push('/'); + } + let decoded = percent_decode(&path); + let reencoded = percent_encode_safe(&decoded); + Href(reencoded) +} + +pub fn absolute_url(base: &str, href: &Href) -> Result { + join_absolute(base, href.as_str()) +} + +pub fn join_absolute(base: &str, href: &str) -> Result { + let base_url = Url::parse(base).map_err(|e| HrefError::InvalidBase { + url: base.to_owned(), + source: e, + })?; + let resolved = base_url.join(href).map_err(|e| HrefError::InvalidHref { + href: href.to_owned(), + base: base.to_owned(), + source: e, + })?; + Ok(resolved.to_string()) +} + +pub fn last_path_component(href: &Href) -> String { + let trimmed = href.as_str().trim_end_matches('/'); + let tail = trimmed.rsplit('/').next().unwrap_or(""); + percent_decode(tail) +} + +pub fn parent_collection(href: &Href) -> Href { + let trimmed = href.as_str().trim_end_matches('/'); + if let Some(slash) = trimmed.rfind('/') { + let parent = &trimmed[..=slash]; + Href(parent.to_owned()) + } else { + Href("/".to_owned()) + } +} + +fn percent_decode(s: &str) -> String { + let bytes = s.as_bytes(); + let mut buf: Vec = Vec::with_capacity(s.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' + && i + 2 < bytes.len() + && let (Some(h), Some(l)) = (hex_val(bytes[i + 1]), hex_val(bytes[i + 2])) + { + buf.push((h << 4) | l); + i += 3; + continue; + } + buf.push(bytes[i]); + i += 1; + } + match String::from_utf8(buf) { + Ok(decoded) => decoded, + Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(), + } +} + +fn hex_val(b: u8) -> Option { + match b { + b'0'..=b'9' => Some(b - b'0'), + b'a'..=b'f' => Some(b - b'a' + 10), + b'A'..=b'F' => Some(b - b'A' + 10), + _ => None, + } +} + +fn percent_encode_safe(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for byte in s.bytes() { + if is_safe(byte) { + out.push(byte as char); + } else { + out.push('%'); + out.push(upper_hex(byte >> 4)); + out.push(upper_hex(byte & 0x0f)); + } + } + out +} + +fn is_safe(b: u8) -> bool { + matches!(b, + b'A'..=b'Z' + | b'a'..=b'z' + | b'0'..=b'9' + | b'-' | b'_' | b'.' | b'~' + | b'/' | b'@' + ) +} + +fn upper_hex(nibble: u8) -> char { + match nibble { + 0..=9 => (b'0' + nibble) as char, + 10..=15 => (b'A' + nibble - 10) as char, + _ => '0', + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn relative_href_resolves_against_base() { + let h = normalise("https://dav.example.com/", "/dav/cal/user/default/").unwrap(); + assert_eq!(h.as_str(), "/dav/cal/user/default/"); + } + + #[test] + fn absolute_href_strips_scheme_and_host() { + let h = normalise( + "https://dav.example.com/cal/", + "https://dav.example.com/dav/cal/user/default/event-1.ics", + ) + .unwrap(); + assert_eq!(h.as_str(), "/dav/cal/user/default/event-1.ics"); + } + + #[test] + fn percent_encoding_canonicalised_to_uppercase_hex() { + let h = normalise("https://x/", "/dav/cal/user%2bwork/event%201.ics").unwrap(); + assert_eq!(h.as_str(), "/dav/cal/user%2Bwork/event%201.ics"); + } + + #[test] + fn at_sign_is_preserved_in_collection_paths() { + let h = normalise("https://x/", "/dav/cal/user@host/default/").unwrap(); + assert_eq!(h.as_str(), "/dav/cal/user@host/default/"); + } + + #[test] + fn trailing_slash_preserved() { + let coll = normalise("https://x/", "/a/b/").unwrap(); + let item = normalise("https://x/", "/a/b").unwrap(); + assert_eq!(coll.as_str(), "/a/b/"); + assert_eq!(item.as_str(), "/a/b"); + assert_ne!(coll, item); + } + + #[test] + fn href_equality_is_byte_exact_after_normalisation() { + let a = normalise("https://x/", "/Dav/Cal/User/Default/").unwrap(); + let b = normalise("https://x/", "/Dav/Cal/User/Default/").unwrap(); + assert_eq!(a, b); + } + + #[test] + fn last_path_component_decodes() { + let h = Href("/dav/cal/user/event%201.ics".to_owned()); + assert_eq!(last_path_component(&h), "event 1.ics"); + } + + #[test] + fn parent_collection_strips_trailing_slash() { + let h = Href("/dav/cal/user/sub/".to_owned()); + assert_eq!(parent_collection(&h).as_str(), "/dav/cal/user/"); + } + + #[test] + fn parent_collection_of_file_is_parent_dir() { + let h = Href("/dav/cal/user/file.ics".to_owned()); + assert_eq!(parent_collection(&h).as_str(), "/dav/cal/user/"); + } + + #[test] + fn parent_collection_of_root_is_root() { + let h = Href("/".to_owned()); + assert_eq!(parent_collection(&h).as_str(), "/"); + } + + #[test] + fn empty_path_treated_as_root() { + let url = Url::parse("https://dav.example.com").unwrap(); + let h = normalise_path(&url); + assert_eq!(h.as_str(), "/"); + } + + #[test] + fn query_and_fragment_stripped() { + let h = normalise("https://x/", "/dav/cal/?q=1#frag").unwrap(); + assert_eq!(h.as_str(), "/dav/cal/"); + } + + #[test] + fn unicode_path_round_trips_through_utf8_percent_encoding() { + let h = normalise("https://x/", "/dav/cal/caf%C3%A9/").unwrap(); + assert_eq!(h.as_str(), "/dav/cal/caf%C3%A9/"); + } +} diff --git a/src/dav/mod.rs b/src/dav/mod.rs new file mode 100644 index 0000000..0981aef --- /dev/null +++ b/src/dav/mod.rs @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod client; +pub mod discover; +pub mod href; +pub mod parse; +pub mod retry; +pub mod xml; diff --git a/src/dav/parse.rs b/src/dav/parse.rs new file mode 100644 index 0000000..a490574 --- /dev/null +++ b/src/dav/parse.rs @@ -0,0 +1,904 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::BufRead; + +use quick_xml::NsReader; +use quick_xml::events::Event; +use quick_xml::name::ResolveResult; + +use crate::dav::href::{Href, normalise}; +use crate::dav::xml::{NS_APPLE_ICAL, NS_CALDAV, NS_CARDDAV, NS_DAV}; + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error("xml error: {0}")] + Xml(String), + #[error("invalid href {href:?}: {source}")] + Href { + href: String, + #[source] + source: crate::dav::href::HrefError, + }, + #[error("missing href in response")] + MissingHref, +} + +impl From for ParseError { + fn from(value: quick_xml::Error) -> Self { + ParseError::Xml(value.to_string()) + } +} + +#[derive(Debug, Clone, Default)] +pub struct ResourceProps { + pub is_collection: bool, + pub is_calendar: bool, + pub is_addressbook: bool, + pub displayname: Option, + pub current_user_principal: Option, + pub calendar_home_set: Option, + pub addressbook_home_set: Option, + pub etag: Option, + pub content_type: Option, + pub last_modified: Option, + pub creation_date: Option, + pub content_length: Option, + pub calendar_description: Option, + pub addressbook_description: Option, + pub calendar_color: Option, + pub calendar_order: Option, + pub calendar_timezone: Option, + pub supported_components: Vec, + pub calendar_data: Option, + pub address_data: Option, +} + +#[derive(Debug, Clone)] +pub struct DavResponse { + pub href: Href, + pub status: Option, + pub props: ResourceProps, + pub propstat_errors: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum NsKind { + Dav, + Caldav, + Carddav, + Apple, + Other, +} + +fn classify_ns(ns: &ResolveResult<'_>) -> NsKind { + match ns { + ResolveResult::Bound(prefix) => { + let p = prefix.as_ref(); + if p == NS_DAV.as_bytes() { + NsKind::Dav + } else if p == NS_CALDAV.as_bytes() { + NsKind::Caldav + } else if p == NS_CARDDAV.as_bytes() { + NsKind::Carddav + } else if p == NS_APPLE_ICAL.as_bytes() { + NsKind::Apple + } else { + NsKind::Other + } + } + _ => NsKind::Other, + } +} + +fn is(ns: NsKind, local: &[u8], expected_ns: NsKind, target: &[u8]) -> bool { + ns == expected_ns && local.eq_ignore_ascii_case(target) +} + +#[derive(Debug)] +enum Step { + StartElement { + ns: NsKind, + local: Vec, + attrs: Vec<(Vec, Vec)>, + }, + EmptyElement { + ns: NsKind, + local: Vec, + attrs: Vec<(Vec, Vec)>, + }, + EndElement { + ns: NsKind, + local: Vec, + }, + Text(String), + CData(String), + Other, + Eof, +} + +fn next_step(xml: &mut NsReader, buf: &mut Vec) -> Result { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(buf)?; + let ns_kind = classify_ns(&ns); + Ok(match ev { + Event::Start(e) => { + let local = e.local_name().as_ref().to_vec(); + let mut attrs: Vec<(Vec, Vec)> = Vec::new(); + for a in e.attributes().flatten() { + attrs.push((a.key.as_ref().to_vec(), a.value.as_ref().to_vec())); + } + Step::StartElement { + ns: ns_kind, + local, + attrs, + } + } + Event::Empty(e) => { + let local = e.local_name().as_ref().to_vec(); + let mut attrs: Vec<(Vec, Vec)> = Vec::new(); + for a in e.attributes().flatten() { + attrs.push((a.key.as_ref().to_vec(), a.value.as_ref().to_vec())); + } + Step::EmptyElement { + ns: ns_kind, + local, + attrs, + } + } + Event::End(e) => Step::EndElement { + ns: ns_kind, + local: e.local_name().as_ref().to_vec(), + }, + Event::Text(t) => { + let s = t.decode().map_err(|e| ParseError::Xml(e.to_string()))?; + Step::Text(s.into_owned()) + } + Event::CData(cd) => Step::CData(String::from_utf8_lossy(cd.as_ref()).into_owned()), + Event::Eof => Step::Eof, + _ => Step::Other, + }) +} + +pub fn parse_multistatus( + reader: R, + base_url: &str, +) -> Result, ParseError> { + let mut xml = NsReader::from_reader(reader); + let mut buf = Vec::new(); + let mut out: Vec = Vec::new(); + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + + loop { + let step = next_step(&mut xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } if is(ns, &local, NsKind::Dav, b"response") => { + if let Some(r) = parse_response(&mut xml, base_url)? + && seen.insert(r.href.as_str().to_owned()) + { + out.push(r); + } + } + Step::Eof => break, + _ => {} + } + } + Ok(out) +} + +fn parse_response( + xml: &mut NsReader, + base_url: &str, +) -> Result, ParseError> { + let mut buf = Vec::new(); + let mut href: Option = None; + let mut status: Option = None; + let mut props = ResourceProps::default(); + let mut propstat_errors: Vec = Vec::new(); + + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } => { + if is(ns, &local, NsKind::Dav, b"href") { + let text = read_token(xml)?; + if href.is_none() { + href = Some(text); + } + } else if is(ns, &local, NsKind::Dav, b"status") { + let text = read_token(xml)?; + status = parse_http_status(&text); + } else if is(ns, &local, NsKind::Dav, b"propstat") { + let (block_status, block_props) = parse_propstat(xml)?; + match block_status { + Some(s) if (200..300).contains(&s) => merge_props(&mut props, block_props), + Some(s) => propstat_errors.push(s), + None => {} + } + } else { + skip_element(xml, &local)?; + } + } + Step::EndElement { ns, local } if is(ns, &local, NsKind::Dav, b"response") => break, + Step::Eof => return Err(ParseError::Xml("unexpected EOF in ".into())), + _ => {} + } + } + + let raw_href = href.ok_or(ParseError::MissingHref)?; + let normalised = normalise(base_url, &raw_href).map_err(|e| ParseError::Href { + href: raw_href, + source: e, + })?; + Ok(Some(DavResponse { + href: normalised, + status, + props, + propstat_errors, + })) +} + +fn parse_propstat( + xml: &mut NsReader, +) -> Result<(Option, ResourceProps), ParseError> { + let mut buf = Vec::new(); + let mut status: Option = None; + let mut props = ResourceProps::default(); + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } => { + if is(ns, &local, NsKind::Dav, b"status") { + let text = read_token(xml)?; + status = parse_http_status(&text); + } else if is(ns, &local, NsKind::Dav, b"prop") { + parse_prop_block(xml, &mut props)?; + } else { + skip_element(xml, &local)?; + } + } + Step::EndElement { ns, local } if is(ns, &local, NsKind::Dav, b"propstat") => break, + Step::Eof => return Err(ParseError::Xml("unexpected EOF in ".into())), + _ => {} + } + } + Ok((status, props)) +} + +fn parse_prop_block( + xml: &mut NsReader, + props: &mut ResourceProps, +) -> Result<(), ParseError> { + let mut buf = Vec::new(); + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } => { + handle_prop_element(xml, props, ns, &local)?; + } + Step::EmptyElement { .. } => {} + Step::EndElement { ns, local } if is(ns, &local, NsKind::Dav, b"prop") => break, + Step::Eof => return Err(ParseError::Xml("unexpected EOF in ".into())), + _ => {} + } + } + Ok(()) +} + +fn handle_prop_element( + xml: &mut NsReader, + props: &mut ResourceProps, + ns: NsKind, + local: &[u8], +) -> Result<(), ParseError> { + if is(ns, local, NsKind::Dav, b"resourcetype") { + consume_resourcetype(xml, props)?; + } else if is(ns, local, NsKind::Dav, b"displayname") { + props.displayname = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Dav, b"current-user-principal") { + props.current_user_principal = read_first_href(xml, b"current-user-principal")?; + } else if is(ns, local, NsKind::Caldav, b"calendar-home-set") { + props.calendar_home_set = read_first_href(xml, b"calendar-home-set")?; + } else if is(ns, local, NsKind::Carddav, b"addressbook-home-set") { + props.addressbook_home_set = read_first_href(xml, b"addressbook-home-set")?; + } else if is(ns, local, NsKind::Dav, b"getetag") { + let raw = read_token(xml)?; + if !raw.is_empty() { + props.etag = Some(raw); + } + } else if is(ns, local, NsKind::Dav, b"getcontenttype") { + props.content_type = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Dav, b"getlastmodified") { + props.last_modified = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Dav, b"creationdate") { + props.creation_date = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Dav, b"getcontentlength") { + let raw = read_token(xml)?; + props.content_length = raw.parse::().ok(); + } else if is(ns, local, NsKind::Caldav, b"calendar-description") { + props.calendar_description = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Carddav, b"addressbook-description") { + props.addressbook_description = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Apple, b"calendar-color") { + props.calendar_color = Some(read_token(xml)?); + } else if is(ns, local, NsKind::Apple, b"calendar-order") { + let raw = read_token(xml)?; + props.calendar_order = raw.parse::().ok(); + } else if is(ns, local, NsKind::Caldav, b"calendar-timezone") { + props.calendar_timezone = Some(read_token(xml)?); + } else if is( + ns, + local, + NsKind::Caldav, + b"supported-calendar-component-set", + ) { + consume_supported_components(xml, props)?; + } else if is(ns, local, NsKind::Caldav, b"calendar-data") { + props.calendar_data = Some(read_text(xml)?); + } else if is(ns, local, NsKind::Carddav, b"address-data") { + props.address_data = Some(read_text(xml)?); + } else { + skip_element(xml, local)?; + } + Ok(()) +} + +fn consume_resourcetype( + xml: &mut NsReader, + props: &mut ResourceProps, +) -> Result<(), ParseError> { + let mut buf = Vec::new(); + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } => { + mark_resourcetype(ns, &local, props); + skip_element(xml, &local)?; + } + Step::EmptyElement { ns, local, .. } => { + mark_resourcetype(ns, &local, props); + } + Step::EndElement { ns, local } if is(ns, &local, NsKind::Dav, b"resourcetype") => break, + Step::Eof => { + return Err(ParseError::Xml("unexpected EOF in ".into())); + } + _ => {} + } + } + Ok(()) +} + +fn mark_resourcetype(ns: NsKind, local: &[u8], props: &mut ResourceProps) { + if is(ns, local, NsKind::Dav, b"collection") { + props.is_collection = true; + } else if is(ns, local, NsKind::Caldav, b"calendar") { + props.is_calendar = true; + } else if is(ns, local, NsKind::Carddav, b"addressbook") { + props.is_addressbook = true; + } +} + +fn consume_supported_components( + xml: &mut NsReader, + props: &mut ResourceProps, +) -> Result<(), ParseError> { + let mut buf = Vec::new(); + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { + ns, + local, + ref attrs, + } if is(ns, &local, NsKind::Caldav, b"comp") => { + if let Some(name) = attr_lookup(attrs, b"name") { + props.supported_components.push(name); + } + skip_element(xml, &local)?; + } + Step::EmptyElement { + ns, + local, + ref attrs, + } if is(ns, &local, NsKind::Caldav, b"comp") => { + if let Some(name) = attr_lookup(attrs, b"name") { + props.supported_components.push(name); + } + } + Step::EndElement { ns, local } + if is( + ns, + &local, + NsKind::Caldav, + b"supported-calendar-component-set", + ) => + { + break; + } + Step::Eof => { + return Err(ParseError::Xml( + "unexpected EOF in ".into(), + )); + } + _ => {} + } + } + Ok(()) +} + +fn attr_lookup(attrs: &[(Vec, Vec)], key: &[u8]) -> Option { + for (k, v) in attrs { + if k == key { + return Some(String::from_utf8_lossy(v).into_owned()); + } + } + None +} + +fn read_text(xml: &mut NsReader) -> Result { + let mut buf = Vec::new(); + let mut out = String::new(); + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::Text(s) | Step::CData(s) => out.push_str(&s), + Step::StartElement { local, .. } => skip_element(xml, &local)?, + Step::EndElement { .. } | Step::EmptyElement { .. } => break, + Step::Eof => return Err(ParseError::Xml("unexpected EOF reading text".into())), + _ => {} + } + } + Ok(out) +} + +fn read_token(xml: &mut NsReader) -> Result { + Ok(read_text(xml)?.trim().to_owned()) +} + +fn read_first_href( + xml: &mut NsReader, + closing_local: &[u8], +) -> Result, ParseError> { + let mut buf = Vec::new(); + let mut out: Option = None; + loop { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { ns, local, .. } => { + if out.is_none() && is(ns, &local, NsKind::Dav, b"href") { + out = Some(read_token(xml)?); + } else { + skip_element(xml, &local)?; + } + } + Step::EmptyElement { .. } => {} + Step::EndElement { local, .. } if local.eq_ignore_ascii_case(closing_local) => { + break; + } + Step::Eof => return Err(ParseError::Xml("unexpected EOF reading href".into())), + _ => {} + } + } + Ok(out) +} + +fn skip_element(xml: &mut NsReader, target: &[u8]) -> Result<(), ParseError> { + let mut buf = Vec::new(); + let mut depth: i32 = 1; + while depth > 0 { + let step = next_step(xml, &mut buf)?; + match step { + Step::StartElement { .. } => depth += 1, + Step::EndElement { local, .. } => { + depth -= 1; + if depth == 0 && !local.eq_ignore_ascii_case(target) { + return Ok(()); + } + } + Step::Eof => return Err(ParseError::Xml("unexpected EOF in skip".into())), + _ => {} + } + } + Ok(()) +} + +fn merge_props(target: &mut ResourceProps, src: ResourceProps) { + if src.is_collection { + target.is_collection = true; + } + if src.is_calendar { + target.is_calendar = true; + } + if src.is_addressbook { + target.is_addressbook = true; + } + overwrite_some(&mut target.displayname, src.displayname); + overwrite_some( + &mut target.current_user_principal, + src.current_user_principal, + ); + overwrite_some(&mut target.calendar_home_set, src.calendar_home_set); + overwrite_some(&mut target.addressbook_home_set, src.addressbook_home_set); + overwrite_some(&mut target.etag, src.etag); + overwrite_some(&mut target.content_type, src.content_type); + overwrite_some(&mut target.last_modified, src.last_modified); + overwrite_some(&mut target.creation_date, src.creation_date); + overwrite_some(&mut target.content_length, src.content_length); + overwrite_some(&mut target.calendar_description, src.calendar_description); + overwrite_some( + &mut target.addressbook_description, + src.addressbook_description, + ); + overwrite_some(&mut target.calendar_color, src.calendar_color); + overwrite_some(&mut target.calendar_order, src.calendar_order); + overwrite_some(&mut target.calendar_timezone, src.calendar_timezone); + overwrite_some(&mut target.calendar_data, src.calendar_data); + overwrite_some(&mut target.address_data, src.address_data); + if !src.supported_components.is_empty() { + target.supported_components = src.supported_components; + } +} + +fn overwrite_some(target: &mut Option, src: Option) { + if src.is_some() { + *target = src; + } +} + +fn parse_http_status(text: &str) -> Option { + let mut parts = text.split_ascii_whitespace(); + let _version = parts.next()?; + let code = parts.next()?; + code.parse::().ok() +} + +pub fn strip_ascii_control_chars(body: &[u8]) -> std::borrow::Cow<'_, [u8]> { + if body.iter().any(|&b| is_xml_illegal_control(b)) { + let cleaned: Vec = body + .iter() + .copied() + .filter(|b| !is_xml_illegal_control(*b)) + .collect(); + std::borrow::Cow::Owned(cleaned) + } else { + std::borrow::Cow::Borrowed(body) + } +} + +pub fn is_xml_illegal_control(b: u8) -> bool { + matches!(b, 0x00..=0x08 | 0x0b | 0x0c | 0x0e..=0x1f) +} + +pub struct ControlStrippingReader { + inner: R, +} + +impl ControlStrippingReader { + pub fn new(inner: R) -> Self { + Self { inner } + } +} + +impl std::io::Read for ControlStrippingReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + loop { + let n = self.inner.read(buf)?; + if n == 0 { + return Ok(0); + } + let mut write = 0; + for read in 0..n { + let b = buf[read]; + if !is_xml_illegal_control(b) { + buf[write] = b; + write += 1; + } + } + if write > 0 { + return Ok(write); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + fn parse(body: &str) -> Vec { + parse_multistatus(Cursor::new(body), "https://x/").expect("parse") + } + + #[test] + fn parses_simple_principal_response() { + let body = r#" + + + /dav/ + + + + /dav/principals/alice/ + + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r.len(), 1); + assert_eq!( + r[0].props.current_user_principal.as_deref(), + Some("/dav/principals/alice/") + ); + } + + #[test] + fn parses_calendar_collection_with_apple_color_and_order() { + let body = r#" + + + /dav/cal/u/default/ + + + + Default + #3366cc + 5 + Work + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r.len(), 1); + assert!(r[0].props.is_collection); + assert!(r[0].props.is_calendar); + assert_eq!(r[0].props.displayname.as_deref(), Some("Default")); + assert_eq!(r[0].props.calendar_color.as_deref(), Some("#3366cc")); + assert_eq!(r[0].props.calendar_order, Some(5)); + assert_eq!(r[0].props.calendar_description.as_deref(), Some("Work")); + } + + #[test] + fn parses_item_etag_and_content_type() { + let body = r#" + + + /dav/cal/u/d/event1.ics + + + + "abc123" + text/calendar; charset=utf-8 + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r[0].props.etag.as_deref(), Some("\"abc123\"")); + assert!(!r[0].props.is_collection); + assert!( + r[0].props + .content_type + .as_deref() + .unwrap() + .starts_with("text/calendar") + ); + } + + #[test] + fn parses_calendar_multiget_response_with_data() { + let body = r#" + + + /dav/cal/u/d/event1.ics + + + "v2" + BEGIN:VCALENDAR +END:VCALENDAR + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r[0].props.etag.as_deref(), Some("\"v2\"")); + let data = r[0].props.calendar_data.as_deref().unwrap(); + assert!(data.contains("VCALENDAR")); + } + + #[test] + fn drops_duplicate_response_hrefs() { + let body = r#" + + + /dav/cal/u/d/event.ics + + "a" + HTTP/1.1 200 OK + + + + /dav/cal/u/d/event.ics + + "a" + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r.len(), 1); + } + + #[test] + fn captures_response_level_status_for_vanished_items() { + let body = r#" + + + /dav/cal/u/d/gone.ics + HTTP/1.1 404 Not Found + + "#; + let r = parse(body); + assert_eq!(r[0].status, Some(404)); + } + + #[test] + fn collects_propstat_errors_separately() { + let body = r#" + + + /dav/cal/u/d/event.ics + + "a" + HTTP/1.1 200 OK + + + + HTTP/1.1 404 Not Found + + + "#; + let r = parse(body); + assert_eq!(r[0].propstat_errors, vec![404]); + assert_eq!(r[0].props.etag.as_deref(), Some("\"a\"")); + } + + #[test] + fn unknown_namespace_elements_ignored() { + let body = r#" + + + /dav/cal/u/d/ + + + + abc + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert!(r[0].props.is_collection); + } + + #[test] + fn strip_control_chars_removes_low_bytes() { + let raw = b"\x01"; + let cleaned = strip_ascii_control_chars(raw); + assert!(!cleaned.contains(&0x01)); + } + + #[test] + fn strip_control_chars_leaves_tab_newline_cr() { + let raw = b"\t\n\r"; + let cleaned = strip_ascii_control_chars(raw); + match cleaned { + std::borrow::Cow::Borrowed(_) => {} + std::borrow::Cow::Owned(_) => panic!("should not have allocated"), + } + } + + #[test] + fn parses_addressbook_collection() { + let body = r#" + + + /dav/card/u/default/ + + + + Contacts + Personal + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert!(r[0].props.is_addressbook); + assert_eq!(r[0].props.displayname.as_deref(), Some("Contacts")); + assert_eq!( + r[0].props.addressbook_description.as_deref(), + Some("Personal") + ); + } + + #[test] + fn empty_multistatus_yields_no_responses() { + let body = r#" + "#; + let r = parse(body); + assert!(r.is_empty()); + } + + #[test] + fn supported_calendar_component_set_captures_empty_element_name_attr() { + let body = r#" + + + /dav/cal/u/d/ + + + + + + + + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r[0].props.supported_components, vec!["VEVENT", "VTODO"]); + } + + #[test] + fn merge_props_last_propstat_wins_for_duplicate_keys() { + let body = r#" + + + /dav/cal/u/d/ + + First + HTTP/1.1 200 OK + + + Second + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r[0].props.displayname.as_deref(), Some("Second")); + } + + #[test] + fn content_length_parses_to_u64() { + let body = r#" + + + /file.bin + + + 123456 + + + HTTP/1.1 200 OK + + + "#; + let r = parse(body); + assert_eq!(r[0].props.content_length, Some(123456)); + } +} diff --git a/src/dav/retry.rs b/src/dav/retry.rs new file mode 100644 index 0000000..b33cc86 --- /dev/null +++ b/src/dav/retry.rs @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::Value; + +use crate::jmap::retry::Disposition; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DavOutcome { + Success, + Retryable, + Vanished, + Auth, + Fatal, +} + +pub fn classify(status: u16, body: &[u8]) -> DavOutcome { + if (200..300).contains(&status) { + return DavOutcome::Success; + } + if status == 401 { + return DavOutcome::Auth; + } + if status == 403 { + if is_google_usage_limit(body) { + return DavOutcome::Retryable; + } + return DavOutcome::Auth; + } + if status == 404 || status == 410 { + return DavOutcome::Vanished; + } + if matches!(status, 423 | 429 | 502 | 503 | 504 | 507) { + return DavOutcome::Retryable; + } + match crate::jmap::retry::classify_http_status(status) { + Disposition::Retryable => DavOutcome::Retryable, + Disposition::Fatal => DavOutcome::Fatal, + } +} + +fn is_google_usage_limit(body: &[u8]) -> bool { + let value: Value = match serde_json::from_slice(body) { + Ok(v) => v, + Err(_) => return false, + }; + let errors = value + .get("error") + .and_then(|e| e.get("errors")) + .and_then(Value::as_array); + let Some(errors) = errors else { + return false; + }; + errors.iter().any(|entry| { + entry + .get("domain") + .and_then(Value::as_str) + .map(|d| d == "usageLimits") + .unwrap_or(false) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn success_range() { + assert_eq!(classify(200, b""), DavOutcome::Success); + assert_eq!(classify(207, b""), DavOutcome::Success); + assert_eq!(classify(299, b""), DavOutcome::Success); + } + + #[test] + fn auth_status() { + assert_eq!(classify(401, b""), DavOutcome::Auth); + assert_eq!(classify(403, b""), DavOutcome::Auth); + } + + #[test] + fn vanished_status() { + assert_eq!(classify(404, b""), DavOutcome::Vanished); + assert_eq!(classify(410, b""), DavOutcome::Vanished); + } + + #[test] + fn locked_and_quota_are_retryable() { + assert_eq!(classify(423, b""), DavOutcome::Retryable); + assert_eq!(classify(429, b""), DavOutcome::Retryable); + assert_eq!(classify(503, b""), DavOutcome::Retryable); + assert_eq!(classify(507, b""), DavOutcome::Retryable); + } + + #[test] + fn google_usage_limit_403_is_retryable() { + let body = + br#"{"error":{"errors":[{"domain":"usageLimits","reason":"rateLimitExceeded"}]}}"#; + assert_eq!(classify(403, body), DavOutcome::Retryable); + } + + #[test] + fn non_google_403_is_auth() { + let body = br#"{"error":{"errors":[{"domain":"global","reason":"forbidden"}]}}"#; + assert_eq!(classify(403, body), DavOutcome::Auth); + } + + #[test] + fn fatal_4xx_other() { + assert_eq!(classify(400, b""), DavOutcome::Fatal); + assert_eq!(classify(405, b""), DavOutcome::Fatal); + assert_eq!(classify(412, b""), DavOutcome::Fatal); + } +} diff --git a/src/dav/xml.rs b/src/dav/xml.rs new file mode 100644 index 0000000..791a523 --- /dev/null +++ b/src/dav/xml.rs @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::dav::href::Href; + +pub const NS_DAV: &str = "DAV:"; +pub const NS_CALDAV: &str = "urn:ietf:params:xml:ns:caldav"; +pub const NS_CARDDAV: &str = "urn:ietf:params:xml:ns:carddav"; +pub const NS_APPLE_ICAL: &str = "http://apple.com/ns/ical/"; + +pub fn propfind_current_user_principal() -> String { + String::from( + "\n\ + \ + \ + ", + ) +} + +pub fn propfind_calendar_home_set() -> String { + String::from( + "\n\ + \ + \ + ", + ) +} + +pub fn propfind_addressbook_home_set() -> String { + String::from( + "\n\ + \ + \ + ", + ) +} + +pub fn propfind_principal_and_calendar_home_set() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn propfind_principal_and_addressbook_home_set() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn propfind_calendar_collections() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn propfind_addressbook_collections() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn propfind_webdav_listing() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn propfind_dav_items() -> String { + String::from( + "\n\ + \ + \ + \ + \ + \ + \ + \ + ", + ) +} + +pub fn calendar_multiget(hrefs: &[Href]) -> String { + let mut out = String::with_capacity(256 + hrefs.len() * 64); + out.push_str( + "\n\ + \ + \ + \ + \ + ", + ); + for h in hrefs { + write_href_element(&mut out, h); + } + out.push_str(""); + out +} + +pub fn addressbook_multiget(hrefs: &[Href]) -> String { + let mut out = String::with_capacity(256 + hrefs.len() * 64); + out.push_str( + "\n\ + \ + \ + \ + \ + ", + ); + for h in hrefs { + write_href_element(&mut out, h); + } + out.push_str(""); + out +} + +fn write_href_element(out: &mut String, h: &Href) { + out.push_str(""); + write_escaped(out, h.as_str()); + out.push_str(""); +} + +fn write_escaped(out: &mut String, s: &str) { + for ch in s.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\'' => out.push_str("'"), + _ => out.push(ch), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dav::href::Href; + + #[test] + fn principal_body_matches_expected_shape() { + let body = propfind_current_user_principal(); + assert!(body.starts_with("")); + assert!(body.contains("xmlns:d=\"DAV:\"")); + } + + #[test] + fn calendar_home_set_includes_caldav_namespace() { + let body = propfind_calendar_home_set(); + assert!(body.contains("xmlns:c=\"urn:ietf:params:xml:ns:caldav\"")); + assert!(body.contains("")); + } + + #[test] + fn addressbook_home_set_includes_carddav_namespace() { + let body = propfind_addressbook_home_set(); + assert!(body.contains("xmlns:c=\"urn:ietf:params:xml:ns:carddav\"")); + assert!(body.contains("")); + } + + #[test] + fn calendar_multiget_lists_hrefs_in_order() { + let hrefs = vec![ + Href::from_normalised("/dav/cal/u/d/a.ics".to_owned()), + Href::from_normalised("/dav/cal/u/d/b.ics".to_owned()), + ]; + let body = calendar_multiget(&hrefs); + let a_pos = body.find("/a.ics").unwrap(); + let b_pos = body.find("/b.ics").unwrap(); + assert!(a_pos < b_pos); + assert!(body.contains("")); + } + + #[test] + fn addressbook_multiget_emits_address_data_element() { + let hrefs = vec![Href::from_normalised("/dav/card/u/d/a.vcf".to_owned())]; + let body = addressbook_multiget(&hrefs); + assert!(body.contains("")); + assert!(body.contains("/dav/card/u/d/a.vcf")); + } + + #[test] + fn escape_handles_xml_metacharacters() { + let h = Href::from_normalised("/dav/cal/u/d/<weird>.ics".to_owned()); + let body = calendar_multiget(&[h]); + assert!(body.contains("&lt;weird&gt;.ics")); + } + + #[test] + fn webdav_listing_requests_filesystem_props() { + let body = propfind_webdav_listing(); + assert!(body.contains("")); + assert!(body.contains("")); + assert!(body.contains("")); + assert!(body.contains("")); + } +} diff --git a/src/db/blobs.rs b/src/db/blobs.rs new file mode 100644 index 0000000..1c91668 --- /dev/null +++ b/src/db/blobs.rs @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, OptionalExtension, params}; + +pub fn intern_blob(conn: &Connection, bytes: &[u8]) -> Result { + let hash = blake3::hash(bytes); + let hash_bytes = hash.as_bytes().as_slice(); + conn.execute( + "INSERT OR IGNORE INTO blobs (hash, data) VALUES (?1, ?2)", + params![hash_bytes, bytes], + )?; + conn.query_row( + "SELECT id FROM blobs WHERE hash = ?1", + params![hash_bytes], + |row| row.get(0), + ) +} + +pub fn blob_bytes(conn: &Connection, id: i64) -> Result>, rusqlite::Error> { + conn.query_row("SELECT data FROM blobs WHERE id = ?1", params![id], |row| { + row.get(0) + }) + .optional() +} + +pub fn gc_orphan_blobs(conn: &Connection) -> Result { + conn.execute( + "DELETE FROM blobs WHERE id NOT IN ( + SELECT blob_id FROM emails + UNION SELECT blob_id FROM sieve_scripts + UNION SELECT blob_id FROM file_nodes WHERE blob_id IS NOT NULL + UNION SELECT json_tree.atom FROM contact_cards, json_tree(contact_cards.data) + WHERE json_tree.key = '@blob' + UNION SELECT json_tree.atom FROM calendar_events, json_tree(calendar_events.data) + WHERE json_tree.key = '@blob')", + [], + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + #[test] + fn intern_dedups_identical_bytes() { + let c = mem(); + let a = intern_blob(&c, b"hello world").unwrap(); + let b = intern_blob(&c, b"hello world").unwrap(); + let other = intern_blob(&c, b"different").unwrap(); + assert_eq!(a, b); + assert_ne!(a, other); + let n: i64 = c + .query_row("SELECT count(*) FROM blobs", [], |r| r.get(0)) + .unwrap(); + assert_eq!(n, 2); + } + + #[test] + fn blob_bytes_roundtrip() { + let c = mem(); + let id = intern_blob(&c, b"payload").unwrap(); + assert_eq!( + blob_bytes(&c, id).unwrap().as_deref(), + Some(&b"payload"[..]) + ); + assert_eq!(blob_bytes(&c, 9999).unwrap(), None); + } + + #[test] + fn gc_keeps_referenced_and_reaps_orphans() { + let c = mem(); + let referenced = intern_blob(&c, b"keep me").unwrap(); + let _orphan = intern_blob(&c, b"reap me").unwrap(); + c.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords) + VALUES (?1, '2020-01-01T00:00:00Z', '[1]', '[]')", + params![referenced], + ) + .unwrap(); + let removed = gc_orphan_blobs(&c).unwrap(); + assert_eq!(removed, 1); + assert!(blob_bytes(&c, referenced).unwrap().is_some()); + } + + #[test] + fn gc_follows_blob_sentinel_in_json_data() { + let c = mem(); + let photo = intern_blob(&c, b"photo bytes").unwrap(); + let _orphan = intern_blob(&c, b"orphan").unwrap(); + c.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) + VALUES ('u1', '[1]', ?1)", + params![format!(r#"{{"photos":{{"p":{{"@blob":{photo}}}}}}}"#)], + ) + .unwrap(); + let removed = gc_orphan_blobs(&c).unwrap(); + assert_eq!(removed, 1); + assert!(blob_bytes(&c, photo).unwrap().is_some()); + } +} diff --git a/src/db/dav_ids.rs b/src/db/dav_ids.rs new file mode 100644 index 0000000..a7cf537 --- /dev/null +++ b/src/db/dav_ids.rs @@ -0,0 +1,336 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +pub const CALENDAR: &str = "calendar"; +pub const CALENDAR_EVENT: &str = "calendarevent"; +pub const ADDRESS_BOOK: &str = "addressbook"; +pub const CONTACT_CARD: &str = "contactcard"; +pub const FILE_NODE: &str = "filenode"; + +#[derive(Debug, Clone)] +pub struct ItemRow { + pub item_href: String, + pub etag: String, + pub local_id: i64, +} + +pub fn insert( + conn: &Connection, + source_id: i64, + type_name: &str, + collection_href: &str, + item_href: &str, + etag: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_dav + (source_id, type_name, collection_href, item_href, etag, local_id) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![ + source_id, + type_name, + collection_href, + item_href, + etag, + local_id + ], + )?; + Ok(()) +} + +pub fn update_etag( + conn: &Connection, + source_id: i64, + type_name: &str, + item_href: &str, + etag: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "UPDATE sync_id_dav + SET etag = ?4 + WHERE source_id = ?1 AND type_name = ?2 AND item_href = ?3", + params![source_id, type_name, item_href, etag], + )?; + Ok(()) +} + +pub fn local_for_item( + conn: &Connection, + source_id: i64, + type_name: &str, + item_href: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2 AND item_href = ?3", + params![source_id, type_name, item_href], + |row| row.get(0), + ) + .optional() +} + +pub fn items_in_collection( + conn: &Connection, + source_id: i64, + type_name: &str, + collection_href: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT item_href, etag, local_id FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2 AND collection_href = ?3", + )?; + let rows = stmt.query_map(params![source_id, type_name, collection_href], |row| { + Ok(ItemRow { + item_href: row.get(0)?, + etag: row.get(1)?, + local_id: row.get(2)?, + }) + })?; + let mut out = Vec::new(); + for r in rows { + out.push(r?); + } + Ok(out) +} + +pub fn collections_of_type( + conn: &Connection, + source_id: i64, + type_name: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT item_href, local_id FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, type_name], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (href, id) = r?; + map.insert(href, id); + } + Ok(map) +} + +pub fn delete_item( + conn: &Connection, + source_id: i64, + type_name: &str, + item_href: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2 AND item_href = ?3", + params![source_id, type_name, item_href], + )?; + Ok(()) +} + +pub fn delete_collection( + conn: &Connection, + source_id: i64, + type_name: &str, + collection_href: &str, +) -> Result { + let removed = conn.execute( + "DELETE FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2 + AND (item_href = ?3 OR collection_href = ?3)", + params![source_id, type_name, collection_href], + )?; + Ok(removed) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (rusqlite::Connection, i64) { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let key = SourceKey { + kind: "caldav".to_owned(), + session_url: "https://x/".to_owned(), + account_id: "u".to_owned(), + }; + let sid = upsert_source(&conn, &key, Some("u"), "u").unwrap(); + (conn, sid) + } + + #[test] + fn insert_and_lookup_round_trip() { + let (conn, sid) = setup(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e.ics", + "\"v1\"", + 7, + ) + .unwrap(); + let id = local_for_item(&conn, sid, CALENDAR_EVENT, "/cal/a/e.ics") + .unwrap() + .unwrap(); + assert_eq!(id, 7); + } + + #[test] + fn replace_keeps_local_id_on_etag_change() { + let (conn, sid) = setup(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e.ics", + "\"v1\"", + 7, + ) + .unwrap(); + update_etag(&conn, sid, CALENDAR_EVENT, "/cal/a/e.ics", "\"v2\"").unwrap(); + let rows = items_in_collection(&conn, sid, CALENDAR_EVENT, "/cal/a/").unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].etag, "\"v2\""); + assert_eq!(rows[0].local_id, 7); + } + + #[test] + fn items_in_collection_filters_by_collection_href() { + let (conn, sid) = setup(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e1.ics", + "v1", + 1, + ) + .unwrap(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e2.ics", + "v2", + 2, + ) + .unwrap(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/b/", + "/cal/b/e3.ics", + "v3", + 3, + ) + .unwrap(); + let rows = items_in_collection(&conn, sid, CALENDAR_EVENT, "/cal/a/").unwrap(); + assert_eq!(rows.len(), 2); + } + + #[test] + fn delete_item_removes_only_one_row() { + let (conn, sid) = setup(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e1.ics", + "v", + 1, + ) + .unwrap(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e2.ics", + "v", + 2, + ) + .unwrap(); + delete_item(&conn, sid, CALENDAR_EVENT, "/cal/a/e1.ics").unwrap(); + let rows = items_in_collection(&conn, sid, CALENDAR_EVENT, "/cal/a/").unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].item_href, "/cal/a/e2.ics"); + } + + #[test] + fn delete_collection_removes_collection_row_and_items() { + let (conn, sid) = setup(); + insert(&conn, sid, CALENDAR, "/cal/a/", "/cal/a/", "", 10).unwrap(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e.ics", + "v", + 1, + ) + .unwrap(); + let removed = delete_collection(&conn, sid, CALENDAR_EVENT, "/cal/a/").unwrap(); + assert_eq!(removed, 1); + let rows = items_in_collection(&conn, sid, CALENDAR_EVENT, "/cal/a/").unwrap(); + assert!(rows.is_empty()); + } + + #[test] + fn collections_of_type_lists_all_known_collections() { + let (conn, sid) = setup(); + insert(&conn, sid, CALENDAR, "/cal/a/", "/cal/a/", "", 1).unwrap(); + insert(&conn, sid, CALENDAR, "/cal/b/", "/cal/b/", "", 2).unwrap(); + let map = collections_of_type(&conn, sid, CALENDAR).unwrap(); + assert_eq!(map.len(), 2); + assert_eq!(map.get("/cal/a/"), Some(&1)); + assert_eq!(map.get("/cal/b/"), Some(&2)); + } + + #[test] + fn unique_local_id_per_type_enforced() { + let (conn, sid) = setup(); + insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e1.ics", + "v", + 1, + ) + .unwrap(); + let err = insert( + &conn, + sid, + CALENDAR_EVENT, + "/cal/a/", + "/cal/a/e2.ics", + "v", + 1, + ) + .unwrap_err(); + let msg = format!("{err}"); + assert!( + msg.contains("UNIQUE"), + "expected UNIQUE violation, got {msg}" + ); + } +} diff --git a/src/db/exchange_ews_ids.rs b/src/db/exchange_ews_ids.rs new file mode 100644 index 0000000..05d68b0 --- /dev/null +++ b/src/db/exchange_ews_ids.rs @@ -0,0 +1,265 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +pub const MAILBOX: &str = "mailbox"; +pub const CALENDAR: &str = "calendar"; +pub const ADDRESS_BOOK: &str = "addressbook"; +pub const EMAIL: &str = "email"; +pub const CALENDAR_EVENT: &str = "calendarevent"; +pub const CONTACT_CARD: &str = "contactcard"; + +#[derive(Debug, Clone)] +pub struct ItemRow { + pub item_id: String, + pub change_key: String, + pub local_id: i64, +} + +#[derive(Debug, Clone)] +pub struct FolderRow { + pub item_id: String, + pub folder_id: String, + pub change_key: String, + pub local_id: i64, +} + +pub fn insert( + conn: &Connection, + source_id: i64, + type_name: &str, + folder_id: &str, + item_id: &str, + change_key: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_exchange_ews + (source_id, type_name, folder_id, item_id, change_key, sync_state, local_id) + VALUES (?1, ?2, ?3, ?4, ?5, '', ?6)", + params![ + source_id, type_name, folder_id, item_id, change_key, local_id + ], + )?; + Ok(()) +} + +pub fn update_change_key( + conn: &Connection, + source_id: i64, + type_name: &str, + item_id: &str, + change_key: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "UPDATE sync_id_exchange_ews + SET change_key = ?4 + WHERE source_id = ?1 AND type_name = ?2 AND item_id = ?3", + params![source_id, type_name, item_id, change_key], + )?; + Ok(()) +} + +pub fn delete_item( + conn: &Connection, + source_id: i64, + type_name: &str, + item_id: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_exchange_ews + WHERE source_id = ?1 AND type_name = ?2 AND item_id = ?3", + params![source_id, type_name, item_id], + )?; + Ok(()) +} + +pub fn local_for_item( + conn: &Connection, + source_id: i64, + type_name: &str, + item_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_exchange_ews + WHERE source_id = ?1 AND type_name = ?2 AND item_id = ?3", + params![source_id, type_name, item_id], + |row| row.get(0), + ) + .optional() +} + +pub fn items_in_folder( + conn: &Connection, + source_id: i64, + type_name: &str, + folder_id: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT item_id, change_key, local_id FROM sync_id_exchange_ews + WHERE source_id = ?1 AND type_name = ?2 AND folder_id = ?3", + )?; + let rows = stmt.query_map(params![source_id, type_name, folder_id], |row| { + Ok(ItemRow { + item_id: row.get(0)?, + change_key: row.get(1)?, + local_id: row.get(2)?, + }) + })?; + let mut out = Vec::new(); + for r in rows { + out.push(r?); + } + Ok(out) +} + +pub fn folders_of_type( + conn: &Connection, + source_id: i64, + type_name: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT item_id, folder_id, change_key, local_id FROM sync_id_exchange_ews + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, type_name], |row| { + Ok(FolderRow { + item_id: row.get(0)?, + folder_id: row.get(1)?, + change_key: row.get(2)?, + local_id: row.get(3)?, + }) + })?; + let mut map = HashMap::new(); + for r in rows { + let f = r?; + map.insert(f.item_id.clone(), f); + } + Ok(map) +} + +pub fn get_sync_state( + conn: &Connection, + source_id: i64, + type_name: &str, + item_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT sync_state FROM sync_id_exchange_ews + WHERE source_id = ?1 AND type_name = ?2 AND item_id = ?3", + params![source_id, type_name, item_id], + |row| row.get::<_, String>(0), + ) + .optional() +} + +pub fn set_sync_state( + conn: &Connection, + source_id: i64, + type_name: &str, + item_id: &str, + sync_state: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "UPDATE sync_id_exchange_ews + SET sync_state = ?4 + WHERE source_id = ?1 AND type_name = ?2 AND item_id = ?3", + params![source_id, type_name, item_id, sync_state], + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (rusqlite::Connection, i64) { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let key = SourceKey { + kind: "exchange_ews".to_owned(), + session_url: "https://x/EWS/Exchange.asmx".to_owned(), + account_id: "u@d".to_owned(), + }; + let sid = upsert_source(&conn, &key, Some("u"), "u@d").unwrap(); + (conn, sid) + } + + #[test] + fn insert_and_lookup_round_trip() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "FOLDER1", "ITEM1", "CK1", 42).unwrap(); + let id = local_for_item(&conn, sid, EMAIL, "ITEM1").unwrap().unwrap(); + assert_eq!(id, 42); + let rows = items_in_folder(&conn, sid, EMAIL, "FOLDER1").unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].change_key, "CK1"); + } + + #[test] + fn update_change_key_preserves_local_id() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "F1", "I1", "A", 7).unwrap(); + update_change_key(&conn, sid, EMAIL, "I1", "B").unwrap(); + let rows = items_in_folder(&conn, sid, EMAIL, "F1").unwrap(); + assert_eq!(rows[0].change_key, "B"); + assert_eq!(rows[0].local_id, 7); + } + + #[test] + fn folders_of_type_lists_by_item_id() { + let (conn, sid) = setup(); + insert(&conn, sid, MAILBOX, "", "F1", "CK1", 1).unwrap(); + insert(&conn, sid, MAILBOX, "F1", "F2", "CK2", 2).unwrap(); + let map = folders_of_type(&conn, sid, MAILBOX).unwrap(); + assert_eq!(map.len(), 2); + assert_eq!(map.get("F1").map(|r| r.local_id), Some(1)); + assert_eq!(map.get("F2").map(|r| r.local_id), Some(2)); + } + + #[test] + fn sync_state_round_trips() { + let (conn, sid) = setup(); + insert(&conn, sid, MAILBOX, "", "F1", "", 1).unwrap(); + assert_eq!( + get_sync_state(&conn, sid, MAILBOX, "F1") + .unwrap() + .as_deref(), + Some("") + ); + set_sync_state(&conn, sid, MAILBOX, "F1", "opaque-state").unwrap(); + assert_eq!( + get_sync_state(&conn, sid, MAILBOX, "F1") + .unwrap() + .as_deref(), + Some("opaque-state") + ); + } + + #[test] + fn delete_item_removes_only_one() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "F1", "I1", "A", 1).unwrap(); + insert(&conn, sid, EMAIL, "F1", "I2", "A", 2).unwrap(); + delete_item(&conn, sid, EMAIL, "I1").unwrap(); + let rows = items_in_folder(&conn, sid, EMAIL, "F1").unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].item_id, "I2"); + } + + #[test] + fn unique_local_id_per_type_is_enforced() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "F1", "I1", "A", 1).unwrap(); + let err = insert(&conn, sid, EMAIL, "F1", "I2", "A", 1).unwrap_err(); + assert!(err.to_string().contains("UNIQUE")); + } +} diff --git a/src/db/exchange_graph_ids.rs b/src/db/exchange_graph_ids.rs new file mode 100644 index 0000000..815bc1e --- /dev/null +++ b/src/db/exchange_graph_ids.rs @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +pub const MAILBOX: &str = "mailbox"; +pub const EMAIL: &str = "email"; +pub const CALENDAR: &str = "calendar"; +pub const CALENDAR_EVENT: &str = "calendarevent"; +pub const ADDRESS_BOOK: &str = "addressbook"; +pub const CONTACT_CARD: &str = "contactcard"; + +#[derive(Debug, Clone)] +pub struct IdRow { + pub graph_id: String, + pub local_id: i64, +} + +pub fn insert( + conn: &Connection, + source_id: i64, + type_name: &str, + graph_id: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_exchange_graph (source_id, type_name, graph_id, local_id) + VALUES (?1, ?2, ?3, ?4)", + params![source_id, type_name, graph_id, local_id], + )?; + Ok(()) +} + +pub fn delete( + conn: &Connection, + source_id: i64, + type_name: &str, + graph_id: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_exchange_graph + WHERE source_id = ?1 AND type_name = ?2 AND graph_id = ?3", + params![source_id, type_name, graph_id], + )?; + Ok(()) +} + +pub fn local_for_graph_id( + conn: &Connection, + source_id: i64, + type_name: &str, + graph_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_exchange_graph + WHERE source_id = ?1 AND type_name = ?2 AND graph_id = ?3", + params![source_id, type_name, graph_id], + |row| row.get(0), + ) + .optional() +} + +pub fn ids_of_type( + conn: &Connection, + source_id: i64, + type_name: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT graph_id, local_id FROM sync_id_exchange_graph + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, type_name], |row| { + Ok(IdRow { + graph_id: row.get(0)?, + local_id: row.get(1)?, + }) + })?; + let mut map = HashMap::new(); + for r in rows { + let row = r?; + map.insert(row.graph_id, row.local_id); + } + Ok(map) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let conn = Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let key = SourceKey { + kind: "exchange_graph".to_owned(), + session_url: "https://x|https://graph.microsoft.com/v1.0".to_owned(), + account_id: "u-uuid".to_owned(), + }; + let sid = upsert_source(&conn, &key, Some("u@d"), "u@d").unwrap(); + (conn, sid) + } + + #[test] + fn insert_and_lookup_round_trip() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "AAkA-base64==", 17).unwrap(); + let id = local_for_graph_id(&conn, sid, EMAIL, "AAkA-base64==") + .unwrap() + .unwrap(); + assert_eq!(id, 17); + } + + #[test] + fn graph_id_is_case_sensitive() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "AaBb", 1).unwrap(); + assert!( + local_for_graph_id(&conn, sid, EMAIL, "aabb") + .unwrap() + .is_none() + ); + assert!( + local_for_graph_id(&conn, sid, EMAIL, "AaBb") + .unwrap() + .is_some() + ); + } + + #[test] + fn unique_local_id_per_type_is_enforced() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "ID-A", 1).unwrap(); + let err = insert(&conn, sid, EMAIL, "ID-B", 1).unwrap_err(); + assert!(err.to_string().contains("UNIQUE")); + } + + #[test] + fn ids_of_type_filters_by_type() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "M1", 1).unwrap(); + insert(&conn, sid, EMAIL, "M2", 2).unwrap(); + insert(&conn, sid, MAILBOX, "F1", 3).unwrap(); + let emails = ids_of_type(&conn, sid, EMAIL).unwrap(); + let folders = ids_of_type(&conn, sid, MAILBOX).unwrap(); + assert_eq!(emails.len(), 2); + assert_eq!(folders.len(), 1); + assert_eq!(emails.get("M1"), Some(&1)); + assert_eq!(folders.get("F1"), Some(&3)); + } + + #[test] + fn delete_removes_only_one() { + let (conn, sid) = setup(); + insert(&conn, sid, EMAIL, "M1", 1).unwrap(); + insert(&conn, sid, EMAIL, "M2", 2).unwrap(); + delete(&conn, sid, EMAIL, "M1").unwrap(); + let remaining = ids_of_type(&conn, sid, EMAIL).unwrap(); + assert_eq!(remaining.len(), 1); + assert!(remaining.contains_key("M2")); + } +} diff --git a/src/db/ids.rs b/src/db/ids.rs new file mode 100644 index 0000000..aa7b34d --- /dev/null +++ b/src/db/ids.rs @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +use crate::types::ObjectType; + +pub fn insert( + conn: &Connection, + source_id: i64, + ty: ObjectType, + jmap_id: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT OR REPLACE INTO sync_id_jmap (source_id, type_name, jmap_id, local_id) + VALUES (?1, ?2, ?3, ?4)", + params![source_id, ty.jmap_name(), jmap_id, local_id], + )?; + Ok(()) +} + +pub fn local_for( + conn: &Connection, + source_id: i64, + ty: ObjectType, + jmap_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_jmap + WHERE source_id = ?1 AND type_name = ?2 AND jmap_id = ?3", + params![source_id, ty.jmap_name(), jmap_id], + |row| row.get(0), + ) + .optional() +} + +pub fn jmap_to_local( + conn: &Connection, + source_id: i64, + ty: ObjectType, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT jmap_id, local_id FROM sync_id_jmap WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, ty.jmap_name()], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (j, l) = r?; + map.insert(j, l); + } + Ok(map) +} + +pub fn delete( + conn: &Connection, + source_id: i64, + ty: ObjectType, + jmap_id: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_jmap + WHERE source_id = ?1 AND type_name = ?2 AND jmap_id = ?3", + params![source_id, ty.jmap_name(), jmap_id], + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "jmap".to_owned(), + session_url: "u".to_owned(), + account_id: "w".to_owned(), + }, + None, + "alice", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn crud_roundtrip() { + let (c, sid) = setup(); + insert(&c, sid, ObjectType::Mailbox, "M1", 1).unwrap(); + insert(&c, sid, ObjectType::Mailbox, "M2", 2).unwrap(); + assert_eq!( + local_for(&c, sid, ObjectType::Mailbox, "M1").unwrap(), + Some(1) + ); + let map = jmap_to_local(&c, sid, ObjectType::Mailbox).unwrap(); + assert_eq!(map.len(), 2); + assert_eq!(map.get("M2"), Some(&2)); + delete(&c, sid, ObjectType::Mailbox, "M1").unwrap(); + assert_eq!(local_for(&c, sid, ObjectType::Mailbox, "M1").unwrap(), None); + } +} diff --git a/src/db/imap_ids.rs b/src/db/imap_ids.rs new file mode 100644 index 0000000..cb784f5 --- /dev/null +++ b/src/db/imap_ids.rs @@ -0,0 +1,292 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use rusqlite::{Connection, OptionalExtension, params}; + +const MAILBOX: &str = "mailbox"; +const EMAIL: &str = "email"; + +pub fn insert_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT OR REPLACE INTO sync_id_imap + (source_id, type_name, folder, uidvalidity, uid, local_id) + VALUES (?1, ?2, ?3, 0, 0, ?4)", + params![source_id, MAILBOX, folder, local_id], + )?; + Ok(()) +} + +pub fn insert_email( + conn: &Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, + uid: u32, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT OR REPLACE INTO sync_id_imap + (source_id, type_name, folder, uidvalidity, uid, local_id) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![source_id, EMAIL, folder, uidvalidity, uid, local_id], + )?; + Ok(()) +} + +pub fn local_for_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 + AND uidvalidity = 0 AND uid = 0", + params![source_id, MAILBOX, folder], + |row| row.get(0), + ) + .optional() +} + +pub fn local_for_email( + conn: &Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, + uid: u32, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 + AND folder = ?3 AND uidvalidity = ?4 AND uid = ?5", + params![source_id, EMAIL, folder, uidvalidity, uid], + |row| row.get(0), + ) + .optional() +} + +pub fn mailbox_folders( + conn: &Connection, + source_id: i64, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT folder, local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, MAILBOX], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (f, l) = r?; + map.insert(f, l); + } + Ok(map) +} + +pub fn email_uids_in_folder( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT uidvalidity, uid, local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + )?; + let rows = stmt.query_map(params![source_id, EMAIL, folder], |row| { + Ok(( + row.get::<_, u32>(0)?, + row.get::<_, u32>(1)?, + row.get::<_, i64>(2)?, + )) + })?; + let mut map = HashMap::new(); + for r in rows { + let (v, u, l) = r?; + map.insert((v, u), l); + } + Ok(map) +} + +pub fn delete_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 + AND uidvalidity = 0 AND uid = 0", + params![source_id, MAILBOX, folder], + )?; + Ok(()) +} + +pub fn delete_email( + conn: &Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, + uid: u32, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 + AND folder = ?3 AND uidvalidity = ?4 AND uid = ?5", + params![source_id, EMAIL, folder, uidvalidity, uid], + )?; + Ok(()) +} + +pub fn delete_all_emails_in_folder( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result { + conn.execute( + "DELETE FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + params![source_id, EMAIL, folder], + ) +} + +pub fn email_uids_at_validity( + conn: &Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT uid FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 + AND uidvalidity = ?4 + ORDER BY uid", + )?; + let rows = stmt.query_map(params![source_id, EMAIL, folder, uidvalidity], |row| { + row.get::<_, u32>(0) + })?; + let mut out = Vec::new(); + for r in rows { + out.push(r?); + } + Ok(out) +} + +pub fn folders_with_emails( + conn: &Connection, + source_id: i64, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT DISTINCT folder FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, EMAIL], |row| row.get::<_, String>(0))?; + let mut out = HashSet::new(); + for r in rows { + out.insert(r?); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "imap".to_owned(), + session_url: "imaps://host:993".to_owned(), + account_id: "alice".to_owned(), + }, + None, + "alice", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn mailbox_roundtrip() { + let (c, sid) = setup(); + insert_mailbox(&c, sid, "INBOX", 7).unwrap(); + insert_mailbox(&c, sid, "INBOX/Sub", 8).unwrap(); + assert_eq!(local_for_mailbox(&c, sid, "INBOX").unwrap(), Some(7)); + let all = mailbox_folders(&c, sid).unwrap(); + assert_eq!(all.len(), 2); + assert_eq!(all.get("INBOX/Sub"), Some(&8)); + delete_mailbox(&c, sid, "INBOX").unwrap(); + assert_eq!(local_for_mailbox(&c, sid, "INBOX").unwrap(), None); + } + + #[test] + fn email_roundtrip() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", 12345, 100, 1).unwrap(); + insert_email(&c, sid, "INBOX", 12345, 101, 2).unwrap(); + insert_email(&c, sid, "Sent", 7777, 1, 3).unwrap(); + assert_eq!( + local_for_email(&c, sid, "INBOX", 12345, 100).unwrap(), + Some(1) + ); + let inbox = email_uids_in_folder(&c, sid, "INBOX").unwrap(); + assert_eq!(inbox.len(), 2); + assert_eq!(inbox.get(&(12345, 100)), Some(&1)); + let folders = folders_with_emails(&c, sid).unwrap(); + assert!(folders.contains("INBOX")); + assert!(folders.contains("Sent")); + } + + #[test] + fn delete_all_emails_in_folder_wipes_only_that_folder() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", 1, 10, 1).unwrap(); + insert_email(&c, sid, "INBOX", 1, 11, 2).unwrap(); + insert_email(&c, sid, "Sent", 1, 10, 3).unwrap(); + let removed = delete_all_emails_in_folder(&c, sid, "INBOX").unwrap(); + assert_eq!(removed, 2); + assert!(email_uids_in_folder(&c, sid, "INBOX").unwrap().is_empty()); + assert_eq!(email_uids_in_folder(&c, sid, "Sent").unwrap().len(), 1); + } + + #[test] + fn re_insert_with_same_local_id_replaces() { + let (c, sid) = setup(); + insert_mailbox(&c, sid, "A", 1).unwrap(); + insert_mailbox(&c, sid, "B", 1).unwrap(); + assert_eq!(local_for_mailbox(&c, sid, "A").unwrap(), None); + assert_eq!(local_for_mailbox(&c, sid, "B").unwrap(), Some(1)); + let all = mailbox_folders(&c, sid).unwrap(); + assert_eq!(all.len(), 1); + } + + #[test] + fn pk_composite_is_per_type_per_folder_per_uid() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", 1, 10, 1).unwrap(); + insert_email(&c, sid, "INBOX", 1, 10, 1).unwrap(); + assert_eq!(email_uids_in_folder(&c, sid, "INBOX").unwrap().len(), 1); + } + + #[test] + fn mailbox_and_email_namespaces_do_not_collide_on_local_id() { + let (c, sid) = setup(); + insert_mailbox(&c, sid, "INBOX", 42).unwrap(); + insert_email(&c, sid, "INBOX", 1, 1, 42).unwrap(); + } +} diff --git a/src/db/imap_state.rs b/src/db/imap_state.rs new file mode 100644 index 0000000..81a8481 --- /dev/null +++ b/src/db/imap_state.rs @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, OptionalExtension, params}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FolderState { + pub uidvalidity: u32, + pub uidnext: u32, + pub last_seen: String, +} + +pub fn get( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT uidvalidity, uidnext, last_seen FROM imap_folder_state + WHERE source_id = ?1 AND folder = ?2", + params![source_id, folder], + |row| { + Ok(FolderState { + uidvalidity: row.get(0)?, + uidnext: row.get(1)?, + last_seen: row.get(2)?, + }) + }, + ) + .optional() +} + +pub fn upsert( + conn: &Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, + uidnext: u32, + last_seen_rfc3339: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO imap_folder_state (source_id, folder, uidvalidity, uidnext, last_seen) + VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT (source_id, folder) + DO UPDATE SET uidvalidity = excluded.uidvalidity, + uidnext = excluded.uidnext, + last_seen = excluded.last_seen", + params![source_id, folder, uidvalidity, uidnext, last_seen_rfc3339], + )?; + Ok(()) +} + +pub fn delete(conn: &Connection, source_id: i64, folder: &str) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM imap_folder_state WHERE source_id = ?1 AND folder = ?2", + params![source_id, folder], + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "imap".to_owned(), + session_url: "imaps://host:993".to_owned(), + account_id: "alice".to_owned(), + }, + None, + "alice", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn upsert_then_get_returns_latest() { + let (c, sid) = setup(); + assert!(get(&c, sid, "INBOX").unwrap().is_none()); + upsert(&c, sid, "INBOX", 100, 50, "2026-05-24T00:00:00Z").unwrap(); + let st = get(&c, sid, "INBOX").unwrap().unwrap(); + assert_eq!(st.uidvalidity, 100); + assert_eq!(st.uidnext, 50); + upsert(&c, sid, "INBOX", 100, 75, "2026-05-24T01:00:00Z").unwrap(); + let st = get(&c, sid, "INBOX").unwrap().unwrap(); + assert_eq!(st.uidnext, 75); + assert_eq!(st.last_seen, "2026-05-24T01:00:00Z"); + } + + #[test] + fn delete_removes_only_named_folder() { + let (c, sid) = setup(); + upsert(&c, sid, "INBOX", 1, 1, "2026-05-24T00:00:00Z").unwrap(); + upsert(&c, sid, "Sent", 2, 1, "2026-05-24T00:00:00Z").unwrap(); + delete(&c, sid, "INBOX").unwrap(); + assert!(get(&c, sid, "INBOX").unwrap().is_none()); + assert!(get(&c, sid, "Sent").unwrap().is_some()); + } +} diff --git a/src/db/init.rs b/src/db/init.rs new file mode 100644 index 0000000..bc765f4 --- /dev/null +++ b/src/db/init.rs @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::Connection; + +pub const SCHEMA_SQL: &str = include_str!("schema.sql"); + +pub fn open(path: &std::path::Path) -> Result { + let conn = Connection::open(path)?; + apply_pragmas(&conn)?; + apply_schema(&conn)?; + Ok(conn) +} + +pub fn apply_schema(conn: &Connection) -> Result<(), OpenError> { + let tx = conn.unchecked_transaction()?; + tx.execute_batch(SCHEMA_SQL)?; + ensure_calendar_events_data_type(&tx)?; + tx.commit()?; + Ok(()) +} + +fn ensure_calendar_events_data_type(conn: &Connection) -> Result<(), OpenError> { + let mut stmt = conn.prepare("PRAGMA table_info(calendar_events)")?; + let rows = stmt.query_map([], |row| row.get::<_, String>(1))?; + let has_column = rows.filter_map(|r| r.ok()).any(|name| name == "data_type"); + if !has_column { + conn.execute( + "ALTER TABLE calendar_events ADD COLUMN data_type TEXT NOT NULL DEFAULT 'Event'", + [], + )?; + } + Ok(()) +} + +fn apply_pragmas(conn: &Connection) -> Result<(), OpenError> { + conn.pragma_update(None, "journal_mode", "WAL")?; + conn.pragma_update(None, "foreign_keys", "ON")?; + conn.pragma_update(None, "locking_mode", "EXCLUSIVE")?; + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum OpenError { + #[error("sqlite error: {0}")] + Sqlite(#[from] rusqlite::Error), +} diff --git a/src/db/maildir_ids.rs b/src/db/maildir_ids.rs new file mode 100644 index 0000000..a3fd905 --- /dev/null +++ b/src/db/maildir_ids.rs @@ -0,0 +1,269 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use rusqlite::{Connection, OptionalExtension, params}; + +const MAILBOX: &str = "mailbox"; +const EMAIL: &str = "email"; + +pub fn insert_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_maildir + (source_id, type_name, folder, unique_id, local_id) + VALUES (?1, ?2, ?3, '', ?4)", + params![source_id, MAILBOX, folder, local_id], + )?; + Ok(()) +} + +pub fn insert_email( + conn: &Connection, + source_id: i64, + folder: &str, + unique_id: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_maildir + (source_id, type_name, folder, unique_id, local_id) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![source_id, EMAIL, folder, unique_id, local_id], + )?; + Ok(()) +} + +pub fn local_for_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 AND unique_id = ''", + params![source_id, MAILBOX, folder], + |row| row.get(0), + ) + .optional() +} + +pub fn local_for_email( + conn: &Connection, + source_id: i64, + folder: &str, + unique_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 AND unique_id = ?4", + params![source_id, EMAIL, folder, unique_id], + |row| row.get(0), + ) + .optional() +} + +pub fn mailbox_folders( + conn: &Connection, + source_id: i64, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT folder, local_id FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND unique_id = ''", + )?; + let rows = stmt.query_map(params![source_id, MAILBOX], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (f, l) = r?; + map.insert(f, l); + } + Ok(map) +} + +pub fn email_ids_in_folder( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT unique_id, local_id FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + )?; + let rows = stmt.query_map(params![source_id, EMAIL, folder], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (u, l) = r?; + map.insert(u, l); + } + Ok(map) +} + +pub fn delete_mailbox( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 AND unique_id = ''", + params![source_id, MAILBOX, folder], + )?; + Ok(()) +} + +pub fn delete_email( + conn: &Connection, + source_id: i64, + folder: &str, + unique_id: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3 AND unique_id = ?4", + params![source_id, EMAIL, folder, unique_id], + )?; + Ok(()) +} + +pub fn delete_all_emails_in_folder( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result { + conn.execute( + "DELETE FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + params![source_id, EMAIL, folder], + ) +} + +pub fn folders_with_emails( + conn: &Connection, + source_id: i64, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT DISTINCT folder FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, EMAIL], |row| row.get::<_, String>(0))?; + let mut out = HashSet::new(); + for r in rows { + out.insert(r?); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "maildir".to_owned(), + session_url: "file:///home/alice/Maildir".to_owned(), + account_id: "/home/alice/Maildir".to_owned(), + }, + Some("Maildir"), + "", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn mailbox_roundtrip() { + let (c, sid) = setup(); + insert_mailbox(&c, sid, "INBOX", 7).unwrap(); + insert_mailbox(&c, sid, "Archive.2025", 8).unwrap(); + assert_eq!(local_for_mailbox(&c, sid, "INBOX").unwrap(), Some(7)); + let all = mailbox_folders(&c, sid).unwrap(); + assert_eq!(all.len(), 2); + assert_eq!(all.get("Archive.2025"), Some(&8)); + delete_mailbox(&c, sid, "INBOX").unwrap(); + assert_eq!(local_for_mailbox(&c, sid, "INBOX").unwrap(), None); + } + + #[test] + fn email_roundtrip() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", "1739471123.M001.host", 1).unwrap(); + insert_email(&c, sid, "INBOX", "1739471200.M002.host", 2).unwrap(); + insert_email(&c, sid, "Sent", "1739471300.M003.host", 3).unwrap(); + assert_eq!( + local_for_email(&c, sid, "INBOX", "1739471123.M001.host").unwrap(), + Some(1) + ); + let inbox = email_ids_in_folder(&c, sid, "INBOX").unwrap(); + assert_eq!(inbox.len(), 2); + assert_eq!(inbox.get("1739471123.M001.host"), Some(&1)); + let folders = folders_with_emails(&c, sid).unwrap(); + assert!(folders.contains("INBOX")); + assert!(folders.contains("Sent")); + } + + #[test] + fn delete_email_removes_only_that_row() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", "u1", 1).unwrap(); + insert_email(&c, sid, "INBOX", "u2", 2).unwrap(); + delete_email(&c, sid, "INBOX", "u1").unwrap(); + let inbox = email_ids_in_folder(&c, sid, "INBOX").unwrap(); + assert_eq!(inbox.len(), 1); + assert!(inbox.contains_key("u2")); + } + + #[test] + fn delete_all_emails_in_folder_wipes_only_that_folder() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", "u1", 1).unwrap(); + insert_email(&c, sid, "INBOX", "u2", 2).unwrap(); + insert_email(&c, sid, "Sent", "u3", 3).unwrap(); + let removed = delete_all_emails_in_folder(&c, sid, "INBOX").unwrap(); + assert_eq!(removed, 2); + assert!(email_ids_in_folder(&c, sid, "INBOX").unwrap().is_empty()); + assert_eq!(email_ids_in_folder(&c, sid, "Sent").unwrap().len(), 1); + } + + #[test] + fn mailbox_and_email_namespaces_do_not_collide_on_local_id() { + let (c, sid) = setup(); + insert_mailbox(&c, sid, "INBOX", 42).unwrap(); + insert_email(&c, sid, "INBOX", "u1", 42).unwrap(); + } + + #[test] + fn re_insert_with_same_unique_id_is_rejected_by_pk() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", "u1", 1).unwrap(); + let err = insert_email(&c, sid, "INBOX", "u1", 2).expect_err("PK conflict"); + assert!(matches!(err, rusqlite::Error::SqliteFailure(_, _))); + assert_eq!(local_for_email(&c, sid, "INBOX", "u1").unwrap(), Some(1)); + assert_eq!(email_ids_in_folder(&c, sid, "INBOX").unwrap().len(), 1); + } + + #[test] + fn re_insert_with_same_local_id_is_rejected_by_unique_constraint() { + let (c, sid) = setup(); + insert_email(&c, sid, "INBOX", "u1", 42).unwrap(); + let err = insert_email(&c, sid, "INBOX", "u2", 42).expect_err("UNIQUE conflict"); + assert!(matches!(err, rusqlite::Error::SqliteFailure(_, _))); + } +} diff --git a/src/db/managesieve_ids.rs b/src/db/managesieve_ids.rs new file mode 100644 index 0000000..d55d100 --- /dev/null +++ b/src/db/managesieve_ids.rs @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +pub fn insert( + conn: &Connection, + source_id: i64, + name: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT OR REPLACE INTO sync_id_managesieve (source_id, name, local_id) + VALUES (?1, ?2, ?3)", + params![source_id, name, local_id], + )?; + Ok(()) +} + +pub fn local_for( + conn: &Connection, + source_id: i64, + name: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_managesieve WHERE source_id = ?1 AND name = ?2", + params![source_id, name], + |row| row.get(0), + ) + .optional() +} + +pub fn all_names( + conn: &Connection, + source_id: i64, +) -> Result, rusqlite::Error> { + let mut stmt = + conn.prepare("SELECT name, local_id FROM sync_id_managesieve WHERE source_id = ?1")?; + let rows = stmt.query_map(params![source_id], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut out = HashMap::new(); + for r in rows { + let (n, l) = r?; + out.insert(n, l); + } + Ok(out) +} + +pub fn delete(conn: &Connection, source_id: i64, name: &str) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_managesieve WHERE source_id = ?1 AND name = ?2", + params![source_id, name], + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "managesieve".to_owned(), + session_url: "sieve://host:4190".to_owned(), + account_id: "alice".to_owned(), + }, + None, + "alice", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn insert_and_lookup_by_name() { + let (c, sid) = setup(); + insert(&c, sid, "vacation", 7).unwrap(); + assert_eq!(local_for(&c, sid, "vacation").unwrap(), Some(7)); + assert_eq!(local_for(&c, sid, "missing").unwrap(), None); + } + + #[test] + fn all_names_returns_full_map() { + let (c, sid) = setup(); + insert(&c, sid, "a", 1).unwrap(); + insert(&c, sid, "b", 2).unwrap(); + let map = all_names(&c, sid).unwrap(); + assert_eq!(map.len(), 2); + assert_eq!(map.get("a"), Some(&1)); + } + + #[test] + fn delete_removes_row() { + let (c, sid) = setup(); + insert(&c, sid, "a", 1).unwrap(); + delete(&c, sid, "a").unwrap(); + assert_eq!(local_for(&c, sid, "a").unwrap(), None); + } + + #[test] + fn re_insert_with_same_name_replaces_local_id() { + let (c, sid) = setup(); + insert(&c, sid, "a", 1).unwrap(); + insert(&c, sid, "a", 2).unwrap(); + assert_eq!(local_for(&c, sid, "a").unwrap(), Some(2)); + assert_eq!(all_names(&c, sid).unwrap().len(), 1); + } + + #[test] + fn insert_or_replace_reassigns_when_local_id_clashes() { + let (c, sid) = setup(); + insert(&c, sid, "a", 1).unwrap(); + insert(&c, sid, "b", 1).unwrap(); + assert!(local_for(&c, sid, "a").unwrap().is_none()); + assert_eq!(local_for(&c, sid, "b").unwrap(), Some(1)); + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..1d3fdc7 --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod blobs; +pub mod dav_ids; +pub mod exchange_ews_ids; +pub mod exchange_graph_ids; +pub mod ids; +pub mod imap_ids; +pub mod imap_state; +pub mod init; +pub mod maildir_ids; +pub mod managesieve_ids; +pub mod sources; +pub mod takeout_ids; diff --git a/src/db/schema.sql b/src/db/schema.sql new file mode 100644 index 0000000..3ca3a57 --- /dev/null +++ b/src/db/schema.sql @@ -0,0 +1,249 @@ +CREATE TABLE IF NOT EXISTS sources ( + id INTEGER PRIMARY KEY, + kind TEXT NOT NULL CHECK (kind IN ('jmap','imap','caldav','carddav','webdav','managesieve','maildir','exchange_ews','exchange_graph','takeout')), + session_url TEXT NOT NULL, + account_id TEXT NOT NULL, + account_name TEXT, + username TEXT NOT NULL, + UNIQUE (kind, session_url, account_id) +); + +CREATE TABLE IF NOT EXISTS blobs ( + id INTEGER PRIMARY KEY, + hash BLOB NOT NULL UNIQUE, + data BLOB NOT NULL +); + +CREATE INDEX IF NOT EXISTS blobs_hash_idx ON blobs (hash); + +CREATE TABLE IF NOT EXISTS sync_id_jmap ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL, + jmap_id TEXT NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, jmap_id), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_jmap_local_idx + ON sync_id_jmap (type_name, local_id); + +CREATE TABLE IF NOT EXISTS sync_id_imap ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ('mailbox','email')), + folder TEXT NOT NULL, + uidvalidity INTEGER NOT NULL, + uid INTEGER NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, folder, uidvalidity, uid), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_imap_folder_idx + ON sync_id_imap (source_id, type_name, folder); + +CREATE TABLE IF NOT EXISTS imap_folder_state ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + folder TEXT NOT NULL, + uidvalidity INTEGER NOT NULL, + uidnext INTEGER NOT NULL, + last_seen TEXT NOT NULL, + PRIMARY KEY (source_id, folder) +); + +CREATE TABLE IF NOT EXISTS sync_id_dav ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ( + 'calendar','calendarevent', + 'addressbook','contactcard', + 'filenode')), + collection_href TEXT NOT NULL, + item_href TEXT NOT NULL, + etag TEXT NOT NULL DEFAULT '', + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, item_href), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_dav_collection_idx + ON sync_id_dav (source_id, type_name, collection_href); + +CREATE TABLE IF NOT EXISTS sync_id_managesieve ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + name TEXT NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, name), + UNIQUE (source_id, local_id) +); + +CREATE TABLE IF NOT EXISTS sync_id_maildir ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ('mailbox','email')), + folder TEXT NOT NULL, + unique_id TEXT NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, folder, unique_id), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_maildir_folder_idx + ON sync_id_maildir (source_id, type_name, folder); + +CREATE TABLE IF NOT EXISTS sync_id_exchange_ews ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ( + 'mailbox','calendar','addressbook', + 'email','calendarevent','contactcard')), + folder_id TEXT NOT NULL DEFAULT '', + item_id TEXT NOT NULL, + change_key TEXT NOT NULL DEFAULT '', + sync_state TEXT NOT NULL DEFAULT '', + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, item_id), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_exchange_ews_folder_idx + ON sync_id_exchange_ews (source_id, type_name, folder_id); + +CREATE TABLE IF NOT EXISTS sync_id_exchange_graph ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ( + 'mailbox','email', + 'calendar','calendarevent', + 'addressbook','contactcard')), + graph_id TEXT NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, graph_id), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_exchange_graph_type_idx + ON sync_id_exchange_graph (source_id, type_name); + +CREATE TABLE IF NOT EXISTS mailboxes ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + parent_id INTEGER REFERENCES mailboxes(id) ON DELETE RESTRICT, + role TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + is_subscribed INTEGER NOT NULL DEFAULT 1, + UNIQUE (parent_id, name) +); + +CREATE TABLE IF NOT EXISTS emails ( + id INTEGER PRIMARY KEY, + blob_id INTEGER NOT NULL REFERENCES blobs(id), + received_at TEXT NOT NULL, + mailbox_ids TEXT NOT NULL CHECK (json_valid(mailbox_ids)), + keywords TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(keywords)), + message_match TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(message_match)) +); + +CREATE INDEX IF NOT EXISTS emails_blob_idx ON emails (blob_id); + +CREATE TABLE IF NOT EXISTS identities ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL DEFAULT '', + email TEXT NOT NULL, + reply_to TEXT CHECK (reply_to IS NULL OR json_valid(reply_to)), + bcc TEXT CHECK (bcc IS NULL OR json_valid(bcc)), + text_signature TEXT NOT NULL DEFAULT '', + html_signature TEXT NOT NULL DEFAULT '' +); + +CREATE TABLE IF NOT EXISTS sieve_scripts ( + id INTEGER PRIMARY KEY, + name TEXT, + is_active INTEGER NOT NULL DEFAULT 0, + blob_id INTEGER NOT NULL REFERENCES blobs(id), + UNIQUE (name) +); + +CREATE UNIQUE INDEX IF NOT EXISTS sieve_scripts_one_active_idx + ON sieve_scripts (is_active) WHERE is_active = 1; + +CREATE TABLE IF NOT EXISTS address_books ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + is_default INTEGER NOT NULL DEFAULT 0, + is_subscribed INTEGER NOT NULL DEFAULT 1 +); + +CREATE TABLE IF NOT EXISTS contact_cards ( + id INTEGER PRIMARY KEY, + uid TEXT NOT NULL UNIQUE, + address_book_ids TEXT NOT NULL CHECK (json_valid(address_book_ids)), + data TEXT NOT NULL CHECK (json_valid(data)) +); + +CREATE TABLE IF NOT EXISTS calendars ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + color TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + is_subscribed INTEGER NOT NULL DEFAULT 1, + is_visible INTEGER NOT NULL DEFAULT 1, + is_default INTEGER NOT NULL DEFAULT 0, + include_in_availability TEXT NOT NULL DEFAULT 'all' + CHECK (include_in_availability IN ('all','attending','none')), + default_alerts_with_time TEXT CHECK (default_alerts_with_time IS NULL + OR json_valid(default_alerts_with_time)), + default_alerts_without_time TEXT CHECK (default_alerts_without_time IS NULL + OR json_valid(default_alerts_without_time)), + time_zone TEXT +); + +CREATE TABLE IF NOT EXISTS calendar_events ( + id INTEGER PRIMARY KEY, + calendar_ids TEXT NOT NULL CHECK (json_valid(calendar_ids)), + is_draft INTEGER NOT NULL DEFAULT 0, + use_default_alerts INTEGER NOT NULL DEFAULT 0, + data TEXT NOT NULL CHECK (json_valid(data)), + data_type TEXT NOT NULL DEFAULT 'Event' + CHECK (data_type IN ('Event','Task','Note','Group')) +); + +CREATE TABLE IF NOT EXISTS participant_identities ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL DEFAULT '', + calendar_address TEXT NOT NULL, + is_default INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS file_nodes ( + id INTEGER PRIMARY KEY, + parent_id INTEGER REFERENCES file_nodes(id) ON DELETE RESTRICT, + node_type TEXT NOT NULL + CHECK (node_type IN ('file','directory','symlink')), + blob_id INTEGER REFERENCES blobs(id), + target TEXT CHECK (target IS NULL OR json_valid(target)), + name TEXT NOT NULL, + media_type TEXT, + created TEXT NOT NULL, + modified TEXT, + is_subscribed INTEGER NOT NULL DEFAULT 1, + role TEXT, + UNIQUE (parent_id, name) +); + +CREATE INDEX IF NOT EXISTS file_nodes_parent_idx ON file_nodes (parent_id); +CREATE INDEX IF NOT EXISTS file_nodes_blob_idx ON file_nodes (blob_id); + +CREATE TABLE IF NOT EXISTS sync_id_takeout ( + source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE, + type_name TEXT NOT NULL CHECK (type_name IN ( + 'mailbox','email', + 'addressbook','contactcard', + 'calendar','calendarevent')), + source_obj_id TEXT NOT NULL, + local_id INTEGER NOT NULL, + PRIMARY KEY (source_id, type_name, source_obj_id), + UNIQUE (source_id, type_name, local_id) +); + +CREATE INDEX IF NOT EXISTS sync_id_takeout_type_idx + ON sync_id_takeout (source_id, type_name); diff --git a/src/db/sources.rs b/src/db/sources.rs new file mode 100644 index 0000000..8a2600a --- /dev/null +++ b/src/db/sources.rs @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, OptionalExtension, params}; + +#[derive(Debug, Clone)] +pub struct SourceKey { + pub kind: String, + pub session_url: String, + pub account_id: String, +} + +pub fn find_source(conn: &Connection, key: &SourceKey) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT id FROM sources WHERE kind = ?1 AND session_url = ?2 AND account_id = ?3", + params![key.kind, key.session_url, key.account_id], + |row| row.get(0), + ) + .optional() +} + +pub fn conflicting_source( + conn: &Connection, + kind: &str, + session_url: &str, + account_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT session_url, account_id FROM sources + WHERE kind = ?1 AND NOT (session_url = ?2 AND account_id = ?3) LIMIT 1", + params![kind, session_url, account_id], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .optional() +} + +pub fn upsert_source( + conn: &Connection, + key: &SourceKey, + account_name: Option<&str>, + username: &str, +) -> Result { + conn.execute( + "INSERT INTO sources (kind, session_url, account_id, account_name, username) + VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT (kind, session_url, account_id) + DO UPDATE SET account_name = excluded.account_name, username = excluded.username", + params![ + key.kind, + key.session_url, + key.account_id, + account_name, + username + ], + )?; + find_source(conn, key).map(|opt| opt.unwrap_or_else(|| conn.last_insert_rowid())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + fn key(url: &str, acc: &str) -> SourceKey { + SourceKey { + kind: "jmap".to_owned(), + session_url: url.to_owned(), + account_id: acc.to_owned(), + } + } + + #[test] + fn upsert_is_idempotent_and_returns_stable_id() { + let c = mem(); + let k = key("https://a/jmap", "w"); + let id1 = upsert_source(&c, &k, Some("alice"), "alice").unwrap(); + let id2 = upsert_source(&c, &k, Some("alice2"), "alice").unwrap(); + assert_eq!(id1, id2); + assert!(find_source(&c, &k).unwrap().is_some()); + } + + #[test] + fn conflicting_source_detects_a_different_account() { + let c = mem(); + upsert_source(&c, &key("https://a/jmap", "w"), None, "alice").unwrap(); + assert!( + conflicting_source(&c, "jmap", "https://a/jmap", "w") + .unwrap() + .is_none() + ); + let conflict = conflicting_source(&c, "jmap", "https://b/jmap", "v") + .unwrap() + .unwrap(); + assert_eq!(conflict, ("https://a/jmap".to_owned(), "w".to_owned())); + } +} diff --git a/src/db/takeout_ids.rs b/src/db/takeout_ids.rs new file mode 100644 index 0000000..0fda03f --- /dev/null +++ b/src/db/takeout_ids.rs @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, OptionalExtension, params}; + +pub const MAILBOX: &str = "mailbox"; +pub const EMAIL: &str = "email"; +pub const ADDRESS_BOOK: &str = "addressbook"; +pub const CONTACT_CARD: &str = "contactcard"; +pub const CALENDAR: &str = "calendar"; +pub const CALENDAR_EVENT: &str = "calendarevent"; + +pub fn insert( + conn: &Connection, + source_id: i64, + type_name: &str, + source_obj_id: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + conn.execute( + "INSERT INTO sync_id_takeout + (source_id, type_name, source_obj_id, local_id) + VALUES (?1, ?2, ?3, ?4)", + params![source_id, type_name, source_obj_id, local_id], + )?; + Ok(()) +} + +pub fn local_for( + conn: &Connection, + source_id: i64, + type_name: &str, + source_obj_id: &str, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT local_id FROM sync_id_takeout + WHERE source_id = ?1 AND type_name = ?2 AND source_obj_id = ?3", + params![source_id, type_name, source_obj_id], + |row| row.get(0), + ) + .optional() +} + +pub fn delete( + conn: &Connection, + source_id: i64, + type_name: &str, + source_obj_id: &str, +) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM sync_id_takeout + WHERE source_id = ?1 AND type_name = ?2 AND source_obj_id = ?3", + params![source_id, type_name, source_obj_id], + )?; + Ok(()) +} + +pub fn all_for_type( + conn: &Connection, + source_id: i64, + type_name: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT source_obj_id, local_id FROM sync_id_takeout + WHERE source_id = ?1 AND type_name = ?2", + )?; + let rows = stmt.query_map(params![source_id, type_name], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + let mut map = HashMap::new(); + for r in rows { + let (k, v) = r?; + map.insert(k, v); + } + Ok(map) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + + fn setup() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "takeout".to_owned(), + session_url: "file:///tmp/Takeout".to_owned(), + account_id: "/tmp/Takeout".to_owned(), + }, + Some("Takeout"), + "", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn insert_and_lookup_roundtrip() { + let (c, sid) = setup(); + insert(&c, sid, MAILBOX, "Inbox", 7).unwrap(); + insert(&c, sid, MAILBOX, "Label/Sub", 8).unwrap(); + assert_eq!(local_for(&c, sid, MAILBOX, "Inbox").unwrap(), Some(7)); + assert_eq!(local_for(&c, sid, MAILBOX, "Label/Sub").unwrap(), Some(8)); + assert_eq!(local_for(&c, sid, MAILBOX, "Missing").unwrap(), None); + } + + #[test] + fn types_are_isolated_namespaces() { + let (c, sid) = setup(); + insert(&c, sid, MAILBOX, "Inbox", 1).unwrap(); + insert(&c, sid, EMAIL, "ab12", 2).unwrap(); + insert(&c, sid, ADDRESS_BOOK, "Imported", 3).unwrap(); + insert(&c, sid, CONTACT_CARD, "uid-1", 4).unwrap(); + insert(&c, sid, CALENDAR, "Imported", 5).unwrap(); + insert(&c, sid, CALENDAR_EVENT, "uid-e1", 6).unwrap(); + assert_eq!(all_for_type(&c, sid, MAILBOX).unwrap().len(), 1); + assert_eq!(all_for_type(&c, sid, EMAIL).unwrap().len(), 1); + assert_eq!(all_for_type(&c, sid, ADDRESS_BOOK).unwrap().len(), 1); + assert_eq!(all_for_type(&c, sid, CONTACT_CARD).unwrap().len(), 1); + assert_eq!(all_for_type(&c, sid, CALENDAR).unwrap().len(), 1); + assert_eq!(all_for_type(&c, sid, CALENDAR_EVENT).unwrap().len(), 1); + } + + #[test] + fn delete_removes_only_target_row() { + let (c, sid) = setup(); + insert(&c, sid, EMAIL, "hash-a", 1).unwrap(); + insert(&c, sid, EMAIL, "hash-b", 2).unwrap(); + delete(&c, sid, EMAIL, "hash-a").unwrap(); + assert_eq!(local_for(&c, sid, EMAIL, "hash-a").unwrap(), None); + assert_eq!(local_for(&c, sid, EMAIL, "hash-b").unwrap(), Some(2)); + } + + #[test] + fn pk_rejects_duplicate_source_obj_id() { + let (c, sid) = setup(); + insert(&c, sid, EMAIL, "h", 1).unwrap(); + let err = insert(&c, sid, EMAIL, "h", 2).expect_err("PK conflict"); + assert!(matches!(err, rusqlite::Error::SqliteFailure(_, _))); + } + + #[test] + fn unique_constraint_rejects_duplicate_local_id_within_type() { + let (c, sid) = setup(); + insert(&c, sid, EMAIL, "h1", 42).unwrap(); + let err = insert(&c, sid, EMAIL, "h2", 42).expect_err("UNIQUE conflict"); + assert!(matches!(err, rusqlite::Error::SqliteFailure(_, _))); + } + + #[test] + fn same_local_id_allowed_across_different_types() { + let (c, sid) = setup(); + insert(&c, sid, MAILBOX, "Inbox", 42).unwrap(); + insert(&c, sid, EMAIL, "h1", 42).unwrap(); + } + + #[test] + fn all_for_type_returns_full_map() { + let (c, sid) = setup(); + insert(&c, sid, EMAIL, "h1", 1).unwrap(); + insert(&c, sid, EMAIL, "h2", 2).unwrap(); + insert(&c, sid, EMAIL, "h3", 3).unwrap(); + let all = all_for_type(&c, sid, EMAIL).unwrap(); + assert_eq!(all.len(), 3); + assert_eq!(all.get("h1"), Some(&1)); + assert_eq!(all.get("h3"), Some(&3)); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f87af6f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::db::init::OpenError; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("usage error: {0}")] + Usage(String), + + #[error("connection error: {0}")] + Connection(String), + + #[error("account resolution failed: {0}")] + Account(String), + + #[error("source-change protection: {0}")] + SourceChange(String), + + #[error("partial failure: {0}")] + Partial(String), + + #[error("aborted at prune confirmation")] + PruneAborted, + + #[error("not yet implemented: {0}")] + Unimplemented(&'static str), + + #[error("archive error: {0}")] + Db(#[from] OpenError), + + #[error("i/o error: {0}")] + Io(#[from] std::io::Error), +} + +impl From for Error { + fn from(e: rusqlite::Error) -> Self { + Error::Db(OpenError::Sqlite(e)) + } +} + +impl Error { + pub fn exit_code(&self) -> i32 { + match self { + Error::Usage(_) => 1, + Error::Connection(_) => 2, + Error::Account(_) => 3, + Error::SourceChange(_) => 4, + Error::Partial(_) => 5, + Error::PruneAborted => 6, + Error::Unimplemented(_) => 1, + Error::Db(_) => 7, + Error::Io(_) => 7, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn exit_codes_match_cli_md_table() { + assert_eq!(Error::Usage("x".into()).exit_code(), 1); + assert_eq!(Error::Unimplemented("x").exit_code(), 1); + assert_eq!(Error::Connection("x".into()).exit_code(), 2); + assert_eq!(Error::Account("x".into()).exit_code(), 3); + assert_eq!(Error::SourceChange("x".into()).exit_code(), 4); + assert_eq!(Error::Partial("x".into()).exit_code(), 5); + assert_eq!(Error::PruneAborted.exit_code(), 6); + let io = Error::Io(std::io::Error::other("disk")); + assert_eq!(io.exit_code(), 7); + } +} diff --git a/src/exchange/mod.rs b/src/exchange/mod.rs new file mode 100644 index 0000000..0a3f097 --- /dev/null +++ b/src/exchange/mod.rs @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod tz; diff --git a/src/exchange/tz.rs b/src/exchange/tz.rs new file mode 100644 index 0000000..35b14b6 --- /dev/null +++ b/src/exchange/tz.rs @@ -0,0 +1,258 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub fn windows_to_iana(windows_name: &str) -> Option<&'static str> { + let key = windows_name.trim(); + for (w, i) in TABLE { + if w.eq_ignore_ascii_case(key) { + return Some(i); + } + } + None +} + +const TABLE: &[(&str, &str)] = &[ + ("UTC", "Etc/UTC"), + ("Coordinated Universal Time", "Etc/UTC"), + ("UTC-11", "Etc/GMT+11"), + ("UTC-09", "Etc/GMT+9"), + ("UTC-08", "Etc/GMT+8"), + ("UTC-02", "Etc/GMT+2"), + ("UTC+12", "Etc/GMT-12"), + ("UTC+13", "Etc/GMT-13"), + ("Dateline Standard Time", "Etc/GMT+12"), + ("Aleutian Standard Time", "America/Adak"), + ("Hawaiian Standard Time", "Pacific/Honolulu"), + ("Marquesas Standard Time", "Pacific/Marquesas"), + ("Alaskan Standard Time", "America/Anchorage"), + ("Pacific Standard Time (Mexico)", "America/Tijuana"), + ("Pacific Standard Time", "America/Los_Angeles"), + ("US Mountain Standard Time", "America/Phoenix"), + ("Mountain Standard Time (Mexico)", "America/Mazatlan"), + ("Mountain Standard Time", "America/Denver"), + ("Yukon Standard Time", "America/Whitehorse"), + ("Central America Standard Time", "America/Guatemala"), + ("Central Standard Time", "America/Chicago"), + ("Easter Island Standard Time", "Pacific/Easter"), + ("Central Standard Time (Mexico)", "America/Mexico_City"), + ("Canada Central Standard Time", "America/Regina"), + ("SA Pacific Standard Time", "America/Bogota"), + ("Eastern Standard Time (Mexico)", "America/Cancun"), + ("Eastern Standard Time", "America/New_York"), + ("Haiti Standard Time", "America/Port-au-Prince"), + ("Cuba Standard Time", "America/Havana"), + ("US Eastern Standard Time", "America/Indianapolis"), + ("Turks And Caicos Standard Time", "America/Grand_Turk"), + ("Paraguay Standard Time", "America/Asuncion"), + ("Atlantic Standard Time", "America/Halifax"), + ("Venezuela Standard Time", "America/Caracas"), + ("Central Brazilian Standard Time", "America/Cuiaba"), + ("SA Western Standard Time", "America/La_Paz"), + ("Pacific SA Standard Time", "America/Santiago"), + ("Newfoundland Standard Time", "America/St_Johns"), + ("Tocantins Standard Time", "America/Araguaina"), + ("E. South America Standard Time", "America/Sao_Paulo"), + ("SA Eastern Standard Time", "America/Cayenne"), + ("Argentina Standard Time", "America/Argentina/Buenos_Aires"), + ("Greenland Standard Time", "America/Godthab"), + ("Montevideo Standard Time", "America/Montevideo"), + ("Magallanes Standard Time", "America/Punta_Arenas"), + ("Saint Pierre Standard Time", "America/Miquelon"), + ("Bahia Standard Time", "America/Bahia"), + ("Mid-Atlantic Standard Time", "Atlantic/South_Georgia"), + ("Azores Standard Time", "Atlantic/Azores"), + ("Cape Verde Standard Time", "Atlantic/Cape_Verde"), + ("GMT Standard Time", "Europe/London"), + ("Greenwich Standard Time", "Atlantic/Reykjavik"), + ("Sao Tome Standard Time", "Africa/Sao_Tome"), + ("Morocco Standard Time", "Africa/Casablanca"), + ("W. Europe Standard Time", "Europe/Berlin"), + ("Central Europe Standard Time", "Europe/Budapest"), + ("Romance Standard Time", "Europe/Paris"), + ("Central European Standard Time", "Europe/Warsaw"), + ("W. Central Africa Standard Time", "Africa/Lagos"), + ("Jordan Standard Time", "Asia/Amman"), + ("GTB Standard Time", "Europe/Bucharest"), + ("Middle East Standard Time", "Asia/Beirut"), + ("Egypt Standard Time", "Africa/Cairo"), + ("E. Europe Standard Time", "Europe/Chisinau"), + ("Syria Standard Time", "Asia/Damascus"), + ("West Bank Standard Time", "Asia/Hebron"), + ("South Africa Standard Time", "Africa/Johannesburg"), + ("FLE Standard Time", "Europe/Kiev"), + ("Israel Standard Time", "Asia/Jerusalem"), + ("South Sudan Standard Time", "Africa/Juba"), + ("Kaliningrad Standard Time", "Europe/Kaliningrad"), + ("Sudan Standard Time", "Africa/Khartoum"), + ("Libya Standard Time", "Africa/Tripoli"), + ("Namibia Standard Time", "Africa/Windhoek"), + ("Arabic Standard Time", "Asia/Baghdad"), + ("Turkey Standard Time", "Europe/Istanbul"), + ("Arab Standard Time", "Asia/Riyadh"), + ("Belarus Standard Time", "Europe/Minsk"), + ("Russian Standard Time", "Europe/Moscow"), + ("E. Africa Standard Time", "Africa/Nairobi"), + ("Iran Standard Time", "Asia/Tehran"), + ("Arabian Standard Time", "Asia/Dubai"), + ("Astrakhan Standard Time", "Europe/Astrakhan"), + ("Azerbaijan Standard Time", "Asia/Baku"), + ("Russia Time Zone 3", "Europe/Samara"), + ("Mauritius Standard Time", "Indian/Mauritius"), + ("Saratov Standard Time", "Europe/Saratov"), + ("Georgian Standard Time", "Asia/Tbilisi"), + ("Volgograd Standard Time", "Europe/Volgograd"), + ("Caucasus Standard Time", "Asia/Yerevan"), + ("Afghanistan Standard Time", "Asia/Kabul"), + ("West Asia Standard Time", "Asia/Tashkent"), + ("Ekaterinburg Standard Time", "Asia/Yekaterinburg"), + ("Pakistan Standard Time", "Asia/Karachi"), + ("Qyzylorda Standard Time", "Asia/Qyzylorda"), + ("India Standard Time", "Asia/Kolkata"), + ("Sri Lanka Standard Time", "Asia/Colombo"), + ("Nepal Standard Time", "Asia/Katmandu"), + ("Central Asia Standard Time", "Asia/Almaty"), + ("Bangladesh Standard Time", "Asia/Dhaka"), + ("Omsk Standard Time", "Asia/Omsk"), + ("Myanmar Standard Time", "Asia/Rangoon"), + ("SE Asia Standard Time", "Asia/Bangkok"), + ("Altai Standard Time", "Asia/Barnaul"), + ("W. Mongolia Standard Time", "Asia/Hovd"), + ("North Asia Standard Time", "Asia/Krasnoyarsk"), + ("N. Central Asia Standard Time", "Asia/Novosibirsk"), + ("Tomsk Standard Time", "Asia/Tomsk"), + ("China Standard Time", "Asia/Shanghai"), + ("North Asia East Standard Time", "Asia/Irkutsk"), + ("Singapore Standard Time", "Asia/Singapore"), + ("W. Australia Standard Time", "Australia/Perth"), + ("Taipei Standard Time", "Asia/Taipei"), + ("Ulaanbaatar Standard Time", "Asia/Ulaanbaatar"), + ("Aus Central W. Standard Time", "Australia/Eucla"), + ("Transbaikal Standard Time", "Asia/Chita"), + ("Tokyo Standard Time", "Asia/Tokyo"), + ("North Korea Standard Time", "Asia/Pyongyang"), + ("Korea Standard Time", "Asia/Seoul"), + ("Yakutsk Standard Time", "Asia/Yakutsk"), + ("Cen. Australia Standard Time", "Australia/Adelaide"), + ("AUS Central Standard Time", "Australia/Darwin"), + ("E. Australia Standard Time", "Australia/Brisbane"), + ("AUS Eastern Standard Time", "Australia/Sydney"), + ("West Pacific Standard Time", "Pacific/Port_Moresby"), + ("Tasmania Standard Time", "Australia/Hobart"), + ("Vladivostok Standard Time", "Asia/Vladivostok"), + ("Lord Howe Standard Time", "Australia/Lord_Howe"), + ("Bougainville Standard Time", "Pacific/Bougainville"), + ("Russia Time Zone 10", "Asia/Srednekolymsk"), + ("Magadan Standard Time", "Asia/Magadan"), + ("Norfolk Standard Time", "Pacific/Norfolk"), + ("Sakhalin Standard Time", "Asia/Sakhalin"), + ("Central Pacific Standard Time", "Pacific/Guadalcanal"), + ("Russia Time Zone 11", "Asia/Kamchatka"), + ("New Zealand Standard Time", "Pacific/Auckland"), + ("Fiji Standard Time", "Pacific/Fiji"), + ("Kamchatka Standard Time", "Asia/Kamchatka"), + ("Chatham Islands Standard Time", "Pacific/Chatham"), + ("Tonga Standard Time", "Pacific/Tongatapu"), + ("Samoa Standard Time", "Pacific/Apia"), + ("Line Islands Standard Time", "Pacific/Kiritimati"), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pacific_standard_time_maps_to_los_angeles() { + assert_eq!( + windows_to_iana("Pacific Standard Time"), + Some("America/Los_Angeles") + ); + } + + #[test] + fn romance_standard_time_maps_to_paris() { + assert_eq!( + windows_to_iana("Romance Standard Time"), + Some("Europe/Paris") + ); + } + + #[test] + fn unknown_zone_returns_none() { + assert_eq!(windows_to_iana("Made Up Zone"), None); + } + + #[test] + fn case_insensitive_lookup() { + assert_eq!( + windows_to_iana("eastern standard time"), + Some("America/New_York") + ); + } + + #[test] + fn whitespace_is_trimmed() { + assert_eq!( + windows_to_iana(" GMT Standard Time "), + Some("Europe/London") + ); + } + + #[test] + fn full_cldr_table_has_at_least_140_rows() { + assert!(TABLE.len() >= 140, "got {} rows", TABLE.len()); + } + + #[test] + fn spot_check_coverage_across_continents() { + assert_eq!(windows_to_iana("Tokyo Standard Time"), Some("Asia/Tokyo")); + assert_eq!( + windows_to_iana("AUS Eastern Standard Time"), + Some("Australia/Sydney") + ); + assert_eq!( + windows_to_iana("Cen. Australia Standard Time"), + Some("Australia/Adelaide") + ); + assert_eq!( + windows_to_iana("Greenland Standard Time"), + Some("America/Godthab") + ); + assert_eq!( + windows_to_iana("China Standard Time"), + Some("Asia/Shanghai") + ); + assert_eq!( + windows_to_iana("Israel Standard Time"), + Some("Asia/Jerusalem") + ); + assert_eq!( + windows_to_iana("Chatham Islands Standard Time"), + Some("Pacific/Chatham") + ); + assert_eq!( + windows_to_iana("Newfoundland Standard Time"), + Some("America/St_Johns") + ); + assert_eq!(windows_to_iana("UTC-08"), Some("Etc/GMT+8")); + assert_eq!(windows_to_iana("UTC+13"), Some("Etc/GMT-13")); + assert_eq!( + windows_to_iana("Yukon Standard Time"), + Some("America/Whitehorse") + ); + assert_eq!( + windows_to_iana("Aus Central W. Standard Time"), + Some("Australia/Eucla") + ); + } + + #[test] + fn coordinated_universal_time_alias_resolves_to_utc() { + assert_eq!( + windows_to_iana("Coordinated Universal Time"), + Some("Etc/UTC") + ); + } +} diff --git a/src/exchange_ews/autodiscover.rs b/src/exchange_ews/autodiscover.rs new file mode 100644 index 0000000..e3d6e72 --- /dev/null +++ b/src/exchange_ews/autodiscover.rs @@ -0,0 +1,561 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use quick_xml::NsReader; +use quick_xml::events::Event; +use serde_json::Value; +use ureq::Agent; +use ureq::config::Config; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::exchange_ews::error::EwsError; + +const V2_HOST: &str = "https://outlook.office365.com"; +const POX_REQ_NS: &str = + "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"; +const POX_RESP_NS: &str = + "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"; + +pub fn is_fully_qualified_ews_url(url: &str) -> bool { + let parsed = match url::Url::parse(url) { + Ok(u) => u, + Err(_) => return false, + }; + if parsed.scheme() != "https" { + return false; + } + parsed.path().eq_ignore_ascii_case("/EWS/Exchange.asmx") +} + +#[derive(Debug, Clone)] +pub struct DiscoveryResult { + pub ews_url: String, + pub source: DiscoverySource, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DiscoverySource { + V2, + V1, + SuppliedUrl, +} + +pub fn discover( + supplied_url: Option<&str>, + email: Option<&str>, + auth_header: Option<&str>, + allow_invalid_certs: bool, +) -> Result { + if let Some(url) = supplied_url + && is_fully_qualified_ews_url(url) + { + return Ok(DiscoveryResult { + ews_url: url.to_owned(), + source: DiscoverySource::SuppliedUrl, + }); + } + let Some(email) = email else { + return Err(EwsError::AutodiscoverFailed( + "either a fully-qualified --url or --mailbox is required".to_owned(), + )); + }; + let agent = build_agent(allow_invalid_certs); + if let Ok(url) = autodiscover_v2(&agent, email) { + return Ok(DiscoveryResult { + ews_url: url, + source: DiscoverySource::V2, + }); + } + let mut tried: Vec = Vec::new(); + let mut redirects: u8 = 0; + let mut url_redirects: u8 = 0; + let mut current_email = email.to_owned(); + 'outer: loop { + let domain = current_email + .split('@') + .nth(1) + .ok_or_else(|| EwsError::AutodiscoverFailed("--mailbox is not an email".to_owned()))?; + let candidates = pox_candidates(domain); + for candidate in &candidates { + tried.push(candidate.clone()); + match autodiscover_v1(&agent, candidate, ¤t_email, auth_header) { + Ok(PoxOutcome::EwsUrl(url)) => { + return Ok(DiscoveryResult { + ews_url: url, + source: DiscoverySource::V1, + }); + } + Ok(PoxOutcome::RedirectAddr(addr)) => { + if redirects >= 4 { + return Err(EwsError::AutodiscoverLoop); + } + redirects += 1; + current_email = addr; + continue 'outer; + } + Ok(PoxOutcome::RedirectUrl(url)) => { + if url_redirects >= 1 { + return Err(EwsError::AutodiscoverLoop); + } + url_redirects += 1; + tried.push(url.clone()); + if let Ok(PoxOutcome::EwsUrl(u)) = + autodiscover_v1(&agent, &url, ¤t_email, auth_header) + { + return Ok(DiscoveryResult { + ews_url: u, + source: DiscoverySource::V1, + }); + } + continue; + } + Err(_) | Ok(PoxOutcome::Failed) => continue, + } + } + break; + } + if let Some(url) = supplied_url { + return Ok(DiscoveryResult { + ews_url: url.to_owned(), + source: DiscoverySource::SuppliedUrl, + }); + } + Err(EwsError::AutodiscoverFailed(format!( + "tried {} candidate(s), none returned a v1 EwsUrl", + tried.len() + ))) +} + +fn build_agent(allow_invalid_certs: bool) -> Agent { + let config: Config = Config::builder() + .http_status_as_error(false) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + config.new_agent() +} + +fn autodiscover_v2(agent: &Agent, email: &str) -> Result { + let url = format!( + "{V2_HOST}/autodiscover/autodiscover.json?Email={}&Protocol=Ews", + urlencode(email) + ); + let mut resp = agent + .get(&url) + .header("Accept", "application/json") + .call() + .map_err(|e| EwsError::Transport(format!("autodiscover v2: {e}")))?; + let status = resp.status().as_u16(); + if !(200..300).contains(&status) { + return Err(EwsError::AutodiscoverFailed(format!( + "v2 returned http {status}" + ))); + } + let body: Value = resp + .body_mut() + .read_json() + .map_err(|e| EwsError::Malformed(format!("v2 body: {e}")))?; + let protocol = body.get("Protocol").and_then(Value::as_str).unwrap_or(""); + if !protocol.eq_ignore_ascii_case("Ews") { + return Err(EwsError::AutodiscoverFailed(format!( + "v2 returned protocol {protocol:?}" + ))); + } + let url = body + .get("Url") + .and_then(Value::as_str) + .ok_or_else(|| EwsError::AutodiscoverFailed("v2 missing Url".to_owned()))?; + Ok(url.to_owned()) +} + +#[derive(Debug, Clone)] +enum PoxOutcome { + EwsUrl(String), + RedirectAddr(String), + RedirectUrl(String), + Failed, +} + +fn pox_candidates(domain: &str) -> Vec { + let mut out = vec![ + format!("https://autodiscover.{domain}/autodiscover/autodiscover.xml"), + format!("https://{domain}/autodiscover/autodiscover.xml"), + ]; + for SrvRecord { target, port, .. } in srv_lookup(&format!("_autodiscover._tcp.{domain}")) { + let host = target.trim_end_matches('.'); + let suffix = if port == 443 { + String::new() + } else { + format!(":{port}") + }; + out.push(format!( + "https://{host}{suffix}/autodiscover/autodiscover.xml" + )); + } + out +} + +#[derive(Debug, Clone)] +struct SrvRecord { + priority: u16, + weight: u16, + port: u16, + target: String, +} + +fn srv_lookup(qname: &str) -> Vec { + use std::net::UdpSocket; + use std::time::Duration; + + let nameservers = resolv_conf_nameservers(); + if nameservers.is_empty() { + return Vec::new(); + } + let mut query = Vec::with_capacity(64); + let txn_id: u16 = 0x1234; + query.extend_from_slice(&txn_id.to_be_bytes()); + query.extend_from_slice(&0x0100u16.to_be_bytes()); + query.extend_from_slice(&1u16.to_be_bytes()); + query.extend_from_slice(&[0u8; 6]); + encode_qname(&mut query, qname); + query.extend_from_slice(&33u16.to_be_bytes()); + query.extend_from_slice(&1u16.to_be_bytes()); + + let mut out: Vec = Vec::new(); + for ns in nameservers { + let Ok(socket) = UdpSocket::bind("0.0.0.0:0") else { + continue; + }; + let _ = socket.set_read_timeout(Some(Duration::from_secs(3))); + if socket.send_to(&query, format!("{ns}:53")).is_err() { + continue; + } + let mut buf = [0u8; 1500]; + let Ok((n, _)) = socket.recv_from(&mut buf) else { + continue; + }; + if let Some(records) = parse_srv_response(&buf[..n]) { + out = records; + break; + } + } + out.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight))); + out +} + +fn resolv_conf_nameservers() -> Vec { + let path = std::path::Path::new("/etc/resolv.conf"); + let Ok(text) = std::fs::read_to_string(path) else { + return Vec::new(); + }; + let mut out: Vec = Vec::new(); + for line in text.lines() { + let line = line.split('#').next().unwrap_or("").trim(); + if let Some(rest) = line.strip_prefix("nameserver") { + let ns = rest.trim(); + if !ns.is_empty() { + out.push(ns.to_owned()); + } + } + } + out +} + +fn encode_qname(out: &mut Vec, qname: &str) { + for label in qname.trim_end_matches('.').split('.') { + if label.is_empty() { + continue; + } + out.push(label.len() as u8); + out.extend_from_slice(label.as_bytes()); + } + out.push(0); +} + +fn parse_srv_response(buf: &[u8]) -> Option> { + if buf.len() < 12 { + return None; + } + let qd = u16::from_be_bytes([buf[4], buf[5]]) as usize; + let an = u16::from_be_bytes([buf[6], buf[7]]) as usize; + let mut cursor = 12; + for _ in 0..qd { + cursor = skip_name(buf, cursor)?; + cursor = cursor.checked_add(4)?; + if cursor > buf.len() { + return None; + } + } + let mut out: Vec = Vec::new(); + for _ in 0..an { + cursor = skip_name(buf, cursor)?; + if cursor + 10 > buf.len() { + return None; + } + let rtype = u16::from_be_bytes([buf[cursor], buf[cursor + 1]]); + let rdlength = u16::from_be_bytes([buf[cursor + 8], buf[cursor + 9]]) as usize; + cursor += 10; + let rdata_end = cursor.checked_add(rdlength)?; + if rdata_end > buf.len() { + return None; + } + if rtype == 33 && rdlength >= 7 { + let priority = u16::from_be_bytes([buf[cursor], buf[cursor + 1]]); + let weight = u16::from_be_bytes([buf[cursor + 2], buf[cursor + 3]]); + let port = u16::from_be_bytes([buf[cursor + 4], buf[cursor + 5]]); + let target = read_name(buf, cursor + 6)?; + out.push(SrvRecord { + priority, + weight, + port, + target, + }); + } + cursor = rdata_end; + } + Some(out) +} + +fn skip_name(buf: &[u8], mut cursor: usize) -> Option { + loop { + if cursor >= buf.len() { + return None; + } + let len = buf[cursor]; + if len == 0 { + return Some(cursor + 1); + } + if (len & 0xC0) == 0xC0 { + return Some(cursor + 2); + } + cursor = cursor.checked_add(1 + len as usize)?; + } +} + +fn read_name(buf: &[u8], start: usize) -> Option { + let mut out = String::new(); + let mut cursor = start; + let mut hops = 0; + loop { + if cursor >= buf.len() { + return None; + } + let len = buf[cursor]; + if len == 0 { + break; + } + if (len & 0xC0) == 0xC0 { + if cursor + 1 >= buf.len() { + return None; + } + let pointer = ((len & 0x3F) as usize) << 8 | (buf[cursor + 1] as usize); + hops += 1; + if hops > 8 { + return None; + } + cursor = pointer; + continue; + } + cursor += 1; + let end = cursor.checked_add(len as usize)?; + if end > buf.len() { + return None; + } + if !out.is_empty() { + out.push('.'); + } + out.push_str(std::str::from_utf8(&buf[cursor..end]).ok()?); + cursor = end; + } + Some(out) +} + +fn autodiscover_v1( + agent: &Agent, + url: &str, + email: &str, + auth_header: Option<&str>, +) -> Result { + let body = format!( + "\ + \ + {}\ + {POX_RESP_NS}\ + ", + xml_escape(email) + ); + let mut req = agent + .post(url) + .header("Content-Type", "text/xml; charset=utf-8"); + if let Some(h) = auth_header { + req = req.header("Authorization", h); + } + let mut resp = req + .send(body.as_bytes()) + .map_err(|e| EwsError::Transport(format!("v1 POST: {e}")))?; + let status = resp.status().as_u16(); + if !(200..300).contains(&status) { + return Err(EwsError::AutodiscoverFailed(format!( + "{url} returned http {status}" + ))); + } + let bytes = resp + .body_mut() + .with_config() + .limit(2 * 1024 * 1024) + .read_to_vec() + .map_err(|e| EwsError::Malformed(format!("v1 body: {e}")))?; + parse_pox_response(&bytes) +} + +fn parse_pox_response(body: &[u8]) -> Result { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut current: Option<&'static str> = None; + let mut current_type: Option = None; + let mut ews_url: Option = None; + let mut redirect_addr: Option = None; + let mut redirect_url: Option = None; + let mut action: Option = None; + loop { + buf.clear(); + let (_, ev) = xml.read_resolved_event_into(&mut buf)?; + match ev { + Event::Start(e) => { + let local = e.local_name().as_ref().to_vec(); + if local.eq_ignore_ascii_case(b"Protocol") { + current_type = None; + } + if local.eq_ignore_ascii_case(b"Type") { + current = Some("type"); + } else if local.eq_ignore_ascii_case(b"EwsUrl") { + if matches!(current_type.as_deref(), Some("EXPR") | Some("EXCH")) { + current = Some("ewsUrl"); + } else { + current = None; + } + } else if local.eq_ignore_ascii_case(b"Action") { + current = Some("action"); + } else if local.eq_ignore_ascii_case(b"RedirectAddr") { + current = Some("redirectAddr"); + } else if local.eq_ignore_ascii_case(b"RedirectUrl") { + current = Some("redirectUrl"); + } else { + current = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + if local.eq_ignore_ascii_case(b"Protocol") { + current_type = None; + } + current = None; + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + match current { + Some("type") => current_type = Some(text), + Some("ewsUrl") => ews_url = Some(text), + Some("action") => action = Some(text), + Some("redirectAddr") => redirect_addr = Some(text), + Some("redirectUrl") => redirect_url = Some(text), + _ => {} + } + } + Event::Eof => break, + _ => {} + } + } + if let Some(addr) = redirect_addr.filter(|_| action.as_deref() == Some("redirectAddr")) { + return Ok(PoxOutcome::RedirectAddr(addr)); + } + if let Some(url) = redirect_url.filter(|_| action.as_deref() == Some("redirectUrl")) { + return Ok(PoxOutcome::RedirectUrl(url)); + } + if let Some(url) = ews_url { + return Ok(PoxOutcome::EwsUrl(url)); + } + Ok(PoxOutcome::Failed) +} + +fn xml_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for ch in s.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + _ => out.push(ch), + } + } + out +} + +fn urlencode(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for b in s.bytes() { + match b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(b as char); + } + _ => out.push_str(&format!("%{b:02X}")), + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn detects_fully_qualified_ews_url() { + assert!(is_fully_qualified_ews_url( + "https://outlook.office365.com/EWS/Exchange.asmx" + )); + assert!(is_fully_qualified_ews_url( + "https://exchange.example.com/EWS/Exchange.asmx" + )); + assert!(!is_fully_qualified_ews_url( + "http://outlook.office365.com/EWS/Exchange.asmx" + )); + assert!(!is_fully_qualified_ews_url( + "https://outlook.office365.com/" + )); + } + + #[test] + fn pox_response_extracts_ews_url() { + let body = format!( + "\ + \ + EXPRhttps://srv/EWS/Exchange.asmx" + ); + match parse_pox_response(body.as_bytes()).unwrap() { + PoxOutcome::EwsUrl(u) => assert_eq!(u, "https://srv/EWS/Exchange.asmx"), + _ => panic!("expected EwsUrl"), + } + } + + #[test] + fn pox_response_handles_redirect_addr() { + let body = format!( + "\ + \ + redirectAddrbob@other.com" + ); + match parse_pox_response(body.as_bytes()).unwrap() { + PoxOutcome::RedirectAddr(a) => assert_eq!(a, "bob@other.com"), + _ => panic!("expected RedirectAddr"), + } + } +} diff --git a/src/exchange_ews/calendar_map.rs b/src/exchange_ews/calendar_map.rs new file mode 100644 index 0000000..72afffb --- /dev/null +++ b/src/exchange_ews/calendar_map.rs @@ -0,0 +1,580 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value, json}; + +use crate::exchange_ews::parse::{CalendarItemRaw, RawAttendee, RawOccurrence}; +use crate::exchange_ews::recurrence::to_jscalendar_rule; +use crate::exchange_ews::tz::windows_to_iana; + +pub struct EventValue { + pub data: Value, + pub is_draft: bool, + pub use_default_alerts: bool, +} + +pub fn to_jscalendar(raw: &CalendarItemRaw) -> EventValue { + let mut event = Map::new(); + event.insert("@type".to_owned(), Value::String("Event".to_owned())); + if let Some(uid) = raw.uid.as_ref() { + event.insert("uid".to_owned(), Value::String(uid.clone())); + } else { + event.insert( + "uid".to_owned(), + Value::String(format!( + "vandelay-ews-event-{}", + blake3::hash(raw.id.id.as_bytes()).to_hex() + )), + ); + } + if let Some(subject) = raw.subject.as_ref() { + event.insert("title".to_owned(), Value::String(subject.clone())); + } + if let Some(loc) = raw.location.as_ref() { + event.insert( + "locations".to_owned(), + Value::Object(map_singleton( + "1", + json!({"@type": "Location", "name": loc}), + )), + ); + } + if let Some(body) = raw.body_text.as_ref() { + event.insert("description".to_owned(), Value::String(body.clone())); + } else if let Some(body) = raw.body_html.as_ref() { + event.insert("description".to_owned(), Value::String(body.clone())); + event.insert( + "descriptionContentType".to_owned(), + Value::String("text/html".to_owned()), + ); + } + if let Some(created) = raw.created.as_ref() { + event.insert( + "created".to_owned(), + Value::String(normalise_utc_datetime(created)), + ); + } + if let Some(updated) = raw.last_modified.as_ref() { + event.insert( + "updated".to_owned(), + Value::String(normalise_utc_datetime(updated)), + ); + } + if !raw.categories.is_empty() { + let mut kw = Map::new(); + for c in &raw.categories { + kw.insert(c.to_ascii_lowercase(), Value::Bool(true)); + } + event.insert("keywords".to_owned(), Value::Object(kw)); + } + let iana = raw + .start_tz + .as_deref() + .map(|tz| windows_to_iana(tz).unwrap_or(tz).to_owned()); + if let Some(true) = raw.is_all_day_event { + if let Some(start) = raw.start.as_ref() { + let date_only = start.split('T').next().unwrap_or(start.as_str()); + event.insert( + "start".to_owned(), + Value::String(format!("{date_only}T00:00:00")), + ); + } + event.insert("showWithoutTime".to_owned(), Value::Bool(true)); + let days = match (raw.start.as_ref(), raw.end.as_ref()) { + (Some(s), Some(e)) => all_day_span_days(s, e).max(1), + _ => 1, + }; + event.insert("duration".to_owned(), Value::String(format!("P{days}D"))); + } else { + if let Some(start) = raw.start.as_ref() { + event.insert( + "start".to_owned(), + Value::String(to_local_datetime_in(start, iana.as_deref())), + ); + } + if let (Some(start), Some(end)) = (raw.start.as_ref(), raw.end.as_ref()) + && let Some(dur) = duration_iso8601(start, end) + { + event.insert("duration".to_owned(), Value::String(dur)); + } + if let Some(tz) = iana.as_deref() { + event.insert("timeZone".to_owned(), Value::String(tz.to_owned())); + } + } + if let Some(status) = raw.legacy_free_busy_status.as_ref() { + let mapped = match status.as_str() { + "Free" => "free", + _ => "busy", + }; + event.insert( + "freeBusyStatus".to_owned(), + Value::String(mapped.to_owned()), + ); + } + let mut participants = Map::new(); + let mut next_id = 1; + if let Some(email) = raw.organizer_smtp.as_ref() { + let key = next_id.to_string(); + next_id += 1; + let cal_addr = format!("mailto:{email}"); + event.insert( + "organizerCalendarAddress".to_owned(), + Value::String(cal_addr.clone()), + ); + let mut p = Map::new(); + p.insert("@type".to_owned(), Value::String("Participant".to_owned())); + p.insert("calendarAddress".to_owned(), Value::String(cal_addr)); + p.insert("email".to_owned(), Value::String(email.clone())); + let mut roles = Map::new(); + roles.insert("owner".to_owned(), Value::Bool(true)); + roles.insert("chair".to_owned(), Value::Bool(true)); + p.insert("roles".to_owned(), Value::Object(roles)); + if let Some(name) = raw.organizer_name.as_ref() { + p.insert("name".to_owned(), Value::String(name.clone())); + } + participants.insert(key, Value::Object(p)); + } + add_attendees( + &mut participants, + &mut next_id, + &raw.required_attendees, + true, + ); + add_attendees( + &mut participants, + &mut next_id, + &raw.optional_attendees, + false, + ); + if !participants.is_empty() { + event.insert("participants".to_owned(), Value::Object(participants)); + } + if let Some(rec) = raw.recurrence.as_ref() + && let Some(rule) = to_jscalendar_rule(rec) + { + event.insert("recurrenceRules".to_owned(), Value::Array(vec![rule])); + } + let overrides = build_recurrence_overrides( + &raw.modified_occurrences, + &raw.deleted_occurrences, + iana.as_deref(), + ); + if let Some(overrides) = overrides { + event.insert("recurrenceOverrides".to_owned(), overrides); + } + EventValue { + data: Value::Object(event), + is_draft: false, + use_default_alerts: false, + } +} + +fn add_attendees( + out: &mut Map, + next_id: &mut u32, + attendees: &[RawAttendee], + required: bool, +) { + for att in attendees { + let key = next_id.to_string(); + *next_id += 1; + let mut p = Map::new(); + p.insert("@type".to_owned(), Value::String("Participant".to_owned())); + if let Some(email) = att.email.as_ref() { + p.insert( + "calendarAddress".to_owned(), + Value::String(format!("mailto:{email}")), + ); + p.insert("email".to_owned(), Value::String(email.clone())); + } + if let Some(name) = att.name.as_ref() { + p.insert("name".to_owned(), Value::String(name.clone())); + } + let mut roles = Map::new(); + if required { + roles.insert("required".to_owned(), Value::Bool(true)); + } else { + roles.insert("optional".to_owned(), Value::Bool(true)); + } + p.insert("roles".to_owned(), Value::Object(roles)); + if let Some(rt) = att.response_type.as_ref() { + let mapped = match rt.as_str() { + "Accept" => "accepted", + "Tentative" => "tentative", + "Decline" => "declined", + "Organizer" => "accepted", + _ => "needs-action", + }; + p.insert( + "participationStatus".to_owned(), + Value::String(mapped.to_owned()), + ); + } + out.insert(key, Value::Object(p)); + } +} + +fn build_recurrence_overrides( + modified: &[RawOccurrence], + deleted: &[RawOccurrence], + iana: Option<&str>, +) -> Option { + if modified.is_empty() && deleted.is_empty() { + return None; + } + let mut map = Map::new(); + for occ in modified { + let Some(key) = occ + .original_start + .as_deref() + .or(occ.start.as_deref()) + .map(|s| to_local_datetime_in(s, iana)) + else { + continue; + }; + let mut o = Map::new(); + if let Some(s) = occ.start.as_ref() { + o.insert( + "start".to_owned(), + Value::String(to_local_datetime_in(s, iana)), + ); + } + if let (Some(s), Some(e)) = (occ.start.as_ref(), occ.end.as_ref()) + && let Some(dur) = duration_iso8601(s, e) + { + o.insert("duration".to_owned(), Value::String(dur)); + } + map.insert(key, Value::Object(o)); + } + for occ in deleted { + let Some(key) = occ.start.as_deref().map(|s| to_local_datetime_in(s, iana)) else { + continue; + }; + map.insert(key, json!({"excluded": true})); + } + if map.is_empty() { + None + } else { + Some(Value::Object(map)) + } +} + +fn normalise_utc_datetime(s: &str) -> String { + let trimmed = s.trim(); + let stripped = trimmed + .strip_suffix('Z') + .or_else(|| { + trimmed + .rfind(['+', '-']) + .filter(|i| *i >= 10) + .map(|i| &trimmed[..i]) + }) + .unwrap_or(trimmed); + let (date, time) = match stripped.split_once('T') { + Some((d, t)) => (d, t), + None => return format!("{stripped}T00:00:00Z"), + }; + let time_clean = if let Some(dot) = time.find('.') { + &time[..dot] + } else { + time + }; + format!("{date}T{time_clean}Z") +} + +fn to_local_datetime_in(s: &str, iana: Option<&str>) -> String { + let trimmed = s.trim(); + let utc_anchored = + trimmed.ends_with('Z') || trimmed.rfind(['+', '-']).filter(|i| *i >= 10).is_some(); + if utc_anchored + && let Some(tz) = iana + && let Some(out) = convert_utc_to_local(trimmed, tz) + { + return out; + } + let stripped = trimmed + .strip_suffix('Z') + .or_else(|| { + trimmed + .rfind(['+', '-']) + .filter(|i| *i >= 10) + .map(|i| &trimmed[..i]) + }) + .unwrap_or(trimmed); + let (date, time) = match stripped.split_once('T') { + Some((d, t)) => (d, t), + None => return format!("{stripped}T00:00:00"), + }; + let time_clean = if let Some(dot) = time.find('.') { + &time[..dot] + } else { + time + }; + format!("{date}T{time_clean}") +} + +fn convert_utc_to_local(s: &str, iana: &str) -> Option { + use chrono::DateTime; + let utc: DateTime = DateTime::parse_from_rfc3339(s) + .ok()? + .with_timezone(&chrono::Utc); + let tz: chrono_tz::Tz = iana.parse().ok()?; + let local = utc.with_timezone(&tz); + Some(local.format("%Y-%m-%dT%H:%M:%S").to_string()) +} + +fn all_day_span_days(start: &str, end: &str) -> u32 { + fn date_only(s: &str) -> &str { + s.split('T').next().unwrap_or(s) + } + fn ymd(s: &str) -> Option<(i64, u32, u32)> { + let mut p = s.split('-'); + let y: i64 = p.next()?.parse().ok()?; + let m: u32 = p.next()?.parse().ok()?; + let d: u32 = p.next()?.parse().ok()?; + Some((y, m, d)) + } + fn to_days(y: i64, m: u32, d: u32) -> i64 { + let (mut y, m) = if m <= 2 { + (y - 1, m as i64 + 12) + } else { + (y, m as i64) + }; + y += 4800; + let mm = m + 1; + 365 * y + y / 4 - y / 100 + y / 400 + 30 * mm + 3 * (mm + 1) / 5 + d as i64 - 32045 + } + let (Some(s), Some(e)) = (ymd(date_only(start)), ymd(date_only(end))) else { + return 1; + }; + let diff = to_days(e.0, e.1, e.2) - to_days(s.0, s.1, s.2); + if diff <= 0 { 1 } else { diff as u32 } +} + +fn duration_iso8601(start: &str, end: &str) -> Option { + use time::OffsetDateTime; + use time::format_description::well_known::Rfc3339; + let s = OffsetDateTime::parse(start, &Rfc3339).ok()?; + let e = OffsetDateTime::parse(end, &Rfc3339).ok()?; + let dur = e - s; + if dur.is_zero() { + return Some("PT0S".to_owned()); + } + let total_seconds = dur.whole_seconds(); + if total_seconds < 0 { + return None; + } + let hours = total_seconds / 3600; + let minutes = (total_seconds % 3600) / 60; + let seconds = total_seconds % 60; + let mut out = String::from("PT"); + if hours > 0 { + out.push_str(&format!("{hours}H")); + } + if minutes > 0 { + out.push_str(&format!("{minutes}M")); + } + if seconds > 0 || (hours == 0 && minutes == 0) { + out.push_str(&format!("{seconds}S")); + } + Some(out) +} + +fn map_singleton(key: &str, value: Value) -> Map { + let mut m = Map::new(); + m.insert(key.to_owned(), value); + m +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::exchange_ews::parse::{RecurrencePattern, RecurrenceRange}; + + #[test] + fn simple_event_round_trip_converts_utc_to_local_wall_clock() { + let raw = CalendarItemRaw { + uid: Some("uid-1".to_owned()), + subject: Some("Meeting".to_owned()), + start: Some("2025-06-15T14:00:00Z".to_owned()), + end: Some("2025-06-15T15:00:00Z".to_owned()), + location: Some("HQ".to_owned()), + start_tz: Some("Pacific Standard Time".to_owned()), + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["@type"], "Event"); + assert_eq!(v["uid"], "uid-1"); + assert_eq!(v["title"], "Meeting"); + assert_eq!(v["start"], "2025-06-15T07:00:00"); + assert_eq!(v["duration"], "PT1H"); + assert_eq!(v["timeZone"], "America/Los_Angeles"); + assert_eq!(v["locations"]["1"]["name"], "HQ"); + } + + #[test] + fn naive_start_without_timezone_stays_floating() { + let raw = CalendarItemRaw { + uid: Some("uid-naive".to_owned()), + start: Some("2025-06-15T14:00:00".to_owned()), + end: Some("2025-06-15T15:00:00".to_owned()), + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["start"], "2025-06-15T14:00:00"); + assert!(v.get("timeZone").is_none()); + } + + #[test] + fn unknown_windows_timezone_passes_through_verbatim() { + let raw = CalendarItemRaw { + uid: Some("uid-x".to_owned()), + start: Some("2025-06-15T14:00:00Z".to_owned()), + end: Some("2025-06-15T15:00:00Z".to_owned()), + start_tz: Some("Made Up Time".to_owned()), + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["start"], "2025-06-15T14:00:00"); + assert_eq!(v["timeZone"], "Made Up Time"); + } + + #[test] + fn recurrence_override_key_uses_local_time_in_event_timezone() { + let raw = CalendarItemRaw { + uid: Some("uid-tz".to_owned()), + start: Some("2025-06-15T21:00:00Z".to_owned()), + end: Some("2025-06-15T22:00:00Z".to_owned()), + start_tz: Some("Pacific Standard Time".to_owned()), + recurrence: Some(crate::exchange_ews::parse::RawRecurrence { + pattern: Some(RecurrencePattern::Daily { interval: 1 }), + range: Some(RecurrenceRange::Numbered { + start_date: "2025-06-15".to_owned(), + number_of_occurrences: 3, + }), + }), + deleted_occurrences: vec![RawOccurrence { + item_id: Default::default(), + start: Some("2025-06-16T21:00:00Z".to_owned()), + end: None, + original_start: None, + }], + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + let over = v["recurrenceOverrides"].as_object().unwrap(); + assert!(over.contains_key("2025-06-16T14:00:00")); + } + + #[test] + fn all_day_event_uses_date_only_start() { + let raw = CalendarItemRaw { + uid: Some("uid-2".to_owned()), + subject: Some("Holiday".to_owned()), + is_all_day_event: Some(true), + start: Some("2025-12-25T00:00:00".to_owned()), + end: Some("2025-12-26T00:00:00".to_owned()), + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["start"], "2025-12-25T00:00:00"); + assert_eq!(v["showWithoutTime"], true); + assert_eq!(v["duration"], "P1D"); + assert!(v.get("timeZone").is_none()); + } + + #[test] + fn recurring_master_with_overrides() { + let raw = CalendarItemRaw { + uid: Some("uid-3".to_owned()), + start: Some("2025-06-15T14:00:00Z".to_owned()), + end: Some("2025-06-15T15:00:00Z".to_owned()), + recurrence: Some(crate::exchange_ews::parse::RawRecurrence { + pattern: Some(RecurrencePattern::Daily { interval: 1 }), + range: Some(RecurrenceRange::Numbered { + start_date: "2025-06-15".to_owned(), + number_of_occurrences: 3, + }), + }), + modified_occurrences: vec![RawOccurrence { + item_id: Default::default(), + start: Some("2025-06-16T15:00:00Z".to_owned()), + end: Some("2025-06-16T16:30:00Z".to_owned()), + original_start: Some("2025-06-16T14:00:00Z".to_owned()), + }], + deleted_occurrences: vec![RawOccurrence { + item_id: Default::default(), + start: Some("2025-06-17T14:00:00Z".to_owned()), + end: None, + original_start: None, + }], + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["recurrenceRules"][0]["frequency"], "daily"); + assert_eq!(v["recurrenceRules"][0]["count"], 3); + let over = v["recurrenceOverrides"].as_object().unwrap(); + assert!(over.contains_key("2025-06-16T14:00:00")); + assert_eq!(over["2025-06-17T14:00:00"]["excluded"], true); + } + + #[test] + fn multi_day_all_day_spans_correct_number_of_days() { + let raw = CalendarItemRaw { + uid: Some("uid-multi".to_owned()), + is_all_day_event: Some(true), + start: Some("2025-07-04T00:00:00".to_owned()), + end: Some("2025-07-07T00:00:00".to_owned()), + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["duration"], "P3D"); + assert_eq!(v["showWithoutTime"], true); + } + + #[test] + fn attendees_get_required_or_optional_role() { + let raw = CalendarItemRaw { + uid: Some("uid-att".to_owned()), + start: Some("2025-06-15T14:00:00Z".to_owned()), + end: Some("2025-06-15T15:00:00Z".to_owned()), + organizer_smtp: Some("alice@x".to_owned()), + required_attendees: vec![crate::exchange_ews::parse::RawAttendee { + email: Some("bob@x".to_owned()), + name: None, + response_type: Some("Accept".to_owned()), + }], + optional_attendees: vec![crate::exchange_ews::parse::RawAttendee { + email: Some("eve@x".to_owned()), + name: None, + response_type: None, + }], + ..CalendarItemRaw::default() + }; + let v = to_jscalendar(&raw).data; + assert_eq!(v["organizerCalendarAddress"], "mailto:alice@x"); + let participants = v["participants"].as_object().unwrap(); + let required = participants + .values() + .find(|p| p["email"] == "bob@x") + .unwrap(); + assert_eq!(required["roles"]["required"], true); + assert!(required["roles"].get("optional").is_none()); + let optional = participants + .values() + .find(|p| p["email"] == "eve@x") + .unwrap(); + assert_eq!(optional["roles"]["optional"], true); + } + + #[test] + fn all_day_span_helper_is_inclusive_of_start() { + assert_eq!(all_day_span_days("2025-07-04", "2025-07-04"), 1); + assert_eq!(all_day_span_days("2025-07-04", "2025-07-05"), 1); + assert_eq!(all_day_span_days("2025-07-04", "2025-07-07"), 3); + assert_eq!(all_day_span_days("bad", "input"), 1); + } +} diff --git a/src/exchange_ews/client.rs b/src/exchange_ews/client.rs new file mode 100644 index 0000000..6d24010 --- /dev/null +++ b/src/exchange_ews/client.rs @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::atomic::{AtomicU8, AtomicU64, Ordering}; +use std::time::Duration; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use ureq::Agent; +use ureq::config::{Config, RedirectAuthHeaders}; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::exchange_ews::error::EwsError; +use crate::exchange_ews::parse::{EnvelopeKind, read_envelope_summary}; +use crate::exchange_ews::retry::{FaultDisposition, classify_fault, classify_http_status}; +use crate::exchange_ews::soap::{EnvelopeOptions, soap_action, wrap_envelope}; +use crate::exchange_ews::types::ServerVersion; +use crate::jmap::http::{Auth, RetryPolicy, retry_after_header}; +use crate::jmap::retry::{self, Disposition, RateLimitState}; +use crate::logging::{LEVEL_BODIES, LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; + +const MAX_BODY: u64 = 2 * 1024 * 1024 * 1024; +const LONG_RETRY_THRESHOLD: Duration = Duration::from_secs(10); + +struct Inner { + agent: Agent, + auth: Mutex, + impersonated_smtp: Mutex>, + anchor_mailbox: Mutex>, + retry: RetryPolicy, + rate_limit: RateLimitState, + version: Mutex, + log_level: AtomicU8, + retries_total: AtomicU64, + retry_after_sleeps: AtomicU64, + soap_calls: AtomicU64, + user_agent: String, +} + +#[derive(Clone)] +pub struct EwsClient { + inner: Arc, +} + +#[derive(Debug, Clone)] +pub struct SoapResponse { + pub body: Vec, + pub server_version: Option, +} + +impl EwsClient { + pub fn new(auth: Auth, retry: RetryPolicy, allow_invalid_certs: bool) -> EwsClient { + let config: Config = Config::builder() + .http_status_as_error(false) + .redirect_auth_headers(RedirectAuthHeaders::SameHost) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + EwsClient { + inner: Arc::new(Inner { + agent: config.new_agent(), + auth: Mutex::new(auth), + impersonated_smtp: Mutex::new(None), + anchor_mailbox: Mutex::new(None), + retry, + rate_limit: RateLimitState::new(), + version: Mutex::new(ServerVersion::Exchange2013Sp1), + log_level: AtomicU8::new(LEVEL_DEFAULT), + retries_total: AtomicU64::new(0), + retry_after_sleeps: AtomicU64::new(0), + soap_calls: AtomicU64::new(0), + user_agent: format!("vandelay/{}", env!("CARGO_PKG_VERSION")), + }), + } + } + + pub fn set_auth(&self, auth: Auth) { + if let Ok(mut g) = self.inner.auth.lock() { + *g = auth; + } + } + + pub fn set_anchor_mailbox(&self, smtp: Option) { + if let Ok(mut g) = self.inner.anchor_mailbox.lock() { + *g = smtp; + } + } + + pub fn set_impersonation(&self, smtp: Option) { + if let Ok(mut g) = self.inner.impersonated_smtp.lock() { + *g = smtp; + } + } + + pub fn set_logger(&self, logger: Logger) { + self.inner + .log_level + .store(logger.level(), Ordering::Relaxed); + } + + pub fn logger(&self) -> Logger { + Logger::new(self.inner.log_level.load(Ordering::Relaxed)) + } + + pub fn set_server_version(&self, version: ServerVersion) { + if let Ok(mut g) = self.inner.version.lock() { + *g = version; + } + } + + pub fn server_version(&self) -> ServerVersion { + self.inner + .version + .lock() + .map(|g| *g) + .unwrap_or(ServerVersion::Exchange2013Sp1) + } + + pub fn retries_observed(&self) -> u64 { + self.inner.retries_total.load(Ordering::Relaxed) + } + + pub fn retry_after_sleeps(&self) -> u64 { + self.inner.retry_after_sleeps.load(Ordering::Relaxed) + } + + pub fn soap_calls(&self) -> u64 { + self.inner.soap_calls.load(Ordering::Relaxed) + } + + pub fn rate_limit(&self) -> &RateLimitState { + &self.inner.rate_limit + } + + pub fn call(&self, url: &str, operation: &str, body: &str) -> Result { + let envelope = self.wrap(body); + let action = soap_action(operation); + self.inner.soap_calls.fetch_add(1, Ordering::Relaxed); + self.execute(url, &envelope, &action) + } + + fn wrap(&self, body: &str) -> String { + let version = self.server_version(); + let smtp = self + .inner + .impersonated_smtp + .lock() + .ok() + .and_then(|g| g.clone()); + let opts = EnvelopeOptions { + version, + impersonated_smtp: smtp.as_deref(), + }; + wrap_envelope(opts, body) + } + + fn auth_header(&self) -> String { + let auth = self.inner.auth.lock().ok().map(|g| g.clone()); + match auth { + Some(Auth::Basic { user, password }) => { + format!("Basic {}", STANDARD.encode(format!("{user}:{password}"))) + } + Some(Auth::Bearer { token }) => format!("Bearer {token}"), + None => String::new(), + } + } + + fn anchor_header(&self) -> Option { + self.inner + .anchor_mailbox + .lock() + .ok() + .and_then(|g| g.clone()) + } + + fn execute(&self, url: &str, body: &str, action: &str) -> Result { + let logger = self.logger(); + let policy = self.inner.retry; + let mut attempt: u32 = 0; + loop { + self.inner.rate_limit.cooldown().wait(); + let outcome = self.one_attempt(url, body, action); + match outcome { + AttemptOutcome::Ok { + status, + body: bytes, + retry_after, + } => { + if (200..300).contains(&status) { + self.inner.rate_limit.on_success(); + match read_envelope_summary(&bytes) { + Ok(EnvelopeKind::Body { version }) => { + if let Some(v) = version.to_server_version() { + self.set_server_version(v); + } + return Ok(SoapResponse { + body: bytes, + server_version: version.to_server_version(), + }); + } + Ok(EnvelopeKind::Fault { fault, version }) => { + if let Some(v) = version.to_server_version() { + self.set_server_version(v); + } + match classify_fault(&fault.response_code) { + FaultDisposition::Fatal => { + return Err(EwsError::SoapFault { + code: fault.response_code, + reason: fault.fault_string, + }); + } + FaultDisposition::Auth => { + return Err(EwsError::Auth(fault.fault_string)); + } + FaultDisposition::Retryable { delay } => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(EwsError::RetriesExhausted(format!( + "{operation} kept returning {code}", + operation = action, + code = fault.response_code + ))); + } + let chosen = + self.inner.rate_limit.on_throttle(&policy, delay); + if chosen >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "EWS soap fault {} ({}); waiting {}s before retry {}/{}", + fault.response_code, + fault.fault_string, + chosen.as_secs(), + attempt, + policy.max_retries + )); + } + if logger.enabled(LEVEL_BODIES) { + eprintln!( + "retry {}/{} {} after {:?} ({})", + attempt, + policy.max_retries, + action, + chosen, + fault.response_code, + ); + } + std::thread::sleep(chosen); + continue; + } + } + } + Err(e) => return Err(e), + } + } + if status == 401 { + return Err(EwsError::Auth(format!( + "server returned 401: {}", + truncate(&bytes) + ))); + } + if status == 403 { + return Err(EwsError::HttpStatus { + status: 403, + body: truncate(&bytes), + }); + } + match classify_http_status(status) { + Disposition::Fatal => { + return Err(EwsError::HttpStatus { + status, + body: truncate(&bytes), + }); + } + Disposition::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(EwsError::RetriesExhausted(format!( + "{action} kept returning http {status}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let chosen = self.inner.rate_limit.on_throttle(&policy, retry_after); + if chosen >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "EWS {action} rate-limited (http {status}); waiting {}s", + chosen.as_secs() + )); + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "retry {}/{} (http {status})", + attempt, policy.max_retries + ); + } + std::thread::sleep(chosen); + } + } + } + AttemptOutcome::Transport(err) => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(err); + } + let delay = retry::backoff_delay(&policy, attempt); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "transient transport failure ({err}); waiting {}s before retry {}/{}", + delay.as_secs(), + attempt, + policy.max_retries + )); + } + std::thread::sleep(delay); + } + } + } + } + + fn one_attempt(&self, url: &str, body: &str, action: &str) -> AttemptOutcome { + let mut req = self + .inner + .agent + .post(url) + .header("Authorization", self.auth_header()) + .header("Content-Type", "text/xml; charset=utf-8") + .header("Accept", "text/xml, application/soap+xml, application/xml") + .header("Accept-Encoding", "gzip") + .header("SOAPAction", action.to_owned()) + .header("User-Agent", self.inner.user_agent.as_str()); + if let Some(anchor) = self.anchor_header() { + req = req.header("X-AnchorMailbox", anchor); + } + let result = req.send(body.as_bytes()); + match result { + Ok(mut resp) => { + let status = resp.status().as_u16(); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + match resp.body_mut().with_config().limit(MAX_BODY).read_to_vec() { + Ok(bytes) => AttemptOutcome::Ok { + status, + body: bytes, + retry_after, + }, + Err(e) => AttemptOutcome::Transport(EwsError::Transport(format!( + "reading response body: {e}" + ))), + } + } + Err(e) => AttemptOutcome::Transport(map_ureq_error(e)), + } + } +} + +enum AttemptOutcome { + Ok { + status: u16, + body: Vec, + retry_after: Option, + }, + Transport(EwsError), +} + +fn map_ureq_error(err: ureq::Error) -> EwsError { + match err { + ureq::Error::Io(e) => EwsError::Transport(format!("io: {e}")), + ureq::Error::Timeout(t) => EwsError::Transport(format!("timeout: {t}")), + ureq::Error::HostNotFound => EwsError::Transport("host not found".to_owned()), + ureq::Error::ConnectionFailed => EwsError::Transport("connection failed".to_owned()), + ureq::Error::BodyStalled => EwsError::Transport("body stalled".to_owned()), + ureq::Error::Tls(m) => EwsError::Transport(format!("tls: {m}")), + ureq::Error::TooManyRedirects => EwsError::Connect("too many redirects".to_owned()), + ureq::Error::RedirectFailed => EwsError::Connect("redirect failed".to_owned()), + ureq::Error::TlsRequired => { + EwsError::Connect("server requires TLS but transport is unsecured".to_owned()) + } + other => EwsError::Connect(other.to_string()), + } +} + +fn truncate(body: &[u8]) -> String { + let text = String::from_utf8_lossy(body); + if text.len() <= 512 { + return text.into_owned(); + } + let end = text.floor_char_boundary(512); + format!("{}...", &text[..end]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn client_constructs_with_defaults() { + let c = EwsClient::new( + Auth::Bearer { token: "t".into() }, + RetryPolicy::new(3), + false, + ); + assert_eq!(c.server_version(), ServerVersion::Exchange2013Sp1); + assert_eq!(c.retries_observed(), 0); + assert_eq!(c.retry_after_sleeps(), 0); + } + + #[test] + fn server_version_can_be_pinned() { + let c = EwsClient::new( + Auth::Bearer { token: "t".into() }, + RetryPolicy::new(0), + false, + ); + c.set_server_version(ServerVersion::Exchange2019); + assert_eq!(c.server_version(), ServerVersion::Exchange2019); + } + + #[test] + fn anchor_mailbox_round_trips() { + let c = EwsClient::new( + Auth::Bearer { token: "t".into() }, + RetryPolicy::new(0), + false, + ); + c.set_anchor_mailbox(Some("alice@x".to_owned())); + assert_eq!(c.anchor_header().as_deref(), Some("alice@x")); + } +} diff --git a/src/exchange_ews/contact_map.rs b/src/exchange_ews/contact_map.rs new file mode 100644 index 0000000..1ffe4c8 --- /dev/null +++ b/src/exchange_ews/contact_map.rs @@ -0,0 +1,454 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value, json}; + +use crate::exchange_ews::parse::{ContactItemRaw, RawContactAddress}; + +pub fn synthetic_uid(item_id: &str) -> String { + let hash = blake3::hash(item_id.as_bytes()).to_hex(); + format!("vandelay-ews-syn-{hash}") +} + +pub fn to_jscontact(raw: &ContactItemRaw) -> Value { + let mut card = Map::new(); + card.insert("@type".to_owned(), Value::String("Card".to_owned())); + card.insert("version".to_owned(), Value::String("1.0".to_owned())); + if let Some(full) = raw.display_name.as_ref() { + let mut name = Map::new(); + name.insert("@type".to_owned(), Value::String("Name".to_owned())); + name.insert("full".to_owned(), Value::String(full.clone())); + let components = name_components(raw); + if !components.is_empty() { + name.insert("components".to_owned(), Value::Array(components)); + } + card.insert("name".to_owned(), Value::Object(name)); + } + if let Some(n) = raw.nickname.as_ref() { + card.insert( + "nicknames".to_owned(), + Value::Object(map_singleton("1", json!({"@type": "Nickname", "name": n}))), + ); + } + let mut organizations: Map = Map::new(); + let mut have_org = false; + let mut org_inner: Map = Map::new(); + org_inner.insert("@type".to_owned(), Value::String("Organization".to_owned())); + if let Some(co) = raw.company_name.as_ref() { + org_inner.insert("name".to_owned(), Value::String(co.clone())); + have_org = true; + } + let mut org_units: Vec = Vec::new(); + if let Some(dep) = raw.department.as_ref() { + org_units.push(json!({"@type": "OrgUnit", "name": dep})); + } + if let Some(office) = raw.office_location.as_ref() { + org_units.push(json!({"@type": "OrgUnit", "name": office})); + } + if !org_units.is_empty() { + org_inner.insert("units".to_owned(), Value::Array(org_units)); + have_org = true; + } + if have_org { + organizations.insert("1".to_owned(), Value::Object(org_inner)); + } + let mut next_org_idx = 2u32; + for extra in &raw.companies { + if matches!(raw.company_name.as_deref(), Some(c) if c == extra) { + continue; + } + organizations.insert( + next_org_idx.to_string(), + json!({"@type": "Organization", "name": extra}), + ); + next_org_idx += 1; + } + if !organizations.is_empty() { + card.insert("organizations".to_owned(), Value::Object(organizations)); + } + if let Some(t) = raw.job_title.as_ref() { + card.insert( + "titles".to_owned(), + Value::Object(map_singleton("1", json!({"@type": "Title", "name": t}))), + ); + } + if !raw.emails.is_empty() { + let mut map = Map::new(); + for (i, (key, addr)) in raw.emails.iter().enumerate() { + let entry_id = format!("{}", i + 1); + let mut entry = Map::new(); + entry.insert("@type".to_owned(), Value::String("EmailAddress".to_owned())); + entry.insert("address".to_owned(), Value::String(addr.clone())); + entry.insert("contexts".to_owned(), email_contexts(key)); + map.insert(entry_id, Value::Object(entry)); + } + card.insert("emails".to_owned(), Value::Object(map)); + } + if !raw.phones.is_empty() { + let mut map = Map::new(); + for (i, (key, num)) in raw.phones.iter().enumerate() { + let entry_id = format!("{}", i + 1); + let mut entry = Map::new(); + entry.insert("@type".to_owned(), Value::String("Phone".to_owned())); + entry.insert("number".to_owned(), Value::String(num.clone())); + entry.insert("contexts".to_owned(), phone_contexts(key)); + entry.insert("features".to_owned(), phone_features(key)); + map.insert(entry_id, Value::Object(entry)); + } + card.insert("phones".to_owned(), Value::Object(map)); + } + if !raw.ims.is_empty() { + let mut map = Map::new(); + for (i, (key, addr)) in raw.ims.iter().enumerate() { + let entry_id = format!("{}", i + 1); + let mut entry = Map::new(); + entry.insert( + "@type".to_owned(), + Value::String("OnlineService".to_owned()), + ); + entry.insert("user".to_owned(), Value::String(addr.clone())); + if let Some(service) = im_service_from_key(key) { + entry.insert("service".to_owned(), Value::String(service)); + } + map.insert(entry_id, Value::Object(entry)); + } + card.insert("onlineServices".to_owned(), Value::Object(map)); + } + if !raw.addresses.is_empty() { + let pref_key = raw + .postal_address_index + .as_deref() + .map(str::to_ascii_lowercase); + let mut map = Map::new(); + for (i, addr) in raw.addresses.iter().enumerate() { + let entry_id = format!("{}", i + 1); + let is_pref = matches!(&pref_key, Some(p) if !p.is_empty() && p == &addr.key.to_ascii_lowercase()); + map.insert(entry_id, address_to_jscontact(addr, is_pref)); + } + card.insert("addresses".to_owned(), Value::Object(map)); + } + if let Some(url) = raw.url.as_ref() { + card.insert( + "links".to_owned(), + Value::Object(map_singleton("1", json!({"@type": "Link", "uri": url}))), + ); + } + if let Some(bday) = raw.birthday.as_ref() + && let Some(partial) = partial_date(bday) + { + card.insert( + "anniversaries".to_owned(), + Value::Object(map_singleton( + "birth", + json!({"@type": "Anniversary", "kind": "birth", "date": partial}), + )), + ); + } + if let Some(anniv) = raw.wedding_anniversary.as_ref() + && let Some(partial) = partial_date(anniv) + { + let key = "wedding"; + let entry = json!({"@type": "Anniversary", "kind": "wedding", "date": partial}); + if let Some(map) = card.get_mut("anniversaries").and_then(Value::as_object_mut) { + map.insert(key.to_owned(), entry); + } else { + card.insert( + "anniversaries".to_owned(), + Value::Object(map_singleton(key, entry)), + ); + } + } + if !raw.categories.is_empty() { + let mut keywords = Map::new(); + for cat in &raw.categories { + keywords.insert(cat.to_ascii_lowercase(), Value::Bool(true)); + } + card.insert("keywords".to_owned(), Value::Object(keywords)); + } + if let Some(notes) = raw.notes.as_ref() { + card.insert( + "notes".to_owned(), + Value::Object(map_singleton("1", json!({"@type": "Note", "note": notes}))), + ); + } + let mut related: Map = Map::new(); + if let Some(v) = raw.spouse.as_ref() { + related.insert( + v.clone(), + json!({"@type": "Relation", "relation": {"spouse": true}}), + ); + } + for child in &raw.children { + related.insert( + child.clone(), + json!({"@type": "Relation", "relation": {"child": true}}), + ); + } + if let Some(v) = raw.manager.as_ref() { + related.insert( + v.clone(), + json!({"@type": "Relation", "relation": {"x-manager": true}}), + ); + } + if let Some(v) = raw.assistant.as_ref() { + related.insert( + v.clone(), + json!({"@type": "Relation", "relation": {"x-assistant": true}}), + ); + } + if !related.is_empty() { + card.insert("relatedTo".to_owned(), Value::Object(related)); + } + Value::Object(card) +} + +fn name_components(raw: &ContactItemRaw) -> Vec { + let mut out: Vec = Vec::new(); + if let Some(v) = raw.given_name.as_ref() { + out.push(json!({"@type": "NameComponent", "kind": "given", "value": v})); + } + if let Some(v) = raw.middle_name.as_ref() { + out.push(json!({"@type": "NameComponent", "kind": "given2", "value": v})); + } + if let Some(v) = raw.surname.as_ref() { + out.push(json!({"@type": "NameComponent", "kind": "surname", "value": v})); + } + if let Some(v) = raw.generation.as_ref() { + out.push(json!({"@type": "NameComponent", "kind": "generation", "value": v})); + } + out +} + +fn email_contexts(key: &str) -> Value { + let mut o = Map::new(); + let lower = key.to_ascii_lowercase(); + if lower.contains("business") || lower.contains("work") { + o.insert("work".to_owned(), Value::Bool(true)); + } else if lower.contains("home") { + o.insert("private".to_owned(), Value::Bool(true)); + } + Value::Object(o) +} + +fn phone_contexts(key: &str) -> Value { + let mut o = Map::new(); + let lower = key.to_ascii_lowercase(); + if lower.starts_with("business") || lower.starts_with("company") { + o.insert("work".to_owned(), Value::Bool(true)); + } else if lower.starts_with("home") || lower.starts_with("mobile") { + o.insert("private".to_owned(), Value::Bool(true)); + } + Value::Object(o) +} + +fn phone_features(key: &str) -> Value { + let mut o = Map::new(); + let lower = key.to_ascii_lowercase(); + if lower.contains("mobile") { + o.insert("mobile".to_owned(), Value::Bool(true)); + } else if lower.contains("fax") { + o.insert("fax".to_owned(), Value::Bool(true)); + } else if lower.contains("pager") { + o.insert("pager".to_owned(), Value::Bool(true)); + } else { + o.insert("voice".to_owned(), Value::Bool(true)); + } + Value::Object(o) +} + +fn address_to_jscontact(addr: &RawContactAddress, is_pref: bool) -> Value { + let mut o = Map::new(); + o.insert("@type".to_owned(), Value::String("Address".to_owned())); + let mut components: Vec = Vec::new(); + if let Some(s) = addr.street.as_ref() { + components.push(json!({"@type": "AddressComponent", "kind": "name", "value": s})); + } + if let Some(s) = addr.city.as_ref() { + components.push(json!({"@type": "AddressComponent", "kind": "locality", "value": s})); + } + if let Some(s) = addr.state.as_ref() { + components.push(json!({"@type": "AddressComponent", "kind": "region", "value": s})); + } + if let Some(s) = addr.postal_code.as_ref() { + components.push(json!({"@type": "AddressComponent", "kind": "postcode", "value": s})); + } + if let Some(s) = addr.country.as_ref() { + components.push(json!({"@type": "AddressComponent", "kind": "country", "value": s})); + } + if !components.is_empty() { + o.insert("components".to_owned(), Value::Array(components)); + } + let mut contexts = Map::new(); + let lower = addr.key.to_ascii_lowercase(); + if lower == "business" || lower == "work" { + contexts.insert("work".to_owned(), Value::Bool(true)); + } else if lower == "home" { + contexts.insert("private".to_owned(), Value::Bool(true)); + } + if !contexts.is_empty() { + o.insert("contexts".to_owned(), Value::Object(contexts)); + } + if is_pref { + o.insert("pref".to_owned(), Value::from(1u32)); + } + Value::Object(o) +} + +fn im_service_from_key(key: &str) -> Option { + let trimmed: String = key.chars().filter(|c| !c.is_ascii_digit()).collect(); + let canonical = trimmed.trim_start_matches("ImAddress"); + if canonical.is_empty() { + return None; + } + Some(canonical.to_owned()) +} + +fn partial_date(s: &str) -> Option { + let head = s.split('T').next().unwrap_or(s); + let mut parts = head.splitn(3, '-'); + let year: u32 = parts.next()?.parse().ok()?; + let month: u32 = parts.next()?.parse().ok()?; + let day: u32 = parts.next()?.parse().ok()?; + let mut o = Map::new(); + o.insert("@type".to_owned(), Value::String("PartialDate".to_owned())); + if year > 0 { + o.insert("year".to_owned(), Value::from(year)); + } + o.insert("month".to_owned(), Value::from(month)); + o.insert("day".to_owned(), Value::from(day)); + Some(Value::Object(o)) +} + +fn map_singleton(key: &str, value: Value) -> Map { + let mut m = Map::new(); + m.insert(key.to_owned(), value); + m +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_contact_round_trip() { + let raw = ContactItemRaw { + display_name: Some("Alice Doe".to_owned()), + given_name: Some("Alice".to_owned()), + surname: Some("Doe".to_owned()), + emails: vec![("EmailAddress1".to_owned(), "alice@x".to_owned())], + phones: vec![("BusinessPhone1".to_owned(), "+1".to_owned())], + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert_eq!(v["@type"], "Card"); + assert_eq!(v["name"]["full"], "Alice Doe"); + let comps = v["name"]["components"].as_array().unwrap(); + assert_eq!(comps[0]["kind"], "given"); + assert_eq!(comps[0]["value"], "Alice"); + assert_eq!(v["emails"]["1"]["address"], "alice@x"); + assert_eq!(v["phones"]["1"]["contexts"]["work"], true); + } + + #[test] + fn synthetic_uid_is_stable() { + let u1 = synthetic_uid("ITEM-1"); + let u2 = synthetic_uid("ITEM-1"); + assert_eq!(u1, u2); + assert_ne!(u1, synthetic_uid("ITEM-2")); + assert!(u1.starts_with("vandelay-ews-syn-")); + } + + #[test] + fn anniversaries_combine_birth_and_wedding() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + birthday: Some("1990-04-15T00:00:00Z".to_owned()), + wedding_anniversary: Some("2015-08-22T00:00:00".to_owned()), + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert_eq!(v["anniversaries"]["birth"]["kind"], "birth"); + assert_eq!(v["anniversaries"]["birth"]["date"]["month"], 4); + assert_eq!(v["anniversaries"]["wedding"]["kind"], "wedding"); + } + + #[test] + fn office_location_is_added_as_org_unit() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + company_name: Some("Initech".to_owned()), + department: Some("R&D".to_owned()), + office_location: Some("Bldg 4".to_owned()), + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + let units = v["organizations"]["1"]["units"].as_array().unwrap(); + assert_eq!(units[0]["name"], "R&D"); + assert_eq!(units[1]["name"], "Bldg 4"); + } + + #[test] + fn extra_companies_become_additional_organizations() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + company_name: Some("Initech".to_owned()), + companies: vec!["Initech".to_owned(), "Initrode".to_owned()], + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert_eq!(v["organizations"]["1"]["name"], "Initech"); + assert_eq!(v["organizations"]["2"]["name"], "Initrode"); + } + + #[test] + fn postal_address_index_sets_pref_on_matching_entry() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + addresses: vec![ + RawContactAddress { + key: "Home".to_owned(), + street: Some("1 St".to_owned()), + ..Default::default() + }, + RawContactAddress { + key: "Business".to_owned(), + street: Some("2 Av".to_owned()), + ..Default::default() + }, + ], + postal_address_index: Some("Business".to_owned()), + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert!(v["addresses"]["1"].get("pref").is_none()); + assert_eq!(v["addresses"]["2"]["pref"], 1); + } + + #[test] + fn im_service_extracted_from_key() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + ims: vec![("ImAddress1".to_owned(), "alice@msn".to_owned())], + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert_eq!(v["onlineServices"]["1"]["user"], "alice@msn"); + assert!(v["onlineServices"]["1"].get("service").is_none()); + } + + #[test] + fn relations_use_x_prefix_for_manager_and_assistant() { + let raw = ContactItemRaw { + display_name: Some("X".to_owned()), + manager: Some("Boss".to_owned()), + assistant: Some("Asst".to_owned()), + ..ContactItemRaw::default() + }; + let v = to_jscontact(&raw); + assert_eq!(v["relatedTo"]["Boss"]["relation"]["x-manager"], true); + assert_eq!(v["relatedTo"]["Asst"]["relation"]["x-assistant"], true); + } +} diff --git a/src/exchange_ews/error.rs b/src/exchange_ews/error.rs new file mode 100644 index 0000000..334617e --- /dev/null +++ b/src/exchange_ews/error.rs @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; +use crate::exchange_ews::types::ResponseCode; +use crate::jmap::error::JmapError; + +#[derive(Debug, thiserror::Error)] +pub enum EwsError { + #[error("transport: {0}")] + Transport(String), + + #[error("connection: {0}")] + Connect(String), + + #[error("authentication rejected: {0}")] + Auth(String), + + #[error("http {status}: {body}")] + HttpStatus { status: u16, body: String }, + + #[error("retries exhausted: {0}")] + RetriesExhausted(String), + + #[error("soap fault {code}: {reason}")] + SoapFault { code: ResponseCode, reason: String }, + + #[error("malformed EWS response: {0}")] + Malformed(String), + + #[error("EWS schema unsupported: {0}")] + Unsupported(String), + + #[error("autodiscover failed: {0}")] + AutodiscoverFailed(String), + + #[error("autodiscover redirect loop")] + AutodiscoverLoop, + + #[error("oauth: {0}")] + OAuth(String), + + #[error("xml: {0}")] + Xml(String), +} + +impl From for EwsError { + fn from(value: quick_xml::Error) -> Self { + EwsError::Xml(value.to_string()) + } +} + +impl From for EwsError { + fn from(value: JmapError) -> Self { + match value { + JmapError::Transport(m) => EwsError::Transport(m), + JmapError::Connect(m) => EwsError::Connect(m), + JmapError::Auth(m) => EwsError::Auth(m), + JmapError::HttpStatus { status, body } => EwsError::HttpStatus { status, body }, + JmapError::RetriesExhausted(m) => EwsError::RetriesExhausted(m), + JmapError::Malformed(m) => EwsError::Malformed(m), + other => EwsError::Connect(other.to_string()), + } + } +} + +impl From for Error { + fn from(value: EwsError) -> Self { + match value { + EwsError::Auth(m) => Error::Connection(format!("authentication rejected: {m}")), + EwsError::Connect(m) | EwsError::Transport(m) => Error::Connection(m), + EwsError::AutodiscoverFailed(m) => { + Error::Connection(format!("autodiscover failed: {m}")) + } + EwsError::AutodiscoverLoop => { + Error::Connection("autodiscover redirect loop".to_owned()) + } + EwsError::OAuth(m) => Error::Connection(format!("oauth: {m}")), + EwsError::Unsupported(m) => Error::Usage(m), + other => Error::Connection(other.to_string()), + } + } +} diff --git a/src/exchange_ews/mod.rs b/src/exchange_ews/mod.rs new file mode 100644 index 0000000..df972b6 --- /dev/null +++ b/src/exchange_ews/mod.rs @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod autodiscover; +pub mod calendar_map; +pub mod client; +pub mod contact_map; +pub mod error; +pub mod oauth; +pub mod parse; +pub mod recurrence; +pub mod retry; +pub mod soap; +pub mod types; +pub mod xml; + +pub use crate::exchange::tz; + +pub use client::EwsClient; +pub use error::EwsError; +pub use types::{ + DistinguishedFolderId, FolderId, ItemId, MailboxKind, ResponseCode, ServerVersion, +}; diff --git a/src/exchange_ews/oauth.rs b/src/exchange_ews/oauth.rs new file mode 100644 index 0000000..2fb2795 --- /dev/null +++ b/src/exchange_ews/oauth.rs @@ -0,0 +1,373 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{self, Write}; +use std::time::Duration; + +use base64::Engine; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use serde_json::Value; +use ureq::config::Config; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::exchange_ews::error::EwsError; + +pub const SCOPE_APP_ONLY: &str = "https://outlook.office365.com/.default"; +pub const SCOPE_DELEGATED: &str = + "https://outlook.office365.com/EWS.AccessAsUser.All offline_access"; + +#[derive(Debug, Clone)] +pub struct AcquiredToken { + pub access_token: String, + pub refresh_token: Option, + pub expires_in: Option, + pub tenant_id: Option, + pub upn: Option, + pub name: Option, +} + +#[derive(Debug, Clone)] +pub enum OAuthFlow { + PreAcquired { + token: String, + }, + ClientCredentials { + tenant: String, + client_id: String, + client_secret: String, + }, + DeviceCode { + tenant: String, + client_id: String, + }, +} + +pub fn acquire(flow: &OAuthFlow, allow_invalid_certs: bool) -> Result { + match flow { + OAuthFlow::PreAcquired { token } => { + let claims = decode_jwt_claims(token).unwrap_or_default(); + Ok(AcquiredToken { + access_token: token.clone(), + refresh_token: None, + expires_in: None, + tenant_id: claims.tenant_id, + upn: claims.upn, + name: claims.name, + }) + } + OAuthFlow::ClientCredentials { + tenant, + client_id, + client_secret, + } => client_credentials(tenant, client_id, client_secret, allow_invalid_certs), + OAuthFlow::DeviceCode { tenant, client_id } => { + device_code_flow(tenant, client_id, allow_invalid_certs) + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct JwtClaims { + pub tenant_id: Option, + pub upn: Option, + pub name: Option, + pub exp: Option, +} + +pub fn decode_jwt_claims(token: &str) -> Option { + let mut parts = token.split('.'); + let _header = parts.next()?; + let payload = parts.next()?; + let bytes = URL_SAFE_NO_PAD + .decode(payload) + .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(payload)) + .or_else(|_| base64::engine::general_purpose::STANDARD.decode(payload)) + .ok()?; + let value: Value = serde_json::from_slice(&bytes).ok()?; + Some(JwtClaims { + tenant_id: value.get("tid").and_then(Value::as_str).map(str::to_owned), + upn: value + .get("upn") + .and_then(Value::as_str) + .or_else(|| value.get("preferred_username").and_then(Value::as_str)) + .map(str::to_owned), + name: value.get("name").and_then(Value::as_str).map(str::to_owned), + exp: value.get("exp").and_then(Value::as_u64), + }) +} + +fn token_endpoint(tenant: &str) -> String { + format!("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token") +} + +fn device_code_endpoint(tenant: &str) -> String { + format!("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode") +} + +fn build_agent(allow_invalid_certs: bool) -> ureq::Agent { + let config: Config = Config::builder() + .http_status_as_error(false) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + config.new_agent() +} + +fn client_credentials( + tenant: &str, + client_id: &str, + client_secret: &str, + allow_invalid_certs: bool, +) -> Result { + let agent = build_agent(allow_invalid_certs); + let body = form_encode(&[ + ("client_id", client_id), + ("client_secret", client_secret), + ("scope", SCOPE_APP_ONLY), + ("grant_type", "client_credentials"), + ]); + let endpoint = token_endpoint(tenant); + let resp = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| EwsError::OAuth(format!("token endpoint: {e}")))?; + parse_token_response(resp) +} + +fn device_code_flow( + tenant: &str, + client_id: &str, + allow_invalid_certs: bool, +) -> Result { + let agent = build_agent(allow_invalid_certs); + let body = form_encode(&[("client_id", client_id), ("scope", SCOPE_DELEGATED)]); + let endpoint = device_code_endpoint(tenant); + let mut resp = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| EwsError::OAuth(format!("device code endpoint: {e}")))?; + let json: Value = resp + .body_mut() + .read_json() + .map_err(|e| EwsError::OAuth(format!("device code body: {e}")))?; + let device_code = json + .get("device_code") + .and_then(Value::as_str) + .ok_or_else(|| EwsError::OAuth("device_code missing".to_owned()))? + .to_owned(); + let user_code = json.get("user_code").and_then(Value::as_str).unwrap_or("?"); + let verify_uri = json + .get("verification_uri") + .and_then(Value::as_str) + .unwrap_or("https://microsoft.com/devicelogin"); + let interval = json.get("interval").and_then(Value::as_u64).unwrap_or(5); + let message = json.get("message").and_then(Value::as_str); + if let Some(m) = message { + eprintln!("{m}"); + } else { + eprintln!("To sign in, open {verify_uri} and enter the code {user_code}"); + } + io::stdout() + .flush() + .map_err(|e| EwsError::OAuth(format!("flushing stdout: {e}")))?; + let endpoint = token_endpoint(tenant); + let mut delay = Duration::from_secs(interval); + let deadline = std::time::Instant::now() + + Duration::from_secs( + json.get("expires_in") + .and_then(Value::as_u64) + .unwrap_or(900), + ); + loop { + std::thread::sleep(delay); + if std::time::Instant::now() >= deadline { + return Err(EwsError::OAuth( + "device code expired before the user completed sign-in".to_owned(), + )); + } + let body = form_encode(&[ + ("client_id", client_id), + ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"), + ("device_code", &device_code), + ]); + let mut resp = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| EwsError::OAuth(format!("device code polling: {e}")))?; + let status = resp.status().as_u16(); + if (200..300).contains(&status) { + return parse_token_response(resp); + } + let payload: Value = resp.body_mut().read_json().unwrap_or(Value::Null); + let error = payload.get("error").and_then(Value::as_str).unwrap_or(""); + match error { + "authorization_pending" => continue, + "slow_down" => { + delay = delay.saturating_add(Duration::from_secs(5)); + continue; + } + "expired_token" => { + return Err(EwsError::OAuth( + "device code expired; restart the import".to_owned(), + )); + } + "" => { + return Err(EwsError::OAuth(format!( + "device code polling: http {status}" + ))); + } + other => { + let desc = payload + .get("error_description") + .and_then(Value::as_str) + .unwrap_or(""); + return Err(EwsError::OAuth(format!( + "device code polling: {other}: {desc}" + ))); + } + } + } +} + +fn parse_token_response( + mut resp: ureq::http::Response, +) -> Result { + let status = resp.status().as_u16(); + let json: Value = resp + .body_mut() + .read_json() + .map_err(|e| EwsError::OAuth(format!("token body: {e}")))?; + if !(200..300).contains(&status) { + let desc = json + .get("error_description") + .and_then(Value::as_str) + .unwrap_or("(no error_description)"); + return Err(EwsError::OAuth(format!("http {status}: {desc}"))); + } + let access = json + .get("access_token") + .and_then(Value::as_str) + .ok_or_else(|| EwsError::OAuth("access_token missing".to_owned()))? + .to_owned(); + let refresh_token = json + .get("refresh_token") + .and_then(Value::as_str) + .map(str::to_owned); + let expires_in = json.get("expires_in").and_then(Value::as_u64); + let claims = decode_jwt_claims(&access).unwrap_or_default(); + Ok(AcquiredToken { + access_token: access, + refresh_token, + expires_in, + tenant_id: claims.tenant_id, + upn: claims.upn, + name: claims.name, + }) +} + +pub fn refresh_with_token( + tenant: &str, + client_id: &str, + refresh_token: &str, + allow_invalid_certs: bool, +) -> Result { + let agent = build_agent(allow_invalid_certs); + let body = form_encode(&[ + ("client_id", client_id), + ("grant_type", "refresh_token"), + ("refresh_token", refresh_token), + ("scope", SCOPE_DELEGATED), + ]); + let endpoint = token_endpoint(tenant); + let resp = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| EwsError::OAuth(format!("refresh token endpoint: {e}")))?; + parse_token_response(resp) +} + +fn form_encode(pairs: &[(&str, &str)]) -> String { + let mut out = String::new(); + for (i, (k, v)) in pairs.iter().enumerate() { + if i > 0 { + out.push('&'); + } + out.push_str(&urlencode(k)); + out.push('='); + out.push_str(&urlencode(v)); + } + out +} + +fn urlencode(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for b in s.bytes() { + match b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(b as char); + } + _ => out.push_str(&format!("%{b:02X}")), + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::engine::general_purpose::URL_SAFE_NO_PAD; + + fn make_jwt(tid: &str, upn: &str, exp: u64) -> String { + let header = URL_SAFE_NO_PAD.encode(b"{\"alg\":\"none\"}"); + let claims = format!(r#"{{"tid":"{tid}","upn":"{upn}","exp":{exp}}}"#); + let payload = URL_SAFE_NO_PAD.encode(claims.as_bytes()); + format!("{header}.{payload}.") + } + + #[test] + fn jwt_decoder_extracts_tid_and_upn() { + let token = make_jwt("tenant-1", "alice@x.com", 9999999999); + let claims = decode_jwt_claims(&token).unwrap(); + assert_eq!(claims.tenant_id.as_deref(), Some("tenant-1")); + assert_eq!(claims.upn.as_deref(), Some("alice@x.com")); + assert_eq!(claims.exp, Some(9999999999)); + } + + #[test] + fn malformed_token_returns_none() { + assert!(decode_jwt_claims("garbage").is_none()); + assert!(decode_jwt_claims("only.two").is_none()); + } + + #[test] + fn pre_acquired_flow_decodes_claims() { + let token = make_jwt("t-2", "bob@x", 9999999999); + let acq = acquire( + &OAuthFlow::PreAcquired { + token: token.clone(), + }, + false, + ) + .unwrap(); + assert_eq!(acq.access_token, token); + assert_eq!(acq.tenant_id.as_deref(), Some("t-2")); + assert_eq!(acq.upn.as_deref(), Some("bob@x")); + } + + #[test] + fn urlencode_handles_special_chars() { + assert_eq!(urlencode("a b/c"), "a%20b%2Fc"); + assert_eq!(urlencode("a.b-c_d"), "a.b-c_d"); + } +} diff --git a/src/exchange_ews/parse.rs b/src/exchange_ews/parse.rs new file mode 100644 index 0000000..0f60bec --- /dev/null +++ b/src/exchange_ews/parse.rs @@ -0,0 +1,2332 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::BufRead; + +use quick_xml::NsReader; +use quick_xml::XmlVersion; +use quick_xml::events::{BytesStart, Event}; +use quick_xml::name::ResolveResult; + +use crate::exchange_ews::error::EwsError; +use crate::exchange_ews::soap::{NS_MESSAGES, NS_TYPES}; +use crate::exchange_ews::types::{CalendarItemType, FolderId, ItemId, ResponseCode, ServerVersion}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Ns { + Messages, + Types, + Other, +} + +fn classify(ns: &ResolveResult<'_>) -> Ns { + match ns { + ResolveResult::Bound(prefix) => { + let p = prefix.as_ref(); + if p == NS_MESSAGES.as_bytes() { + Ns::Messages + } else if p == NS_TYPES.as_bytes() { + Ns::Types + } else { + Ns::Other + } + } + _ => Ns::Other, + } +} + +fn is(ns: Ns, local: &[u8], expected_ns: Ns, target: &[u8]) -> bool { + ns == expected_ns && local.eq_ignore_ascii_case(target) +} + +fn attr_value(e: &BytesStart<'_>, name: &[u8]) -> Option { + for a in e.attributes().flatten() { + let key = a.key.local_name(); + if key.as_ref().eq_ignore_ascii_case(name) { + return Some( + a.normalized_value(XmlVersion::Implicit1_0) + .map(|c| c.into_owned()) + .unwrap_or_else(|_| String::from_utf8_lossy(a.value.as_ref()).into_owned()), + ); + } + } + None +} + +fn capture_id_attrs(e: &BytesStart<'_>, id_out: &mut String, ck_out: &mut String) { + for a in e.attributes().flatten() { + let key = a.key.local_name(); + let kb = key.as_ref(); + let v = a + .normalized_value(XmlVersion::Implicit1_0) + .map(|c| c.into_owned()) + .unwrap_or_else(|_| String::from_utf8_lossy(a.value.as_ref()).into_owned()); + if kb.eq_ignore_ascii_case(b"Id") { + *id_out = v; + } else if kb.eq_ignore_ascii_case(b"ChangeKey") { + *ck_out = v; + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct ServerVersionInfo { + pub major_version: Option, + pub minor_version: Option, + pub major_build: Option, + pub minor_build: Option, + pub version: Option, +} + +impl ServerVersionInfo { + pub fn to_server_version(&self) -> Option { + match (self.major_version, self.minor_version) { + (Some(maj), Some(min)) => Some(ServerVersion::from_build(maj, min)), + _ => None, + } + } +} + +fn read_version_attrs(e: &BytesStart<'_>, out: &mut ServerVersionInfo) { + for a in e.attributes().flatten() { + let key = a.key.local_name(); + let kb = key.as_ref(); + let v = String::from_utf8_lossy(a.value.as_ref()).into_owned(); + if kb.eq_ignore_ascii_case(b"MajorVersion") { + out.major_version = v.parse().ok(); + } else if kb.eq_ignore_ascii_case(b"MinorVersion") { + out.minor_version = v.parse().ok(); + } else if kb.eq_ignore_ascii_case(b"MajorBuildNumber") { + out.major_build = v.parse().ok(); + } else if kb.eq_ignore_ascii_case(b"MinorBuildNumber") { + out.minor_build = v.parse().ok(); + } else if kb.eq_ignore_ascii_case(b"Version") { + out.version = Some(v); + } + } +} + +#[derive(Debug, Clone)] +pub struct SoapFault { + pub fault_code: String, + pub fault_string: String, + pub response_code: ResponseCode, + pub back_off_ms: Option, +} + +#[derive(Debug, Clone)] +pub enum EnvelopeKind { + Fault { + version: ServerVersionInfo, + fault: SoapFault, + }, + Body { + version: ServerVersionInfo, + }, +} + +pub fn read_envelope_summary(bytes: &[u8]) -> Result { + let mut xml = NsReader::from_reader(bytes); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut version = ServerVersionInfo::default(); + let mut seen_body = false; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + if is(ns_kind, &local, Ns::Types, b"ServerVersionInfo") { + read_version_attrs(e, &mut version); + } else if local.eq_ignore_ascii_case(b"Fault") { + let fault = parse_fault(&mut xml)?; + return Ok(EnvelopeKind::Fault { version, fault }); + } else if local.eq_ignore_ascii_case(b"Body") { + seen_body = true; + } + } + Event::Eof => break, + _ => {} + } + } + if seen_body { + Ok(EnvelopeKind::Body { version }) + } else { + Err(EwsError::Malformed("envelope had no soap:Body".to_owned())) + } +} + +fn parse_fault(xml: &mut NsReader) -> Result { + let mut buf = Vec::new(); + let mut fault_code = String::new(); + let mut fault_string = String::new(); + let mut code = ResponseCode::Other(String::new()); + let mut back_off: Option = None; + let mut text_target: Option<&'static str> = None; + let mut last_value_name: Option = None; + let mut depth: u32 = 1; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(e) => { + depth += 1; + let local = e.local_name().as_ref().to_vec(); + if local.eq_ignore_ascii_case(b"faultcode") { + text_target = Some("faultcode"); + } else if local.eq_ignore_ascii_case(b"faultstring") { + text_target = Some("faultstring"); + } else if local.eq_ignore_ascii_case(b"ResponseCode") { + text_target = Some("responseCode"); + } else if local.eq_ignore_ascii_case(b"Value") && ns_kind == Ns::Types { + last_value_name = attr_value(&e, b"Name"); + text_target = Some("messageXmlValue"); + } else { + text_target = None; + } + } + Event::Empty(e) => { + let local = e.local_name().as_ref().to_vec(); + if is(ns_kind, &local, Ns::Types, b"Value") { + last_value_name = attr_value(&e, b"Name"); + } + } + Event::End(_) => { + if depth == 0 { + break; + } + depth -= 1; + text_target = None; + if depth == 0 { + break; + } + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + match text_target { + Some("faultcode") => fault_code = text, + Some("faultstring") => fault_string = text, + Some("responseCode") => code = ResponseCode::parse(&text), + Some("messageXmlValue") + if last_value_name.as_deref() == Some("BackOffMilliseconds") => + { + back_off = text.trim().parse().ok(); + } + _ => {} + } + } + Event::CData(c) if text_target == Some("faultstring") => { + fault_string = String::from_utf8_lossy(c.as_ref()).into_owned(); + } + Event::Eof => break, + _ => {} + } + } + if matches!(code, ResponseCode::ServerBusy { .. }) { + code = ResponseCode::ServerBusy { + back_off_ms: back_off, + }; + } + Ok(SoapFault { + fault_code, + fault_string, + response_code: code, + back_off_ms: back_off, + }) +} + +#[derive(Debug, Clone, Default)] +pub struct FindFolderResponse { + pub folders: Vec, + pub more: bool, + pub total_in_view: Option, +} + +#[derive(Debug, Clone)] +pub struct FolderEntry { + pub element: FolderElement, + pub folder_id: FolderId, + pub parent_id: Option, + pub display_name: String, + pub folder_class: String, + pub total_count: Option, + pub child_count: Option, +} + +impl Default for FolderEntry { + fn default() -> Self { + FolderEntry { + element: FolderElement::Folder, + folder_id: FolderId { + id: String::new(), + change_key: String::new(), + }, + parent_id: None, + display_name: String::new(), + folder_class: String::new(), + total_count: None, + child_count: None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FolderElement { + Folder, + CalendarFolder, + ContactsFolder, + TasksFolder, + SearchFolder, +} + +impl FolderElement { + fn from_local(local: &[u8]) -> Option { + if local.eq_ignore_ascii_case(b"Folder") { + Some(FolderElement::Folder) + } else if local.eq_ignore_ascii_case(b"CalendarFolder") { + Some(FolderElement::CalendarFolder) + } else if local.eq_ignore_ascii_case(b"ContactsFolder") { + Some(FolderElement::ContactsFolder) + } else if local.eq_ignore_ascii_case(b"TasksFolder") { + Some(FolderElement::TasksFolder) + } else if local.eq_ignore_ascii_case(b"SearchFolder") { + Some(FolderElement::SearchFolder) + } else { + None + } + } +} + +pub fn parse_find_folder_response(body: &[u8]) -> Result { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut out = FindFolderResponse::default(); + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + if is(ns_kind, &local, Ns::Messages, b"RootFolder") { + if let Some(v) = attr_value(e, b"TotalItemsInView") { + out.total_in_view = v.trim().parse().ok(); + } + if let Some(v) = attr_value(e, b"IncludesLastItemInRange") { + out.more = !matches!(v.trim(), "true" | "1"); + } + } else if ns_kind == Ns::Types + && let Some(elem) = FolderElement::from_local(&local) + { + let entry = parse_folder_element(&mut xml, elem)?; + out.folders.push(entry); + } + } + Event::Eof => break, + _ => {} + } + } + Ok(out) +} + +pub fn parse_folder_inner(inner_xml: &str) -> Result, EwsError> { + let mut xml = NsReader::from_reader(inner_xml.as_bytes()); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(_) | Event::Empty(_) => { + let local = match &ev { + Event::Start(e) | Event::Empty(e) => e.local_name().as_ref().to_vec(), + _ => continue, + }; + if ns_kind == Ns::Types + && let Some(elem) = FolderElement::from_local(&local) + { + return Ok(Some(parse_folder_element(&mut xml, elem)?)); + } + } + Event::Eof => return Ok(None), + _ => {} + } + } +} + +pub fn parse_get_folder_response(body: &[u8]) -> Result, EwsError> { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut out: Vec = Vec::new(); + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(_) | Event::Empty(_) => { + let local = match &ev { + Event::Start(e) | Event::Empty(e) => e.local_name().as_ref().to_vec(), + _ => continue, + }; + if ns_kind == Ns::Types + && let Some(elem) = FolderElement::from_local(&local) + { + let entry = parse_folder_element(&mut xml, elem)?; + out.push(entry); + } + } + Event::Eof => break, + _ => {} + } + } + Ok(out) +} + +fn parse_folder_element( + xml: &mut NsReader, + element: FolderElement, +) -> Result { + let mut entry = FolderEntry { + element, + ..FolderEntry::default() + }; + let mut buf = Vec::new(); + let mut depth: u32 = 1; + let mut current: Option<&'static str> = None; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let is_empty = matches!(ev, Event::Empty(_)); + if !is_empty { + depth += 1; + } + let local = e.local_name().as_ref().to_vec(); + if ns_kind == Ns::Types { + if local.eq_ignore_ascii_case(b"FolderId") { + capture_id_attrs( + e, + &mut entry.folder_id.id, + &mut entry.folder_id.change_key, + ); + } else if local.eq_ignore_ascii_case(b"ParentFolderId") { + let mut pid = String::new(); + let mut ck = String::new(); + capture_id_attrs(e, &mut pid, &mut ck); + if !pid.is_empty() { + entry.parent_id = Some(pid); + } + } else if local.eq_ignore_ascii_case(b"DisplayName") { + current = Some("displayName"); + } else if local.eq_ignore_ascii_case(b"FolderClass") { + current = Some("folderClass"); + } else if local.eq_ignore_ascii_case(b"TotalCount") { + current = Some("totalCount"); + } else if local.eq_ignore_ascii_case(b"ChildFolderCount") { + current = Some("childCount"); + } else { + current = None; + } + } + if is_empty { + current = None; + } + } + Event::End(_) => { + if depth == 0 { + break; + } + depth -= 1; + current = None; + if depth == 0 { + break; + } + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + match current { + Some("displayName") => entry.display_name = text, + Some("folderClass") => entry.folder_class = text, + Some("totalCount") => entry.total_count = text.trim().parse().ok(), + Some("childCount") => entry.child_count = text.trim().parse().ok(), + _ => {} + } + } + Event::Eof => break, + _ => {} + } + } + Ok(entry) +} + +#[derive(Debug, Clone, Default)] +pub struct FindItemResponse { + pub items: Vec, + pub total_in_view: Option, + pub more: bool, +} + +#[derive(Debug, Clone)] +pub struct ItemEntry { + pub element: String, + pub id: ItemId, +} + +pub fn parse_find_item_response(body: &[u8]) -> Result { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut out = FindItemResponse::default(); + let mut in_root = false; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + if is(ns_kind, &local, Ns::Messages, b"RootFolder") { + in_root = true; + if let Some(v) = attr_value(e, b"TotalItemsInView") { + out.total_in_view = v.trim().parse().ok(); + } + if let Some(v) = attr_value(e, b"IncludesLastItemInRange") { + out.more = !matches!(v.trim(), "true" | "1"); + } + } else if in_root && ns_kind == Ns::Types { + if is_item_element(&local) { + out.items.push(ItemEntry { + element: String::from_utf8_lossy(&local).into_owned(), + id: ItemId::default(), + }); + } else if local.eq_ignore_ascii_case(b"ItemId") + && let Some(last) = out.items.last_mut() + { + capture_id_attrs(e, &mut last.id.id, &mut last.id.change_key); + } + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + if local.eq_ignore_ascii_case(b"RootFolder") { + in_root = false; + } + } + Event::Eof => break, + _ => {} + } + } + Ok(out) +} + +fn is_item_element(local: &[u8]) -> bool { + matches!( + local.to_ascii_lowercase().as_slice(), + b"message" + | b"calendaritem" + | b"contact" + | b"meetingrequest" + | b"meetingresponse" + | b"meetingmessage" + | b"meetingcancellation" + | b"item" + ) +} + +#[derive(Debug, Clone)] +pub struct ResponseMessage { + pub success: bool, + pub response_code: ResponseCode, + pub message_text: String, + pub inner_xml: String, +} + +pub fn parse_response_messages( + body: &[u8], + response_message_local: &[u8], +) -> Result, EwsError> { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut out: Vec = Vec::new(); + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(e) => { + let local = e.local_name().as_ref().to_vec(); + if is(ns_kind, &local, Ns::Messages, response_message_local) { + let response_class = + attr_value(&e, b"ResponseClass").unwrap_or_else(|| "Success".to_owned()); + let msg = parse_one_response_message(&mut xml, response_class)?; + out.push(msg); + } + } + Event::Eof => break, + _ => {} + } + } + Ok(out) +} + +fn parse_one_response_message( + xml: &mut NsReader, + response_class: String, +) -> Result { + let mut buf = Vec::new(); + let mut response_code = ResponseCode::NoError; + let mut message_text = String::new(); + let mut inner = String::new(); + let mut depth: u32 = 1; + let mut current: Option<&'static str> = None; + let mut capture_depth: u32 = 0; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + let in_capture = capture_depth > 0; + match ev { + Event::Start(e) => { + depth += 1; + let local = e.local_name().as_ref().to_vec(); + if !in_capture && is(ns_kind, &local, Ns::Messages, b"ResponseCode") { + current = Some("responseCode"); + } else if !in_capture && is(ns_kind, &local, Ns::Messages, b"MessageText") { + current = Some("messageText"); + } else if !in_capture + && (is(ns_kind, &local, Ns::Messages, b"Items") + || is(ns_kind, &local, Ns::Messages, b"Attachments") + || is(ns_kind, &local, Ns::Messages, b"Folders")) + { + capture_depth = 1; + current = None; + } else if in_capture { + write_start_xml(&mut inner, &e); + capture_depth += 1; + } else { + current = None; + } + } + Event::Empty(e) => { + let local = e.local_name().as_ref().to_vec(); + if in_capture { + write_empty_xml(&mut inner, &e); + } else if is(ns_kind, &local, Ns::Messages, b"ResponseCode") + || is(ns_kind, &local, Ns::Messages, b"MessageText") + { + } + } + Event::End(e) => { + if in_capture { + if capture_depth == 1 { + capture_depth = 0; + } else { + write_end_xml_event(&mut inner, &e); + capture_depth -= 1; + } + } + if depth == 0 { + break; + } + depth -= 1; + current = None; + if depth == 0 { + break; + } + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + if in_capture { + write_text_xml(&mut inner, &text); + } else { + match current { + Some("responseCode") => response_code = ResponseCode::parse(text.trim()), + Some("messageText") => message_text = text, + _ => {} + } + } + } + Event::CData(c) => { + let text = String::from_utf8_lossy(c.as_ref()).into_owned(); + if in_capture { + write_text_xml(&mut inner, &text); + } + } + Event::Eof => break, + _ => {} + } + } + Ok(ResponseMessage { + success: matches!(response_class.as_str(), "Success" | "Warning"), + response_code, + message_text, + inner_xml: wrap_inner_with_namespaces(&inner), + }) +} + +fn wrap_inner_with_namespaces(inner: &str) -> String { + if inner.is_empty() { + return String::new(); + } + format!( + "{inner}", + ) +} + +fn write_start_xml(out: &mut String, e: &BytesStart<'_>) { + out.push('<'); + out.push_str(&String::from_utf8_lossy(e.name().as_ref())); + for a in e.attributes().flatten() { + out.push(' '); + out.push_str(&String::from_utf8_lossy(a.key.as_ref())); + out.push_str("=\""); + let val = String::from_utf8_lossy(a.value.as_ref()); + for ch in val.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + _ => out.push(ch), + } + } + out.push('"'); + } + out.push('>'); +} + +fn write_empty_xml(out: &mut String, e: &BytesStart<'_>) { + out.push('<'); + out.push_str(&String::from_utf8_lossy(e.name().as_ref())); + for a in e.attributes().flatten() { + out.push(' '); + out.push_str(&String::from_utf8_lossy(a.key.as_ref())); + out.push_str("=\""); + let val = String::from_utf8_lossy(a.value.as_ref()); + for ch in val.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + _ => out.push(ch), + } + } + out.push('"'); + } + out.push_str("/>"); +} + +fn write_end_xml_event(out: &mut String, e: &quick_xml::events::BytesEnd<'_>) { + out.push_str("'); +} + +fn write_text_xml(out: &mut String, text: &str) { + for ch in text.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + _ => out.push(ch), + } + } +} + +#[derive(Debug, Clone)] +pub struct SyncFolderItemsResponse { + pub sync_state: String, + pub more: bool, + pub changes: Vec, +} + +#[derive(Debug, Clone)] +pub enum SyncChange { + Create { id: ItemId, element: String }, + Update { id: ItemId, element: String }, + Delete { id: ItemId }, + ReadFlagChange { id: ItemId, is_read: bool }, +} + +pub fn parse_sync_folder_items_response(body: &[u8]) -> Result { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut sync_state = String::new(); + let mut more = false; + let mut changes: Vec = Vec::new(); + let mut current_text: Option<&'static str> = None; + let mut in_changes = false; + let mut current_change: Option = None; + let mut reading_is_read = false; + let mut pending_is_read: Option = None; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + let is_empty = matches!(ev, Event::Empty(_)); + if is(ns_kind, &local, Ns::Messages, b"SyncState") { + current_text = Some("syncState"); + } else if is(ns_kind, &local, Ns::Messages, b"IncludesLastItemInRange") { + current_text = Some("includesLast"); + } else if is(ns_kind, &local, Ns::Messages, b"Changes") { + in_changes = true; + } else if in_changes && ns_kind == Ns::Types { + if local.eq_ignore_ascii_case(b"Create") { + current_change = Some(SyncChange::Create { + id: ItemId::default(), + element: String::new(), + }); + } else if local.eq_ignore_ascii_case(b"Update") { + current_change = Some(SyncChange::Update { + id: ItemId::default(), + element: String::new(), + }); + } else if local.eq_ignore_ascii_case(b"Delete") { + current_change = Some(SyncChange::Delete { + id: ItemId::default(), + }); + } else if local.eq_ignore_ascii_case(b"ReadFlagChange") { + current_change = Some(SyncChange::ReadFlagChange { + id: ItemId::default(), + is_read: false, + }); + pending_is_read = None; + } else if local.eq_ignore_ascii_case(b"ItemId") { + if let Some(change) = current_change.as_mut() { + let (id, ck) = item_id_mut(change); + capture_id_attrs(e, id, ck); + } + } else if local.eq_ignore_ascii_case(b"IsRead") { + reading_is_read = true; + } else if is_item_element(&local) { + match current_change.as_mut() { + Some(SyncChange::Create { element, .. }) + | Some(SyncChange::Update { element, .. }) => { + *element = String::from_utf8_lossy(&local).into_owned(); + } + _ => {} + } + } + } + if is_empty { + current_text = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + let lower = local.to_ascii_lowercase(); + match lower.as_slice() { + b"create" | b"update" | b"delete" | b"readflagchange" => { + if let Some(mut change) = current_change.take() { + if let SyncChange::ReadFlagChange { is_read, .. } = &mut change + && let Some(v) = pending_is_read.take() + { + *is_read = v; + } + changes.push(change); + } + } + b"isread" => reading_is_read = false, + b"changes" => in_changes = false, + _ => {} + } + current_text = None; + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + if reading_is_read { + pending_is_read = Some(matches!(text.trim(), "true" | "1")); + } + match current_text { + Some("syncState") => sync_state = text, + Some("includesLast") => more = !matches!(text.trim(), "true" | "1"), + _ => {} + } + } + Event::CData(c) => { + let text = String::from_utf8_lossy(c.as_ref()).into_owned(); + if current_text == Some("syncState") { + sync_state = text; + } + } + Event::Eof => break, + _ => {} + } + } + Ok(SyncFolderItemsResponse { + sync_state, + more, + changes, + }) +} + +fn item_id_mut(change: &mut SyncChange) -> (&mut String, &mut String) { + match change { + SyncChange::Create { id, .. } + | SyncChange::Update { id, .. } + | SyncChange::Delete { id } + | SyncChange::ReadFlagChange { id, .. } => (&mut id.id, &mut id.change_key), + } +} + +#[derive(Debug, Clone, Default)] +pub struct MessageItem { + pub element: String, + pub id: ItemId, + pub parent_folder_id: Option, + pub mime_content: Option, + pub mime_charset: Option, + pub subject: Option, + pub date_time_received: Option, + pub is_read: Option, + pub is_draft: Option, + pub is_read_receipt_requested: Option, + pub categories: Vec, + pub flag_status: Option, +} + +pub fn parse_message_item(inner_xml: &str) -> Result { + let mut xml = NsReader::from_reader(inner_xml.as_bytes()); + xml.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut item = MessageItem::default(); + let mut current: Option<&'static str> = None; + let mut mime_charset: Option = None; + let mut category_collecting = false; + let mut in_flag = false; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + let is_empty = matches!(ev, Event::Empty(_)); + if item.element.is_empty() && is_item_element(&local) { + item.element = String::from_utf8_lossy(&local).into_owned(); + } else if ns_kind == Ns::Types { + if local.eq_ignore_ascii_case(b"ItemId") { + capture_id_attrs(e, &mut item.id.id, &mut item.id.change_key); + } else if local.eq_ignore_ascii_case(b"ParentFolderId") { + let mut pid = String::new(); + let mut ck = String::new(); + capture_id_attrs(e, &mut pid, &mut ck); + if !pid.is_empty() { + item.parent_folder_id = Some(pid); + } + } else if local.eq_ignore_ascii_case(b"MimeContent") { + mime_charset = attr_value(e, b"CharacterSet"); + current = Some("mimeContent"); + } else if local.eq_ignore_ascii_case(b"Subject") { + current = Some("subject"); + } else if local.eq_ignore_ascii_case(b"DateTimeReceived") { + current = Some("received"); + } else if local.eq_ignore_ascii_case(b"IsRead") { + current = Some("isRead"); + } else if local.eq_ignore_ascii_case(b"IsDraft") { + current = Some("isDraft"); + } else if local.eq_ignore_ascii_case(b"IsReadReceiptRequested") { + current = Some("readReceipt"); + } else if local.eq_ignore_ascii_case(b"Categories") { + category_collecting = true; + } else if category_collecting && local.eq_ignore_ascii_case(b"String") { + current = Some("category"); + } else if local.eq_ignore_ascii_case(b"Flag") { + in_flag = true; + } else if in_flag && local.eq_ignore_ascii_case(b"FlagStatus") { + current = Some("flagStatus"); + } else { + current = None; + } + } + if is_empty { + current = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + let lower = local.to_ascii_lowercase(); + if lower == b"categories" { + category_collecting = false; + } else if lower == b"flag" { + in_flag = false; + } + current = None; + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + match current { + Some("mimeContent") => { + item.mime_content + .get_or_insert_with(String::new) + .push_str(&text); + item.mime_charset = mime_charset.clone(); + } + Some("subject") => item.subject = Some(text), + Some("received") => item.date_time_received = Some(text), + Some("isRead") => item.is_read = Some(matches!(text.trim(), "true" | "1")), + Some("isDraft") => item.is_draft = Some(matches!(text.trim(), "true" | "1")), + Some("readReceipt") => { + item.is_read_receipt_requested = Some(matches!(text.trim(), "true" | "1")); + } + Some("category") => item.categories.push(text), + Some("flagStatus") => item.flag_status = Some(text), + _ => {} + } + } + Event::CData(c) => { + let text = String::from_utf8_lossy(c.as_ref()).into_owned(); + if current == Some("mimeContent") { + item.mime_content + .get_or_insert_with(String::new) + .push_str(&text); + item.mime_charset = mime_charset.clone(); + } + } + Event::Eof => break, + _ => {} + } + } + Ok(item) +} + +#[derive(Debug, Clone, Default)] +pub struct CalendarItemRaw { + pub element: String, + pub id: ItemId, + pub parent_folder_id: Option, + pub uid: Option, + pub subject: Option, + pub start: Option, + pub end: Option, + pub original_start: Option, + pub is_all_day_event: Option, + pub legacy_free_busy_status: Option, + pub location: Option, + pub calendar_item_type: Option, + pub recurrence_id: Option, + pub start_tz: Option, + pub end_tz: Option, + pub recurrence: Option, + pub modified_occurrences: Vec, + pub deleted_occurrences: Vec, + pub organizer_smtp: Option, + pub organizer_name: Option, + pub required_attendees: Vec, + pub optional_attendees: Vec, + pub categories: Vec, + pub created: Option, + pub last_modified: Option, + pub body_text: Option, + pub body_html: Option, + pub attachments: Vec, +} + +#[derive(Debug, Clone, Default)] +pub struct RawOccurrence { + pub item_id: ItemId, + pub start: Option, + pub end: Option, + pub original_start: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct RawAttendee { + pub email: Option, + pub name: Option, + pub response_type: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct RawAttachmentRef { + pub attachment_id: String, + pub name: Option, + pub content_type: Option, + pub is_contact_photo: bool, + pub is_item_attachment: bool, +} + +#[derive(Debug, Clone, Default)] +pub struct RawRecurrence { + pub pattern: Option, + pub range: Option, +} + +#[derive(Debug, Clone)] +pub enum RecurrencePattern { + Daily { + interval: u32, + }, + Weekly { + interval: u32, + days_of_week: Vec, + }, + AbsoluteMonthly { + interval: u32, + day_of_month: u32, + }, + RelativeMonthly { + interval: u32, + day_of_week_index: String, + days_of_week: Vec, + }, + AbsoluteYearly { + month: String, + day_of_month: u32, + }, + RelativeYearly { + month: String, + day_of_week_index: String, + days_of_week: Vec, + }, +} + +#[derive(Debug, Clone)] +pub enum RecurrenceRange { + NoEnd { + start_date: String, + }, + EndDate { + start_date: String, + end_date: String, + }, + Numbered { + start_date: String, + number_of_occurrences: u32, + }, +} + +pub fn parse_calendar_item(inner_xml: &str) -> Result { + let mut xml = NsReader::from_reader(inner_xml.as_bytes()); + xml.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut item = CalendarItemRaw::default(); + let mut text_target: Option<&'static str> = None; + let mut category_collecting = false; + let mut attendee_kind: Option<&'static str> = None; + let mut current_attendee: Option = None; + let mut in_mailbox = false; + let mut in_organizer = false; + let mut occurrence_stack: Vec = Vec::new(); + let mut deleted_stack: Vec = Vec::new(); + let mut in_modified = false; + let mut in_deleted = false; + let mut recurrence_path: Vec> = Vec::new(); + let mut recurrence_text: Option<&'static str> = None; + let mut recurrence = RawRecurrence::default(); + let mut pending = PendingRecurrence::default(); + + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + let is_empty = matches!(ev, Event::Empty(_)); + if item.element.is_empty() && is_item_element(&local) { + item.element = String::from_utf8_lossy(&local).into_owned(); + continue; + } + if ns_kind != Ns::Types { + continue; + } + if !recurrence_path.is_empty() { + recurrence_path.push(local.clone()); + if local.eq_ignore_ascii_case(b"Interval") { + recurrence_text = Some("interval"); + } else if local.eq_ignore_ascii_case(b"DaysOfWeek") { + recurrence_text = Some("daysOfWeek"); + } else if local.eq_ignore_ascii_case(b"DayOfMonth") { + recurrence_text = Some("dayOfMonth"); + } else if local.eq_ignore_ascii_case(b"DayOfWeekIndex") { + recurrence_text = Some("dayOfWeekIndex"); + } else if local.eq_ignore_ascii_case(b"Month") { + recurrence_text = Some("month"); + } else if local.eq_ignore_ascii_case(b"StartDate") { + recurrence_text = Some("startDate"); + } else if local.eq_ignore_ascii_case(b"EndDate") { + recurrence_text = Some("endDate"); + } else if local.eq_ignore_ascii_case(b"NumberOfOccurrences") { + recurrence_text = Some("numberOfOccurrences"); + } else if local.eq_ignore_ascii_case(b"DailyRecurrence") { + pending.pattern_choice = Some("Daily"); + } else if local.eq_ignore_ascii_case(b"WeeklyRecurrence") { + pending.pattern_choice = Some("Weekly"); + } else if local.eq_ignore_ascii_case(b"AbsoluteMonthlyRecurrence") { + pending.pattern_choice = Some("AbsoluteMonthly"); + } else if local.eq_ignore_ascii_case(b"RelativeMonthlyRecurrence") { + pending.pattern_choice = Some("RelativeMonthly"); + } else if local.eq_ignore_ascii_case(b"AbsoluteYearlyRecurrence") { + pending.pattern_choice = Some("AbsoluteYearly"); + } else if local.eq_ignore_ascii_case(b"RelativeYearlyRecurrence") { + pending.pattern_choice = Some("RelativeYearly"); + } else if local.eq_ignore_ascii_case(b"NoEndRecurrence") { + pending.range_choice = Some("NoEnd"); + } else if local.eq_ignore_ascii_case(b"EndDateRecurrence") { + pending.range_choice = Some("EndDate"); + } else if local.eq_ignore_ascii_case(b"NumberedRecurrence") { + pending.range_choice = Some("Numbered"); + } else { + recurrence_text = None; + } + if is_empty { + recurrence_path.pop(); + recurrence_text = None; + } + continue; + } + if local.eq_ignore_ascii_case(b"ItemId") { + if in_modified { + if let Some(occ) = occurrence_stack.last_mut() { + capture_id_attrs(e, &mut occ.item_id.id, &mut occ.item_id.change_key); + } + } else if in_deleted { + if let Some(occ) = deleted_stack.last_mut() { + capture_id_attrs(e, &mut occ.item_id.id, &mut occ.item_id.change_key); + } + } else { + capture_id_attrs(e, &mut item.id.id, &mut item.id.change_key); + } + } else if local.eq_ignore_ascii_case(b"ParentFolderId") { + let mut pid = String::new(); + let mut ck = String::new(); + capture_id_attrs(e, &mut pid, &mut ck); + if !pid.is_empty() { + item.parent_folder_id = Some(pid); + } + } else if local.eq_ignore_ascii_case(b"UID") { + text_target = Some("uid"); + } else if local.eq_ignore_ascii_case(b"Subject") { + text_target = Some("subject"); + } else if local.eq_ignore_ascii_case(b"Start") { + if in_modified || in_deleted { + text_target = Some("occStart"); + } else { + text_target = Some("start"); + } + } else if local.eq_ignore_ascii_case(b"End") { + if in_modified || in_deleted { + text_target = Some("occEnd"); + } else { + text_target = Some("end"); + } + } else if local.eq_ignore_ascii_case(b"OriginalStart") { + if in_modified || in_deleted { + text_target = Some("occOrig"); + } else { + text_target = Some("originalStart"); + } + } else if local.eq_ignore_ascii_case(b"IsAllDayEvent") { + text_target = Some("isAllDay"); + } else if local.eq_ignore_ascii_case(b"LegacyFreeBusyStatus") { + text_target = Some("freeBusy"); + } else if local.eq_ignore_ascii_case(b"Location") { + text_target = Some("location"); + } else if local.eq_ignore_ascii_case(b"CalendarItemType") { + text_target = Some("calendarItemType"); + } else if local.eq_ignore_ascii_case(b"RecurrenceId") { + text_target = Some("recurrenceId"); + } else if local.eq_ignore_ascii_case(b"StartTimeZone") { + if let Some(v) = attr_value(e, b"Id") { + item.start_tz = Some(v); + } + } else if local.eq_ignore_ascii_case(b"EndTimeZone") { + if let Some(v) = attr_value(e, b"Id") { + item.end_tz = Some(v); + } + } else if local.eq_ignore_ascii_case(b"Recurrence") { + recurrence_path.push(local.clone()); + } else if local.eq_ignore_ascii_case(b"ModifiedOccurrences") { + in_modified = true; + } else if local.eq_ignore_ascii_case(b"DeletedOccurrences") { + in_deleted = true; + } else if local.eq_ignore_ascii_case(b"Occurrence") { + let mut occ = RawOccurrence::default(); + capture_id_attrs(e, &mut occ.item_id.id, &mut occ.item_id.change_key); + occurrence_stack.push(occ); + } else if local.eq_ignore_ascii_case(b"DeletedOccurrence") { + deleted_stack.push(RawOccurrence::default()); + } else if local.eq_ignore_ascii_case(b"Organizer") { + in_organizer = true; + } else if local.eq_ignore_ascii_case(b"RequiredAttendees") { + attendee_kind = Some("required"); + } else if local.eq_ignore_ascii_case(b"OptionalAttendees") { + attendee_kind = Some("optional"); + } else if local.eq_ignore_ascii_case(b"Attendee") && attendee_kind.is_some() { + current_attendee = Some(RawAttendee::default()); + } else if local.eq_ignore_ascii_case(b"Mailbox") { + in_mailbox = true; + } else if local.eq_ignore_ascii_case(b"Name") && in_mailbox { + text_target = Some(if in_organizer { + "organizerName" + } else { + "attendeeName" + }); + } else if local.eq_ignore_ascii_case(b"EmailAddress") && in_mailbox { + text_target = Some(if in_organizer { + "organizerEmail" + } else { + "attendeeEmail" + }); + } else if local.eq_ignore_ascii_case(b"ResponseType") && current_attendee.is_some() + { + text_target = Some("attendeeResponse"); + } else if local.eq_ignore_ascii_case(b"Categories") { + category_collecting = true; + } else if category_collecting && local.eq_ignore_ascii_case(b"String") { + text_target = Some("category"); + } else if local.eq_ignore_ascii_case(b"DateTimeCreated") { + text_target = Some("created"); + } else if local.eq_ignore_ascii_case(b"LastModifiedTime") { + text_target = Some("lastModified"); + } else if local.eq_ignore_ascii_case(b"Body") { + let body_type = attr_value(e, b"BodyType").unwrap_or_default(); + text_target = Some(if body_type.eq_ignore_ascii_case("HTML") { + "bodyHtml" + } else { + "bodyText" + }); + } else if local.eq_ignore_ascii_case(b"FileAttachment") + || local.eq_ignore_ascii_case(b"ItemAttachment") + { + let att = parse_attachment_ref(&mut xml, &local)?; + item.attachments.push(att); + } else { + text_target = None; + } + if is_empty { + text_target = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + let lower = local.to_ascii_lowercase(); + if !recurrence_path.is_empty() { + recurrence_path.pop(); + if recurrence_path.is_empty() { + pending.finalize(&mut recurrence); + item.recurrence = Some(recurrence.clone()); + pending = PendingRecurrence::default(); + } + recurrence_text = None; + continue; + } + match lower.as_slice() { + b"modifiedoccurrences" => in_modified = false, + b"deletedoccurrences" => in_deleted = false, + b"occurrence" => { + if let Some(occ) = occurrence_stack.pop() { + item.modified_occurrences.push(occ); + } + } + b"deletedoccurrence" => { + if let Some(occ) = deleted_stack.pop() { + item.deleted_occurrences.push(occ); + } + } + b"attendee" => { + if let Some(att) = current_attendee.take() { + match attendee_kind { + Some("required") => item.required_attendees.push(att), + Some("optional") => item.optional_attendees.push(att), + _ => {} + } + } + } + b"requiredattendees" | b"optionalattendees" => attendee_kind = None, + b"organizer" => in_organizer = false, + b"mailbox" => in_mailbox = false, + b"categories" => category_collecting = false, + _ => {} + } + text_target = None; + } + Event::Text(_) | Event::CData(_) => { + let text = match ev { + Event::Text(ref t) => t.decode().map(|c| c.into_owned()).unwrap_or_default(), + Event::CData(ref c) => String::from_utf8_lossy(c.as_ref()).into_owned(), + _ => unreachable!(), + }; + if !recurrence_path.is_empty() { + match recurrence_text { + Some("interval") => pending.interval = text.trim().parse().unwrap_or(1), + Some("daysOfWeek") => { + for word in text.split_whitespace() { + pending.days_of_week.push(word.to_owned()); + } + } + Some("dayOfMonth") => { + pending.day_of_month = text.trim().parse().unwrap_or(0); + } + Some("dayOfWeekIndex") => { + pending.day_of_week_index = text.trim().to_owned() + } + Some("month") => pending.month = text.trim().to_owned(), + Some("startDate") => pending.start_date = text.trim().to_owned(), + Some("endDate") => pending.end_date = text.trim().to_owned(), + Some("numberOfOccurrences") => { + pending.number_of_occurrences = text.trim().parse().unwrap_or(0); + } + _ => {} + } + + continue; + } + + match text_target { + Some("uid") => item.uid = Some(text), + Some("subject") => item.subject = Some(text), + Some("start") => item.start = Some(text), + Some("end") => item.end = Some(text), + Some("originalStart") => item.original_start = Some(text), + Some("isAllDay") => { + item.is_all_day_event = Some(matches!(text.trim(), "true" | "1")); + } + Some("freeBusy") => item.legacy_free_busy_status = Some(text), + Some("location") => item.location = Some(text), + Some("calendarItemType") => { + item.calendar_item_type = CalendarItemType::parse(text.trim()); + } + Some("recurrenceId") => item.recurrence_id = Some(text), + Some("organizerName") => item.organizer_name = Some(text), + Some("organizerEmail") => item.organizer_smtp = Some(text), + Some("attendeeName") => { + if let Some(att) = current_attendee.as_mut() { + att.name = Some(text); + } + } + Some("attendeeEmail") => { + if let Some(att) = current_attendee.as_mut() { + att.email = Some(text); + } + } + Some("attendeeResponse") => { + if let Some(att) = current_attendee.as_mut() { + att.response_type = Some(text); + } + } + Some("category") => item.categories.push(text), + Some("created") => item.created = Some(text), + Some("lastModified") => item.last_modified = Some(text), + Some("bodyText") => { + item.body_text + .get_or_insert_with(String::new) + .push_str(&text); + } + Some("bodyHtml") => { + item.body_html + .get_or_insert_with(String::new) + .push_str(&text); + } + Some("occStart") => { + if let Some(occ) = occurrence_stack.last_mut() { + occ.start = Some(text.clone()); + } + if let Some(occ) = deleted_stack.last_mut() { + occ.start = Some(text); + } + } + Some("occEnd") => { + if let Some(occ) = occurrence_stack.last_mut() { + occ.end = Some(text); + } + } + Some("occOrig") => { + if let Some(occ) = occurrence_stack.last_mut() { + occ.original_start = Some(text); + } + } + _ => {} + } + } + Event::Eof => break, + _ => {} + } + } + Ok(item) +} + +#[derive(Default)] +struct PendingRecurrence { + pattern_choice: Option<&'static str>, + range_choice: Option<&'static str>, + interval: u32, + days_of_week: Vec, + day_of_month: u32, + day_of_week_index: String, + month: String, + start_date: String, + end_date: String, + number_of_occurrences: u32, +} + +impl PendingRecurrence { + fn finalize(self, out: &mut RawRecurrence) { + let interval = if self.interval == 0 { 1 } else { self.interval }; + out.pattern = match self.pattern_choice { + Some("Daily") => Some(RecurrencePattern::Daily { interval }), + Some("Weekly") => Some(RecurrencePattern::Weekly { + interval, + days_of_week: self.days_of_week.clone(), + }), + Some("AbsoluteMonthly") => Some(RecurrencePattern::AbsoluteMonthly { + interval, + day_of_month: self.day_of_month, + }), + Some("RelativeMonthly") => Some(RecurrencePattern::RelativeMonthly { + interval, + day_of_week_index: self.day_of_week_index.clone(), + days_of_week: self.days_of_week.clone(), + }), + Some("AbsoluteYearly") => Some(RecurrencePattern::AbsoluteYearly { + month: self.month.clone(), + day_of_month: self.day_of_month, + }), + Some("RelativeYearly") => Some(RecurrencePattern::RelativeYearly { + month: self.month.clone(), + day_of_week_index: self.day_of_week_index.clone(), + days_of_week: self.days_of_week.clone(), + }), + _ => None, + }; + out.range = match self.range_choice { + Some("NoEnd") => Some(RecurrenceRange::NoEnd { + start_date: self.start_date, + }), + Some("EndDate") => Some(RecurrenceRange::EndDate { + start_date: self.start_date, + end_date: self.end_date, + }), + Some("Numbered") => Some(RecurrenceRange::Numbered { + start_date: self.start_date, + number_of_occurrences: self.number_of_occurrences, + }), + _ => None, + }; + } +} + +#[derive(Debug, Clone, Default)] +pub struct ContactItemRaw { + pub id: ItemId, + pub parent_folder_id: Option, + pub display_name: Option, + pub given_name: Option, + pub middle_name: Option, + pub surname: Option, + pub initials: Option, + pub nickname: Option, + pub company_name: Option, + pub department: Option, + pub job_title: Option, + pub generation: Option, + pub office_location: Option, + pub url: Option, + pub birthday: Option, + pub wedding_anniversary: Option, + pub notes: Option, + pub categories: Vec, + pub emails: Vec<(String, String)>, + pub phones: Vec<(String, String)>, + pub addresses: Vec, + pub ims: Vec<(String, String)>, + pub manager: Option, + pub spouse: Option, + pub assistant: Option, + pub children: Vec, + pub companies: Vec, + pub profession: Option, + pub postal_address_index: Option, + pub created: Option, + pub last_modified: Option, + pub attachments: Vec, +} + +#[derive(Debug, Clone, Default)] +pub struct RawContactAddress { + pub key: String, + pub street: Option, + pub city: Option, + pub state: Option, + pub country: Option, + pub postal_code: Option, +} + +pub fn parse_contact_item(inner_xml: &str) -> Result { + let mut xml = NsReader::from_reader(inner_xml.as_bytes()); + xml.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut item = ContactItemRaw::default(); + let mut text_target: Option<&'static str> = None; + let mut category_collecting = false; + let mut children_collecting = false; + let mut companies_collecting = false; + let mut entry_container: Option<&'static str> = None; + let mut entry_key: Option = None; + let mut current_address: Option = None; + let mut address_text: Option<&'static str> = None; + let mut seen_root_contact = false; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + let is_empty = matches!(ev, Event::Empty(_)); + if !seen_root_contact && local.eq_ignore_ascii_case(b"Contact") { + seen_root_contact = true; + continue; + } + if ns_kind != Ns::Types { + continue; + } + if local.eq_ignore_ascii_case(b"ItemId") { + capture_id_attrs(e, &mut item.id.id, &mut item.id.change_key); + } else if local.eq_ignore_ascii_case(b"ParentFolderId") { + let mut pid = String::new(); + let mut ck = String::new(); + capture_id_attrs(e, &mut pid, &mut ck); + if !pid.is_empty() { + item.parent_folder_id = Some(pid); + } + } else if local.eq_ignore_ascii_case(b"DisplayName") { + text_target = Some("displayName"); + } else if local.eq_ignore_ascii_case(b"GivenName") { + text_target = Some("givenName"); + } else if local.eq_ignore_ascii_case(b"MiddleName") { + text_target = Some("middleName"); + } else if local.eq_ignore_ascii_case(b"Surname") { + text_target = Some("surname"); + } else if local.eq_ignore_ascii_case(b"Initials") { + text_target = Some("initials"); + } else if local.eq_ignore_ascii_case(b"Nickname") { + text_target = Some("nickname"); + } else if local.eq_ignore_ascii_case(b"CompanyName") { + text_target = Some("companyName"); + } else if local.eq_ignore_ascii_case(b"Department") { + text_target = Some("department"); + } else if local.eq_ignore_ascii_case(b"JobTitle") { + text_target = Some("jobTitle"); + } else if local.eq_ignore_ascii_case(b"Generation") { + text_target = Some("generation"); + } else if local.eq_ignore_ascii_case(b"OfficeLocation") { + text_target = Some("officeLocation"); + } else if local.eq_ignore_ascii_case(b"BusinessHomePage") { + text_target = Some("url"); + } else if local.eq_ignore_ascii_case(b"Birthday") { + text_target = Some("birthday"); + } else if local.eq_ignore_ascii_case(b"WeddingAnniversary") { + text_target = Some("weddingAnniversary"); + } else if local.eq_ignore_ascii_case(b"Manager") { + text_target = Some("manager"); + } else if local.eq_ignore_ascii_case(b"SpouseName") { + text_target = Some("spouse"); + } else if local.eq_ignore_ascii_case(b"AssistantName") { + text_target = Some("assistant"); + } else if local.eq_ignore_ascii_case(b"Profession") { + text_target = Some("profession"); + } else if local.eq_ignore_ascii_case(b"PostalAddressIndex") { + text_target = Some("postalAddressIndex"); + } else if local.eq_ignore_ascii_case(b"Body") { + text_target = Some("notes"); + } else if local.eq_ignore_ascii_case(b"DateTimeCreated") { + text_target = Some("created"); + } else if local.eq_ignore_ascii_case(b"LastModifiedTime") { + text_target = Some("lastModified"); + } else if local.eq_ignore_ascii_case(b"Categories") { + category_collecting = true; + } else if category_collecting && local.eq_ignore_ascii_case(b"String") { + text_target = Some("category"); + } else if local.eq_ignore_ascii_case(b"Children") { + children_collecting = true; + } else if children_collecting && local.eq_ignore_ascii_case(b"String") { + text_target = Some("child"); + } else if local.eq_ignore_ascii_case(b"Companies") { + companies_collecting = true; + } else if companies_collecting && local.eq_ignore_ascii_case(b"String") { + text_target = Some("company"); + } else if local.eq_ignore_ascii_case(b"EmailAddresses") { + entry_container = Some("email"); + } else if local.eq_ignore_ascii_case(b"PhoneNumbers") { + entry_container = Some("phone"); + } else if local.eq_ignore_ascii_case(b"ImAddresses") { + entry_container = Some("im"); + } else if local.eq_ignore_ascii_case(b"PhysicalAddresses") { + entry_container = Some("address"); + } else if local.eq_ignore_ascii_case(b"Entry") { + entry_key = attr_value(e, b"Key"); + if entry_container == Some("address") { + current_address = Some(RawContactAddress { + key: entry_key.clone().unwrap_or_default(), + ..RawContactAddress::default() + }); + } else { + text_target = Some("entryValue"); + } + } else if current_address.is_some() { + if local.eq_ignore_ascii_case(b"Street") { + address_text = Some("street"); + } else if local.eq_ignore_ascii_case(b"City") { + address_text = Some("city"); + } else if local.eq_ignore_ascii_case(b"State") { + address_text = Some("state"); + } else if local.eq_ignore_ascii_case(b"CountryOrRegion") { + address_text = Some("country"); + } else if local.eq_ignore_ascii_case(b"PostalCode") { + address_text = Some("postal"); + } else { + address_text = None; + } + } else if local.eq_ignore_ascii_case(b"FileAttachment") + || local.eq_ignore_ascii_case(b"ItemAttachment") + { + let att = parse_attachment_ref(&mut xml, &local)?; + item.attachments.push(att); + } else { + text_target = None; + } + if is_empty { + text_target = None; + address_text = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + let lower = local.to_ascii_lowercase(); + match lower.as_slice() { + b"categories" => category_collecting = false, + b"children" => children_collecting = false, + b"companies" => companies_collecting = false, + b"emailaddresses" | b"phonenumbers" | b"imaddresses" | b"physicaladdresses" => { + entry_container = None + } + b"entry" => { + if let Some(addr) = current_address.take() { + item.addresses.push(addr); + } + entry_key = None; + } + b"street" | b"city" | b"state" | b"countryorregion" | b"postalcode" => { + address_text = None; + } + _ => {} + } + text_target = None; + } + Event::Text(_) | Event::CData(_) => { + let text = match ev { + Event::Text(ref t) => t.decode().map(|c| c.into_owned()).unwrap_or_default(), + Event::CData(ref c) => String::from_utf8_lossy(c.as_ref()).into_owned(), + _ => unreachable!(), + }; + + if let Some(at) = address_text + && let Some(addr) = current_address.as_mut() + { + match at { + "street" => addr.street = Some(text.clone()), + "city" => addr.city = Some(text.clone()), + "state" => addr.state = Some(text.clone()), + "country" => addr.country = Some(text.clone()), + "postal" => addr.postal_code = Some(text.clone()), + _ => {} + } + continue; + } + match text_target { + Some("displayName") => item.display_name = Some(text), + Some("givenName") => item.given_name = Some(text), + Some("middleName") => item.middle_name = Some(text), + Some("surname") => item.surname = Some(text), + Some("initials") => item.initials = Some(text), + Some("nickname") => item.nickname = Some(text), + Some("companyName") => item.company_name = Some(text), + Some("department") => item.department = Some(text), + Some("jobTitle") => item.job_title = Some(text), + Some("generation") => item.generation = Some(text), + Some("officeLocation") => item.office_location = Some(text), + Some("url") => item.url = Some(text), + Some("birthday") => item.birthday = Some(text), + Some("weddingAnniversary") => item.wedding_anniversary = Some(text), + Some("manager") => item.manager = Some(text), + Some("spouse") => item.spouse = Some(text), + Some("assistant") => item.assistant = Some(text), + Some("profession") => item.profession = Some(text), + Some("postalAddressIndex") => item.postal_address_index = Some(text), + Some("notes") => item.notes = Some(text), + Some("created") => item.created = Some(text), + Some("lastModified") => item.last_modified = Some(text), + Some("category") => item.categories.push(text), + Some("child") => item.children.push(text), + Some("company") => item.companies.push(text), + Some("entryValue") => { + let key = entry_key.clone().unwrap_or_default(); + match entry_container { + Some("email") => item.emails.push((key, text)), + Some("phone") => item.phones.push((key, text)), + Some("im") => item.ims.push((key, text)), + _ => {} + } + } + _ => {} + } + } + Event::Eof => break, + _ => {} + } + } + Ok(item) +} + +fn parse_attachment_ref( + xml: &mut NsReader, + element_local: &[u8], +) -> Result { + let mut att = RawAttachmentRef { + is_item_attachment: element_local.eq_ignore_ascii_case(b"itemattachment"), + ..RawAttachmentRef::default() + }; + let mut buf = Vec::new(); + let mut depth: u32 = 1; + let mut current: Option<&'static str> = None; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let is_empty = matches!(ev, Event::Empty(_)); + if !is_empty { + depth += 1; + } + let local = e.local_name().as_ref().to_vec(); + if ns_kind == Ns::Types { + if local.eq_ignore_ascii_case(b"AttachmentId") { + if let Some(v) = attr_value(e, b"Id") { + att.attachment_id = v; + } + } else if local.eq_ignore_ascii_case(b"Name") { + current = Some("name"); + } else if local.eq_ignore_ascii_case(b"ContentType") { + current = Some("contentType"); + } else if local.eq_ignore_ascii_case(b"IsContactPhoto") { + current = Some("isContactPhoto"); + } else { + current = None; + } + } + if is_empty { + current = None; + } + } + Event::End(_) => { + if depth == 0 { + break; + } + depth -= 1; + current = None; + if depth == 0 { + break; + } + } + Event::Text(t) => { + let text = t.decode().map(|c| c.into_owned()).unwrap_or_default(); + match current { + Some("name") => att.name = Some(text), + Some("contentType") => att.content_type = Some(text), + Some("isContactPhoto") => { + att.is_contact_photo = matches!(text.trim(), "true" | "1"); + } + _ => {} + } + } + Event::Eof => break, + _ => {} + } + } + Ok(att) +} + +#[derive(Debug, Clone, Default)] +pub struct GetAttachmentInline { + pub attachment_id: String, + pub name: Option, + pub content_type: Option, + pub is_contact_photo: bool, + pub content_base64: String, +} + +pub fn parse_get_attachment_inline(body: &[u8]) -> Result, EwsError> { + let mut xml = NsReader::from_reader(body); + xml.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut out: Vec = Vec::new(); + let mut current: Option = None; + let mut text_target: Option<&'static str> = None; + loop { + buf.clear(); + let (ns, ev) = xml.read_resolved_event_into(&mut buf)?; + let ns_kind = classify(&ns); + match ev { + Event::Start(ref e) | Event::Empty(ref e) => { + let local = e.local_name().as_ref().to_vec(); + let is_empty = matches!(ev, Event::Empty(_)); + if ns_kind == Ns::Types + && (local.eq_ignore_ascii_case(b"FileAttachment") + || local.eq_ignore_ascii_case(b"ItemAttachment")) + { + current = Some(GetAttachmentInline::default()); + } else if let Some(cur) = current.as_mut() + && ns_kind == Ns::Types + { + if local.eq_ignore_ascii_case(b"AttachmentId") { + if let Some(v) = attr_value(e, b"Id") { + cur.attachment_id = v; + } + } else if local.eq_ignore_ascii_case(b"Name") { + text_target = Some("name"); + } else if local.eq_ignore_ascii_case(b"ContentType") { + text_target = Some("contentType"); + } else if local.eq_ignore_ascii_case(b"IsContactPhoto") { + text_target = Some("isContactPhoto"); + } else if local.eq_ignore_ascii_case(b"Content") { + text_target = Some("content"); + } else { + text_target = None; + } + } + if is_empty { + text_target = None; + } + } + Event::End(e) => { + let local = e.local_name().as_ref().to_vec(); + if (local.eq_ignore_ascii_case(b"FileAttachment") + || local.eq_ignore_ascii_case(b"ItemAttachment")) + && let Some(att) = current.take() + { + out.push(att); + } + text_target = None; + } + Event::Text(_) | Event::CData(_) => { + let text = match ev { + Event::Text(ref t) => t.decode().map(|c| c.into_owned()).unwrap_or_default(), + Event::CData(ref c) => String::from_utf8_lossy(c.as_ref()).into_owned(), + _ => unreachable!(), + }; + + if let Some(cur) = current.as_mut() { + match text_target { + Some("name") => cur.name = Some(text), + Some("contentType") => cur.content_type = Some(text), + Some("isContactPhoto") => { + cur.is_contact_photo = matches!(text.trim(), "true" | "1"); + } + Some("content") => cur.content_base64.push_str(&text), + _ => {} + } + } + } + Event::Eof => break, + _ => {} + } + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + const NS: &str = " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" \ + xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" \ + xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""; + + #[test] + fn server_busy_fault_extracts_backoff_milliseconds() { + let body = format!( + "\ + soap:Serverbusy\ + ErrorServerBusy\ + 297749\ + " + ); + let env = read_envelope_summary(body.as_bytes()).unwrap(); + match env { + EnvelopeKind::Fault { fault, version } => { + assert!(matches!( + fault.response_code, + ResponseCode::ServerBusy { + back_off_ms: Some(297749) + } + )); + assert_eq!(fault.back_off_ms, Some(297749)); + assert_eq!( + version.to_server_version(), + Some(ServerVersion::Exchange2016) + ); + } + _ => panic!("expected fault"), + } + } + + #[test] + fn body_envelope_returns_version_only() { + let body = format!( + "\ + " + ); + let env = read_envelope_summary(body.as_bytes()).unwrap(); + assert!(matches!(env, EnvelopeKind::Body { .. })); + } + + #[test] + fn find_folder_parses_two_folders() { + let body = format!( + "\ + NoError\ + \ + \ + \ + IPF.NoteInbox\ + 70\ + \ + IPF.AppointmentCalendar\ + \ + " + ); + let r = parse_find_folder_response(body.as_bytes()).unwrap(); + assert_eq!(r.folders.len(), 2); + assert_eq!(r.folders[0].folder_id.id, "F1"); + assert_eq!(r.folders[0].folder_id.change_key, "C1"); + assert_eq!(r.folders[0].display_name, "Inbox"); + assert_eq!(r.folders[0].folder_class, "IPF.Note"); + assert_eq!(r.folders[0].parent_id.as_deref(), Some("ROOT")); + assert_eq!(r.folders[1].element, FolderElement::CalendarFolder); + assert!(!r.more); + } + + #[test] + fn find_item_parses_two_messages_with_pagination_flag() { + let body = format!( + "\ + NoError\ + \ + \ + \ + \ + " + ); + let r = parse_find_item_response(body.as_bytes()).unwrap(); + assert_eq!(r.items.len(), 2); + assert_eq!(r.items[0].id.id, "I1"); + assert_eq!(r.items[0].id.change_key, "CK1"); + assert_eq!(r.items[1].id.id, "I2"); + assert!(r.more); + assert_eq!(r.total_in_view, Some(3)); + } + + #[test] + fn parse_response_messages_carries_inner_xml_and_codes() { + let body = format!( + "\ + \ + NoError\ + \ + Hitrue\ + SGVsbG8=\ + \ + \ + ErrorItemNotFound\ + not found\ + \ + " + ); + let r = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + assert_eq!(r.len(), 2); + assert!(r[0].success); + assert!(r[0].inner_xml.contains("\ + NoError\ + AAAA\ + true\ + \ + \ + \ + true\ + " + ); + let r = parse_sync_folder_items_response(body.as_bytes()).unwrap(); + assert_eq!(r.sync_state, "AAAA"); + assert!(!r.more); + assert_eq!(r.changes.len(), 3); + match &r.changes[0] { + SyncChange::Create { id, element } => { + assert_eq!(id.id, "I1"); + assert_eq!(element, "Message"); + } + _ => panic!("expected Create"), + } + match &r.changes[2] { + SyncChange::ReadFlagChange { id, is_read } => { + assert_eq!(id.id, "I3"); + assert!(*is_read); + } + _ => panic!("expected ReadFlagChange"), + } + } + + #[test] + fn calendar_master_recurrence_and_occurrences_round_trip() { + let body = format!( + "\ + \ + Daily\ + uid-1\ + 2025-06-15T14:00:00Z\ + 2025-06-15T15:00:00Z\ + RecurringMaster\ + \ + 1\ + 2025-06-153\ + \ + \ + 2025-06-16T15:00:00Z2025-06-16T16:30:00Z2025-06-16T14:00:00Z\ + \ + \ + 2025-06-17T14:00:00Z\ + \ + " + ); + let parsed = parse_calendar_item(&body).unwrap(); + assert_eq!(parsed.id.id, "M1"); + assert_eq!(parsed.uid.as_deref(), Some("uid-1")); + assert_eq!( + parsed.calendar_item_type, + Some(CalendarItemType::RecurringMaster) + ); + assert_eq!(parsed.modified_occurrences.len(), 1); + assert_eq!( + parsed.modified_occurrences[0].original_start.as_deref(), + Some("2025-06-16T14:00:00Z") + ); + assert_eq!(parsed.deleted_occurrences.len(), 1); + assert_eq!( + parsed.deleted_occurrences[0].start.as_deref(), + Some("2025-06-17T14:00:00Z") + ); + let rec = parsed.recurrence.unwrap(); + assert!(matches!( + rec.pattern, + Some(RecurrencePattern::Daily { interval: 1 }) + )); + assert!(matches!( + rec.range, + Some(RecurrenceRange::Numbered { + number_of_occurrences: 3, + .. + }) + )); + } + + #[test] + fn contact_parses_emails_phones_addresses() { + let body = format!( + "\ + \ + Alice Doe\ + Alice\ + Doe\ + \ + alice@example.com\ + \ + \ + +15551234\ + \ + \ + 1 MainTownUS\ + \ + " + ); + let parsed = parse_contact_item(&body).unwrap(); + assert_eq!(parsed.given_name.as_deref(), Some("Alice")); + assert_eq!(parsed.surname.as_deref(), Some("Doe")); + assert_eq!(parsed.emails.len(), 1); + assert_eq!(parsed.emails[0].0, "EmailAddress1"); + assert_eq!(parsed.emails[0].1, "alice@example.com"); + assert_eq!(parsed.phones.len(), 1); + assert_eq!(parsed.phones[0].0, "BusinessPhone1"); + assert_eq!(parsed.addresses.len(), 1); + assert_eq!(parsed.addresses[0].key, "Home"); + assert_eq!(parsed.addresses[0].street.as_deref(), Some("1 Main")); + assert_eq!(parsed.addresses[0].country.as_deref(), Some("US")); + } + + #[test] + fn get_item_with_unprefixed_types_elements_parses() { + let body = format!( + "\ + \ + NoError\ + \ + \ + Hello\ + SGk=\ + \ + " + ); + let msgs = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + assert_eq!(msgs.len(), 1); + let item = parse_message_item(&msgs[0].inner_xml).unwrap(); + assert_eq!(item.id.id, "NP1"); + assert_eq!(item.subject.as_deref(), Some("Hello")); + assert_eq!(item.mime_content.as_deref(), Some("SGk=")); + } + + #[test] + fn default_namespace_response_parses_via_resolved_names() { + let body = "\ + \ + \ + \ + NoError\ + \ + \ + IPF.NoteInbox\ + \ + \ + "; + let parsed = parse_find_folder_response(body.as_bytes()).unwrap(); + assert_eq!(parsed.folders.len(), 1); + assert_eq!(parsed.folders[0].folder_id.id, "D1"); + assert_eq!(parsed.folders[0].display_name, "Inbox"); + } + + #[test] + fn fault_response_code_in_messages_namespace_is_recognised() { + let body = format!( + "\ + soap:Serverbusy\ + ErrorServerBusy\ + 42\ + " + ); + let env = read_envelope_summary(body.as_bytes()).unwrap(); + match env { + EnvelopeKind::Fault { fault, .. } => { + assert!(matches!( + fault.response_code, + ResponseCode::ServerBusy { .. } + )); + assert_eq!(fault.back_off_ms, Some(42)); + } + _ => panic!("expected fault"), + } + } + + #[test] + fn change_key_attribute_with_entities_is_xml_decoded() { + let body = format!( + "\ + NoError\ + \ + \ + X\ + " + ); + let parsed = parse_find_folder_response(body.as_bytes()).unwrap(); + assert_eq!(parsed.folders[0].folder_id.change_key, "A&B"); + } + + #[test] + fn warning_response_class_is_treated_as_success() { + let body = format!( + "\ + \ + NoError\ + \ + " + ); + let r = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + assert_eq!(r.len(), 1); + assert!(r[0].success, "Warning should be success-equivalent"); + } + + #[test] + fn get_folder_response_message_captures_folder_inner() { + let body = format!( + "\ + \ + NoError\ + \ + Inbox\ + \ + \ + ErrorAccessDenied\ + " + ); + let msgs = parse_response_messages(body.as_bytes(), b"GetFolderResponseMessage").unwrap(); + assert_eq!(msgs.len(), 2); + assert!(msgs[0].success); + let f = parse_folder_inner(&msgs[0].inner_xml).unwrap().unwrap(); + assert_eq!(f.folder_id.id, "F1"); + assert!(!msgs[1].success); + } + + #[test] + fn get_attachment_inline_decodes_content_field() { + let body = format!( + "\ + \ + NoError\ + \ + p.png\ + image/png\ + true\ + iVBORw0KGgo=\ + " + ); + let items = parse_get_attachment_inline(body.as_bytes()).unwrap(); + assert_eq!(items.len(), 1); + assert_eq!(items[0].attachment_id, "A1"); + assert_eq!(items[0].name.as_deref(), Some("p.png")); + assert_eq!(items[0].content_type.as_deref(), Some("image/png")); + assert!(items[0].is_contact_photo); + assert_eq!(items[0].content_base64, "iVBORw0KGgo="); + } +} diff --git a/src/exchange_ews/recurrence.rs b/src/exchange_ews/recurrence.rs new file mode 100644 index 0000000..d7ead56 --- /dev/null +++ b/src/exchange_ews/recurrence.rs @@ -0,0 +1,309 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value, json}; + +use crate::exchange_ews::parse::{RawRecurrence, RecurrencePattern, RecurrenceRange}; + +pub fn to_jscalendar_rule(raw: &RawRecurrence) -> Option { + let mut rule: Map = Map::new(); + match raw.pattern.as_ref()? { + RecurrencePattern::Daily { interval } => { + rule.insert("frequency".to_owned(), Value::String("daily".to_owned())); + if *interval > 1 { + rule.insert("interval".to_owned(), Value::from(*interval)); + } + } + RecurrencePattern::Weekly { + interval, + days_of_week, + } => { + rule.insert("frequency".to_owned(), Value::String("weekly".to_owned())); + if *interval > 1 { + rule.insert("interval".to_owned(), Value::from(*interval)); + } + let days: Vec = days_of_week + .iter() + .filter_map(|d| day_token(d).map(|t| json!({"day": t}))) + .collect(); + if !days.is_empty() { + rule.insert("byDay".to_owned(), Value::Array(days)); + } + } + RecurrencePattern::AbsoluteMonthly { + interval, + day_of_month, + } => { + rule.insert("frequency".to_owned(), Value::String("monthly".to_owned())); + if *interval > 1 { + rule.insert("interval".to_owned(), Value::from(*interval)); + } + rule.insert( + "byMonthDay".to_owned(), + Value::Array(vec![Value::from(*day_of_month)]), + ); + } + RecurrencePattern::RelativeMonthly { + interval, + day_of_week_index, + days_of_week, + } => { + rule.insert("frequency".to_owned(), Value::String("monthly".to_owned())); + if *interval > 1 { + rule.insert("interval".to_owned(), Value::from(*interval)); + } + let nth = nth_of_period(day_of_week_index); + let days: Vec = days_of_week + .iter() + .filter_map(|d| { + day_token(d).map(|t| { + let mut o = Map::new(); + o.insert("day".to_owned(), Value::String(t.to_owned())); + if let Some(n) = nth { + o.insert("nthOfPeriod".to_owned(), Value::from(n)); + } + Value::Object(o) + }) + }) + .collect(); + if !days.is_empty() { + rule.insert("byDay".to_owned(), Value::Array(days)); + } + } + RecurrencePattern::AbsoluteYearly { + month, + day_of_month, + } => { + rule.insert("frequency".to_owned(), Value::String("yearly".to_owned())); + if let Some(n) = month_number(month) { + rule.insert( + "byMonth".to_owned(), + Value::Array(vec![Value::String(n.to_string())]), + ); + } + rule.insert( + "byMonthDay".to_owned(), + Value::Array(vec![Value::from(*day_of_month)]), + ); + } + RecurrencePattern::RelativeYearly { + month, + day_of_week_index, + days_of_week, + } => { + rule.insert("frequency".to_owned(), Value::String("yearly".to_owned())); + if let Some(n) = month_number(month) { + rule.insert( + "byMonth".to_owned(), + Value::Array(vec![Value::String(n.to_string())]), + ); + } + let nth = nth_of_period(day_of_week_index); + let days: Vec = days_of_week + .iter() + .filter_map(|d| { + day_token(d).map(|t| { + let mut o = Map::new(); + o.insert("day".to_owned(), Value::String(t.to_owned())); + if let Some(n) = nth { + o.insert("nthOfPeriod".to_owned(), Value::from(n)); + } + Value::Object(o) + }) + }) + .collect(); + if !days.is_empty() { + rule.insert("byDay".to_owned(), Value::Array(days)); + } + } + } + match raw.range.as_ref() { + Some(RecurrenceRange::NoEnd { .. }) | None => {} + Some(RecurrenceRange::EndDate { end_date, .. }) => { + let local = if end_date.contains('T') { + end_date.clone() + } else { + format!("{end_date}T23:59:59") + }; + rule.insert("until".to_owned(), Value::String(local)); + } + Some(RecurrenceRange::Numbered { + number_of_occurrences, + .. + }) => { + rule.insert("count".to_owned(), Value::from(*number_of_occurrences)); + } + } + Some(Value::Object(rule)) +} + +fn day_token(d: &str) -> Option<&'static str> { + match d.to_ascii_lowercase().as_str() { + "monday" | "mo" => Some("mo"), + "tuesday" | "tu" => Some("tu"), + "wednesday" | "we" => Some("we"), + "thursday" | "th" => Some("th"), + "friday" | "fr" => Some("fr"), + "saturday" | "sa" => Some("sa"), + "sunday" | "su" => Some("su"), + _ => None, + } +} + +fn nth_of_period(index: &str) -> Option { + match index { + "First" => Some(1), + "Second" => Some(2), + "Third" => Some(3), + "Fourth" => Some(4), + "Last" => Some(-1), + _ => None, + } +} + +fn month_number(m: &str) -> Option { + Some(match m { + "January" => 1, + "February" => 2, + "March" => 3, + "April" => 4, + "May" => 5, + "June" => 6, + "July" => 7, + "August" => 8, + "September" => 9, + "October" => 10, + "November" => 11, + "December" => 12, + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn daily_interval_round_trips() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::Daily { interval: 3 }), + range: Some(RecurrenceRange::Numbered { + start_date: "2025-01-01".to_owned(), + number_of_occurrences: 5, + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "daily"); + assert_eq!(rule["interval"], 3); + assert_eq!(rule["count"], 5); + } + + #[test] + fn relative_monthly_nth_of_period() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::RelativeMonthly { + interval: 1, + day_of_week_index: "First".to_owned(), + days_of_week: vec!["Monday".to_owned()], + }), + range: Some(RecurrenceRange::NoEnd { + start_date: "2025-01-01".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "monthly"); + let by_day = rule["byDay"].as_array().unwrap(); + assert_eq!(by_day[0]["day"], "mo"); + assert_eq!(by_day[0]["nthOfPeriod"], 1); + } + + #[test] + fn relative_yearly_last_friday_in_june() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::RelativeYearly { + month: "June".to_owned(), + day_of_week_index: "Last".to_owned(), + days_of_week: vec!["Friday".to_owned()], + }), + range: Some(RecurrenceRange::NoEnd { + start_date: "2020-01-01".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "yearly"); + assert_eq!(rule["byMonth"][0], "6"); + assert_eq!(rule["byDay"][0]["day"], "fr"); + assert_eq!(rule["byDay"][0]["nthOfPeriod"], -1); + } + + #[test] + fn absolute_monthly_translates_to_by_month_day() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::AbsoluteMonthly { + interval: 2, + day_of_month: 15, + }), + range: Some(RecurrenceRange::NoEnd { + start_date: "2025-01-15".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "monthly"); + assert_eq!(rule["interval"], 2); + assert_eq!(rule["byMonthDay"][0], 15); + } + + #[test] + fn absolute_yearly_translates_to_by_month_and_day() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::AbsoluteYearly { + month: "January".to_owned(), + day_of_month: 1, + }), + range: Some(RecurrenceRange::NoEnd { + start_date: "2025-01-01".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "yearly"); + assert_eq!(rule["byMonth"][0], "1"); + assert_eq!(rule["byMonthDay"][0], 1); + } + + #[test] + fn no_end_range_emits_no_until_or_count() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::Daily { interval: 1 }), + range: Some(RecurrenceRange::NoEnd { + start_date: "2025-01-01".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert!(rule.get("until").is_none()); + assert!(rule.get("count").is_none()); + } + + #[test] + fn weekly_two_days() { + let raw = RawRecurrence { + pattern: Some(RecurrencePattern::Weekly { + interval: 1, + days_of_week: vec!["Monday".to_owned(), "Wednesday".to_owned()], + }), + range: Some(RecurrenceRange::EndDate { + start_date: "2025-01-06".to_owned(), + end_date: "2025-06-30".to_owned(), + }), + }; + let rule = to_jscalendar_rule(&raw).unwrap(); + assert_eq!(rule["frequency"], "weekly"); + assert_eq!(rule["until"], "2025-06-30T23:59:59"); + let by_day = rule["byDay"].as_array().unwrap(); + assert_eq!(by_day.len(), 2); + assert_eq!(by_day[0]["day"], "mo"); + assert_eq!(by_day[1]["day"], "we"); + } +} diff --git a/src/exchange_ews/retry.rs b/src/exchange_ews/retry.rs new file mode 100644 index 0000000..33a7450 --- /dev/null +++ b/src/exchange_ews/retry.rs @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::time::Duration; + +use crate::exchange_ews::types::ResponseCode; +use crate::jmap::retry::Disposition; + +pub fn classify_http_status(status: u16) -> Disposition { + match status { + 429 | 502 | 503 | 504 => Disposition::Retryable, + _ => Disposition::Fatal, + } +} + +#[derive(Debug, Clone)] +pub enum FaultDisposition { + Fatal, + Auth, + Retryable { delay: Option }, +} + +pub fn classify_fault(code: &ResponseCode) -> FaultDisposition { + match code { + ResponseCode::ServerBusy { back_off_ms } => FaultDisposition::Retryable { + delay: back_off_ms.map(Duration::from_millis), + }, + ResponseCode::TimeoutExpired + | ResponseCode::InternalServerTransientError + | ResponseCode::MailboxStoreUnavailable + | ResponseCode::ConnectionFailed + | ResponseCode::AdUnavailable + | ResponseCode::BatchProcessingStopped => FaultDisposition::Retryable { delay: None }, + ResponseCode::AuthenticationRequired => FaultDisposition::Auth, + _ => FaultDisposition::Fatal, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ItemAction { + Success, + Vanished, + Skip, + Retryable, + RetryDiscardSyncState, +} + +pub fn classify_item_response(code: &ResponseCode) -> ItemAction { + match code { + ResponseCode::NoError => ItemAction::Success, + ResponseCode::ItemNotFound | ResponseCode::AttachmentNotFound => ItemAction::Vanished, + ResponseCode::ServerBusy { .. } + | ResponseCode::TimeoutExpired + | ResponseCode::InternalServerTransientError + | ResponseCode::MailboxStoreUnavailable + | ResponseCode::ConnectionFailed + | ResponseCode::AdUnavailable + | ResponseCode::BatchProcessingStopped => ItemAction::Retryable, + ResponseCode::InvalidSyncStateData => ItemAction::RetryDiscardSyncState, + ResponseCode::AccessDenied + | ResponseCode::InvalidIdMalformed + | ResponseCode::MimeContentConversionFailed + | ResponseCode::ImpersonationFailed => ItemAction::Skip, + ResponseCode::AuthenticationRequired => ItemAction::Skip, + ResponseCode::Other(_) => ItemAction::Skip, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn server_busy_with_backoff_is_retryable_with_delay() { + let code = ResponseCode::ServerBusy { + back_off_ms: Some(297749), + }; + match classify_fault(&code) { + FaultDisposition::Retryable { delay: Some(d) } => { + assert_eq!(d, Duration::from_millis(297749)); + } + _ => panic!("expected retryable with delay"), + } + } + + #[test] + fn item_responses_are_classified() { + assert_eq!( + classify_item_response(&ResponseCode::NoError), + ItemAction::Success + ); + assert_eq!( + classify_item_response(&ResponseCode::ItemNotFound), + ItemAction::Vanished + ); + assert_eq!( + classify_item_response(&ResponseCode::AttachmentNotFound), + ItemAction::Vanished + ); + assert_eq!( + classify_item_response(&ResponseCode::AccessDenied), + ItemAction::Skip + ); + assert_eq!( + classify_item_response(&ResponseCode::InvalidSyncStateData), + ItemAction::RetryDiscardSyncState + ); + assert_eq!( + classify_item_response(&ResponseCode::ServerBusy { back_off_ms: None }), + ItemAction::Retryable + ); + } + + #[test] + fn http_status_table() { + assert_eq!(classify_http_status(429), Disposition::Retryable); + assert_eq!(classify_http_status(503), Disposition::Retryable); + assert_eq!(classify_http_status(500), Disposition::Fatal); + assert_eq!(classify_http_status(401), Disposition::Fatal); + } + + #[test] + fn transient_codes_are_retryable_at_item_and_fault_level() { + for code in [ + ResponseCode::InternalServerTransientError, + ResponseCode::MailboxStoreUnavailable, + ResponseCode::ConnectionFailed, + ResponseCode::AdUnavailable, + ResponseCode::BatchProcessingStopped, + ] { + assert_eq!(classify_item_response(&code), ItemAction::Retryable); + assert!(matches!( + classify_fault(&code), + FaultDisposition::Retryable { .. } + )); + } + } + + #[test] + fn mime_content_conversion_failure_skips_item() { + assert_eq!( + classify_item_response(&ResponseCode::MimeContentConversionFailed), + ItemAction::Skip + ); + } + + #[test] + fn other_unknown_codes_skip_at_item_level() { + assert_eq!( + classify_item_response(&ResponseCode::Other("ErrorWhoKnows".to_owned())), + ItemAction::Skip + ); + } +} diff --git a/src/exchange_ews/soap.rs b/src/exchange_ews/soap.rs new file mode 100644 index 0000000..765e239 --- /dev/null +++ b/src/exchange_ews/soap.rs @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::exchange_ews::types::ServerVersion; + +pub const NS_SOAP: &str = "http://schemas.xmlsoap.org/soap/envelope/"; +pub const NS_TYPES: &str = "http://schemas.microsoft.com/exchange/services/2006/types"; +pub const NS_MESSAGES: &str = "http://schemas.microsoft.com/exchange/services/2006/messages"; + +pub const SOAP_ACTION_BASE: &str = "http://schemas.microsoft.com/exchange/services/2006/messages"; + +#[derive(Debug, Clone, Copy)] +pub struct EnvelopeOptions<'a> { + pub version: ServerVersion, + pub impersonated_smtp: Option<&'a str>, +} + +impl Default for EnvelopeOptions<'_> { + fn default() -> Self { + EnvelopeOptions { + version: ServerVersion::Exchange2013Sp1, + impersonated_smtp: None, + } + } +} + +pub fn wrap_envelope(opts: EnvelopeOptions<'_>, body: &str) -> String { + let mut out = String::with_capacity(body.len() + 512); + out.push_str(""); + out.push_str(""); + out.push_str(""); + if let Some(smtp) = opts.impersonated_smtp { + out.push_str(""); + write_escaped(&mut out, smtp); + out.push_str(""); + } + out.push_str(""); + out.push_str(body); + out.push_str(""); + out +} + +pub fn soap_action(operation: &str) -> String { + format!("\"{SOAP_ACTION_BASE}/{operation}\"") +} + +pub fn write_escaped(out: &mut String, s: &str) { + for ch in s.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\'' => out.push_str("'"), + _ => out.push(ch), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn envelope_has_canonical_namespaces() { + let env = wrap_envelope(EnvelopeOptions::default(), ""); + assert!(env.contains("xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"")); + assert!( + env.contains("xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"") + ); + assert!( + env.contains( + "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\"" + ) + ); + assert!(env.contains("")); + assert!(env.contains("")); + } + + #[test] + fn envelope_impersonation_includes_smtp() { + let opts = EnvelopeOptions { + version: ServerVersion::Exchange2016, + impersonated_smtp: Some("alice@contoso.com"), + }; + let env = wrap_envelope(opts, ""); + assert!(env.contains("")); + assert!(env.contains("alice@contoso.com")); + assert!(env.contains("Version=\"Exchange2016\"")); + } + + #[test] + fn soap_action_quotes_operation() { + assert_eq!( + soap_action("FindItem"), + "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"" + ); + } + + #[test] + fn write_escaped_handles_metachars() { + let mut s = String::new(); + write_escaped(&mut s, ""); + assert_eq!(s, "<a&b>"); + } +} diff --git a/src/exchange_ews/types.rs b/src/exchange_ews/types.rs new file mode 100644 index 0000000..f2fb225 --- /dev/null +++ b/src/exchange_ews/types.rs @@ -0,0 +1,314 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::fmt; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] +pub struct ItemId { + pub id: String, + pub change_key: String, +} + +impl ItemId { + pub fn new(id: impl Into, change_key: impl Into) -> ItemId { + ItemId { + id: id.into(), + change_key: change_key.into(), + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] +pub struct FolderId { + pub id: String, + pub change_key: String, +} + +impl FolderId { + pub fn new(id: impl Into, change_key: impl Into) -> FolderId { + FolderId { + id: id.into(), + change_key: change_key.into(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MailboxKind { + Primary, + Archive, + PublicFolders, +} + +impl MailboxKind { + pub fn as_str(self) -> &'static str { + match self { + MailboxKind::Primary => "primary", + MailboxKind::Archive => "archive", + MailboxKind::PublicFolders => "public-folders", + } + } + + pub fn distinguished_root(self) -> DistinguishedFolderId { + match self { + MailboxKind::Primary => DistinguishedFolderId::MsgFolderRoot, + MailboxKind::Archive => DistinguishedFolderId::ArchiveRoot, + MailboxKind::PublicFolders => DistinguishedFolderId::PublicFoldersRoot, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DistinguishedFolderId { + MsgFolderRoot, + ArchiveRoot, + PublicFoldersRoot, + Inbox, + SentItems, + Drafts, + DeletedItems, + JunkEmail, + Outbox, + ConversationHistory, + Archive, +} + +impl DistinguishedFolderId { + pub fn as_str(self) -> &'static str { + match self { + DistinguishedFolderId::MsgFolderRoot => "msgfolderroot", + DistinguishedFolderId::ArchiveRoot => "archiveroot", + DistinguishedFolderId::PublicFoldersRoot => "publicfoldersroot", + DistinguishedFolderId::Inbox => "inbox", + DistinguishedFolderId::SentItems => "sentitems", + DistinguishedFolderId::Drafts => "drafts", + DistinguishedFolderId::DeletedItems => "deleteditems", + DistinguishedFolderId::JunkEmail => "junkemail", + DistinguishedFolderId::Outbox => "outbox", + DistinguishedFolderId::ConversationHistory => "conversationhistory", + DistinguishedFolderId::Archive => "archive", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ServerVersion { + Exchange2007, + Exchange2010, + Exchange2010Sp1, + Exchange2010Sp2, + Exchange2013, + Exchange2013Sp1, + Exchange2016, + Exchange2019, +} + +impl ServerVersion { + pub fn as_str(self) -> &'static str { + match self { + ServerVersion::Exchange2007 => "Exchange2007", + ServerVersion::Exchange2010 => "Exchange2010", + ServerVersion::Exchange2010Sp1 => "Exchange2010_SP1", + ServerVersion::Exchange2010Sp2 => "Exchange2010_SP2", + ServerVersion::Exchange2013 => "Exchange2013", + ServerVersion::Exchange2013Sp1 => "Exchange2013_SP1", + ServerVersion::Exchange2016 => "Exchange2016", + ServerVersion::Exchange2019 => "Exchange2019", + } + } + + pub fn from_build(major: u32, minor: u32) -> ServerVersion { + match (major, minor) { + (8, _) => ServerVersion::Exchange2007, + (14, 0) => ServerVersion::Exchange2010, + (14, 1) => ServerVersion::Exchange2010Sp1, + (14, 2 | 3) => ServerVersion::Exchange2010Sp2, + (15, 0) => ServerVersion::Exchange2013, + (15, 1) => ServerVersion::Exchange2016, + (15, 2) => ServerVersion::Exchange2019, + _ if major >= 15 => ServerVersion::Exchange2019, + _ => ServerVersion::Exchange2016, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FolderClass { + Mail, + Calendar, + Contacts, + Skipped, +} + +impl FolderClass { + pub fn from_ipf(class: &str) -> FolderClass { + if class.eq_ignore_ascii_case("IPF.Note") { + FolderClass::Mail + } else if class.eq_ignore_ascii_case("IPF.Appointment") + || class.starts_with("IPF.Appointment.") + { + FolderClass::Calendar + } else if class.eq_ignore_ascii_case("IPF.Contact") || class.starts_with("IPF.Contact.") { + FolderClass::Contacts + } else { + FolderClass::Skipped + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CalendarItemType { + Single, + RecurringMaster, + Occurrence, + Exception, +} + +impl CalendarItemType { + pub fn parse(value: &str) -> Option { + match value { + "Single" => Some(CalendarItemType::Single), + "RecurringMaster" => Some(CalendarItemType::RecurringMaster), + "Occurrence" => Some(CalendarItemType::Occurrence), + "Exception" => Some(CalendarItemType::Exception), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ResponseCode { + NoError, + ItemNotFound, + AccessDenied, + InvalidIdMalformed, + ServerBusy { back_off_ms: Option }, + TimeoutExpired, + InvalidSyncStateData, + MimeContentConversionFailed, + AttachmentNotFound, + AuthenticationRequired, + ImpersonationFailed, + BatchProcessingStopped, + InternalServerTransientError, + MailboxStoreUnavailable, + ConnectionFailed, + AdUnavailable, + Other(String), +} + +impl ResponseCode { + pub fn parse(code: &str) -> ResponseCode { + match code { + "NoError" => ResponseCode::NoError, + "ErrorItemNotFound" => ResponseCode::ItemNotFound, + "ErrorAccessDenied" => ResponseCode::AccessDenied, + "ErrorInvalidIdMalformed" => ResponseCode::InvalidIdMalformed, + "ErrorServerBusy" => ResponseCode::ServerBusy { back_off_ms: None }, + "ErrorTimeoutExpired" => ResponseCode::TimeoutExpired, + "ErrorInvalidSyncStateData" => ResponseCode::InvalidSyncStateData, + "ErrorMimeContentConversionFailed" => ResponseCode::MimeContentConversionFailed, + "ErrorAttachmentNotFound" => ResponseCode::AttachmentNotFound, + "ErrorAuthenticationRequired" => ResponseCode::AuthenticationRequired, + "ErrorImpersonationFailed" => ResponseCode::ImpersonationFailed, + "ErrorBatchProcessingStopped" => ResponseCode::BatchProcessingStopped, + "ErrorInternalServerTransientError" => ResponseCode::InternalServerTransientError, + "ErrorMailboxStoreUnavailable" => ResponseCode::MailboxStoreUnavailable, + "ErrorConnectionFailed" => ResponseCode::ConnectionFailed, + "ErrorADUnavailable" => ResponseCode::AdUnavailable, + other => ResponseCode::Other(other.to_owned()), + } + } + + pub fn as_wire(&self) -> &str { + match self { + ResponseCode::NoError => "NoError", + ResponseCode::ItemNotFound => "ErrorItemNotFound", + ResponseCode::AccessDenied => "ErrorAccessDenied", + ResponseCode::InvalidIdMalformed => "ErrorInvalidIdMalformed", + ResponseCode::ServerBusy { .. } => "ErrorServerBusy", + ResponseCode::TimeoutExpired => "ErrorTimeoutExpired", + ResponseCode::InvalidSyncStateData => "ErrorInvalidSyncStateData", + ResponseCode::MimeContentConversionFailed => "ErrorMimeContentConversionFailed", + ResponseCode::AttachmentNotFound => "ErrorAttachmentNotFound", + ResponseCode::AuthenticationRequired => "ErrorAuthenticationRequired", + ResponseCode::ImpersonationFailed => "ErrorImpersonationFailed", + ResponseCode::BatchProcessingStopped => "ErrorBatchProcessingStopped", + ResponseCode::InternalServerTransientError => "ErrorInternalServerTransientError", + ResponseCode::MailboxStoreUnavailable => "ErrorMailboxStoreUnavailable", + ResponseCode::ConnectionFailed => "ErrorConnectionFailed", + ResponseCode::AdUnavailable => "ErrorADUnavailable", + ResponseCode::Other(s) => s.as_str(), + } + } +} + +impl fmt::Display for ResponseCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_wire()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn folder_class_mapping() { + assert_eq!(FolderClass::from_ipf("IPF.Note"), FolderClass::Mail); + assert_eq!( + FolderClass::from_ipf("IPF.Appointment"), + FolderClass::Calendar + ); + assert_eq!( + FolderClass::from_ipf("IPF.Appointment.Birthday"), + FolderClass::Calendar + ); + assert_eq!(FolderClass::from_ipf("IPF.Contact"), FolderClass::Contacts); + assert_eq!(FolderClass::from_ipf("IPF.Task"), FolderClass::Skipped); + } + + #[test] + fn server_version_from_build() { + assert_eq!( + ServerVersion::from_build(15, 0), + ServerVersion::Exchange2013 + ); + assert_eq!( + ServerVersion::from_build(15, 1), + ServerVersion::Exchange2016 + ); + assert_eq!( + ServerVersion::from_build(15, 2), + ServerVersion::Exchange2019 + ); + assert_eq!( + ServerVersion::from_build(99, 99), + ServerVersion::Exchange2019 + ); + } + + #[test] + fn response_code_round_trip() { + let codes = [ + "NoError", + "ErrorItemNotFound", + "ErrorAccessDenied", + "ErrorInvalidIdMalformed", + "ErrorServerBusy", + "ErrorTimeoutExpired", + "ErrorInvalidSyncStateData", + "ErrorMimeContentConversionFailed", + "ErrorAttachmentNotFound", + "ErrorAuthenticationRequired", + "ErrorImpersonationFailed", + "ErrorWhatever", + ]; + for c in codes { + assert_eq!(ResponseCode::parse(c).as_wire(), c); + } + } +} diff --git a/src/exchange_ews/xml.rs b/src/exchange_ews/xml.rs new file mode 100644 index 0000000..b7ac5b7 --- /dev/null +++ b/src/exchange_ews/xml.rs @@ -0,0 +1,340 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::exchange_ews::soap::write_escaped; +use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId}; + +#[derive(Debug, Clone, Copy)] +pub enum FolderRef<'a> { + Distinguished(DistinguishedFolderId), + Concrete(&'a FolderId), +} + +#[derive(Debug, Clone, Copy)] +pub enum FolderShape { + IdOnly, +} + +impl FolderShape { + fn as_str(self) -> &'static str { + match self { + FolderShape::IdOnly => "IdOnly", + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Traversal { + Shallow, + Deep, +} + +impl Traversal { + fn as_str(self) -> &'static str { + match self { + Traversal::Shallow => "Shallow", + Traversal::Deep => "Deep", + } + } +} + +pub fn find_folder_body(parent: FolderRef<'_>, traversal: Traversal) -> String { + let mut out = String::with_capacity(512); + out.push_str(""); + out.push_str("Default"); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + write_folder_ref(&mut out, parent); + out.push_str(""); + out +} + +pub fn get_folder_body(folders: &[FolderRef<'_>], shape: FolderShape) -> String { + let mut out = String::with_capacity(256 + folders.len() * 64); + out.push_str(""); + out.push_str(""); + out.push_str(shape.as_str()); + out.push_str(""); + out.push_str(""); + for f in folders { + write_folder_ref(&mut out, *f); + } + out.push_str(""); + out +} + +pub fn find_item_body( + parent: FolderRef<'_>, + traversal: Traversal, + offset: u32, + page_size: u32, +) -> String { + let mut out = String::with_capacity(512); + out.push_str("IdOnly"); + out.push_str(""); + out.push_str(""); + write_folder_ref(&mut out, parent); + out.push_str(""); + out +} + +pub fn sync_folder_items_body(folder: &FolderId, sync_state: &str, max_changes: u32) -> String { + let mut out = String::with_capacity(512); + out.push_str(""); + out.push_str("IdOnly"); + out.push_str(""); + write_folder_id_element(&mut out, folder); + out.push_str(""); + out.push_str(""); + write_escaped(&mut out, sync_state); + out.push_str(""); + out.push_str(""); + out.push_str(&max_changes.to_string()); + out.push_str(""); + out.push_str("NormalItems"); + out.push_str(""); + out +} + +#[derive(Debug, Clone, Copy)] +pub enum ItemShape { + Message, + CalendarItem, + Contact, +} + +pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String { + let mut out = String::with_capacity(256 + ids.len() * 128); + out.push_str(""); + match shape { + ItemShape::Message => { + out.push_str("IdOnly"); + out.push_str("true"); + out.push_str("Best"); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + out.push_str(""); + } + ItemShape::CalendarItem => { + out.push_str("IdOnly"); + out.push_str("Best"); + out.push_str(""); + for f in CALENDAR_FIELDS { + out.push_str(""); + } + out.push_str(""); + } + ItemShape::Contact => { + out.push_str("AllProperties"); + } + } + out.push_str(""); + for id in ids { + write_item_id_element(&mut out, id); + } + out.push_str(""); + out +} + +const CALENDAR_FIELDS: &[&str] = &[ + "calendar:UID", + "calendar:RecurrenceId", + "calendar:Start", + "calendar:End", + "calendar:OriginalStart", + "calendar:IsAllDayEvent", + "calendar:LegacyFreeBusyStatus", + "calendar:Location", + "calendar:IsRecurring", + "calendar:CalendarItemType", + "calendar:Organizer", + "calendar:RequiredAttendees", + "calendar:OptionalAttendees", + "calendar:Recurrence", + "calendar:ModifiedOccurrences", + "calendar:DeletedOccurrences", + "calendar:StartTimeZone", + "calendar:EndTimeZone", + "calendar:Duration", + "item:Subject", + "item:Body", + "item:Categories", + "item:DateTimeCreated", + "item:LastModifiedTime", + "item:ParentFolderId", +]; + +pub fn get_attachment_body(ids: &[&str]) -> String { + let mut out = String::with_capacity(256 + ids.len() * 64); + out.push_str(""); + out.push_str("true"); + out.push_str("Best"); + out.push_str(""); + for id in ids { + out.push_str(""); + } + out.push_str(""); + out +} + +pub fn write_folder_id_element(out: &mut String, folder: &FolderId) { + out.push_str(""); +} + +pub fn write_item_id_element(out: &mut String, id: &ItemId) { + out.push_str(""); +} + +fn write_folder_ref(out: &mut String, parent: FolderRef<'_>) { + match parent { + FolderRef::Distinguished(d) => { + out.push_str(""); + } + FolderRef::Concrete(f) => write_folder_id_element(out, f), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn find_folder_uses_deep_traversal_for_msgfolderroot() { + let body = find_folder_body( + FolderRef::Distinguished(DistinguishedFolderId::MsgFolderRoot), + Traversal::Deep, + ); + assert!(body.contains("Traversal=\"Deep\"")); + assert!(body.contains("")); + assert!(body.contains("folder:FolderClass")); + assert!(body.contains("folder:DisplayName")); + assert!(body.contains("folder:ParentFolderId")); + } + + #[test] + fn find_folder_archive_uses_archiveroot() { + let body = find_folder_body( + FolderRef::Distinguished(DistinguishedFolderId::ArchiveRoot), + Traversal::Deep, + ); + assert!(body.contains("")); + } + + #[test] + fn find_item_paginates_with_offset_and_page_size() { + let folder = FolderId::new("FID", "FCK"); + let body = find_item_body(FolderRef::Concrete(&folder), Traversal::Shallow, 200, 50); + assert!(body.contains("Offset=\"200\"")); + assert!(body.contains("MaxEntriesReturned=\"50\"")); + assert!(body.contains("")); + } + + #[test] + fn get_item_message_shape_requests_mime() { + let ids = vec![ItemId::new("I1", "CK1"), ItemId::new("I2", "")]; + let body = get_item_body(ItemShape::Message, &ids); + assert!(body.contains("true")); + assert!(body.contains("")); + assert!(body.contains("")); + } + + #[test] + fn get_item_calendar_shape_lists_calendar_fields() { + let body = get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")]); + assert!(body.contains("calendar:Recurrence")); + assert!(body.contains("calendar:ModifiedOccurrences")); + assert!(body.contains("calendar:DeletedOccurrences")); + assert!(body.contains("calendar:UID")); + assert!(!body.contains("")); + } + + #[test] + fn get_item_contact_shape_requests_all_properties() { + let body = get_item_body(ItemShape::Contact, &[ItemId::new("X", "")]); + assert!(body.contains("AllProperties")); + } + + #[test] + fn sync_folder_items_body_carries_state_and_max() { + let folder = FolderId::new("FID", "FCK"); + let body = sync_folder_items_body(&folder, "STATE", 512); + assert!(body.contains("")); + assert!(body.contains("STATE")); + assert!(body.contains("512")); + assert!(body.contains("NormalItems")); + } + + #[test] + fn sync_folder_items_empty_state_round_trips() { + let folder = FolderId::new("FID", ""); + let body = sync_folder_items_body(&folder, "", 100); + assert!(body.contains("")); + } + + #[test] + fn get_attachment_lists_ids() { + let body = get_attachment_body(&["AID1", "AID2"]); + assert!(body.contains("")); + assert!(body.contains("")); + assert!(body.contains("true")); + } + + #[test] + fn get_folder_emits_distinguished_ids() { + let body = get_folder_body( + &[ + FolderRef::Distinguished(DistinguishedFolderId::Inbox), + FolderRef::Distinguished(DistinguishedFolderId::SentItems), + ], + FolderShape::IdOnly, + ); + assert!(body.contains("")); + assert!(body.contains("")); + assert!(body.contains("IdOnly")); + } +} diff --git a/src/exchange_graph/api.rs b/src/exchange_graph/api.rs new file mode 100644 index 0000000..a7d8d2f --- /dev/null +++ b/src/exchange_graph/api.rs @@ -0,0 +1,286 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::Value; + +use crate::exchange_graph::client::GraphClient; +use crate::exchange_graph::error::GraphError; +use crate::exchange_graph::types::MailboxKind; + +pub const DEFAULT_API_BASE: &str = "https://graph.microsoft.com/v1.0"; + +pub const PREFER_TIMEZONE_UTC: &str = "outlook.timezone=\"UTC\""; +pub const PREFER_BODY_TEXT: &str = "outlook.body-content-type=\"text\""; +pub const PREFER_BODY_HTML: &str = "outlook.body-content-type=\"html\""; + +#[derive(Debug, Clone)] +pub struct Endpoints { + pub api_base: String, + pub user_path: String, +} + +impl Endpoints { + pub fn for_me(api_base: &str) -> Endpoints { + Endpoints { + api_base: api_base.trim_end_matches('/').to_owned(), + user_path: "/me".to_owned(), + } + } + + pub fn for_user(api_base: &str, user_id: &str) -> Endpoints { + Endpoints { + api_base: api_base.trim_end_matches('/').to_owned(), + user_path: format!("/users/{user_id}"), + } + } + + pub fn me_or_user(&self) -> String { + format!("{}{}", self.api_base, self.user_path) + } + + pub fn mail_folders_root(&self, kind: MailboxKind, top: usize) -> String { + match kind { + MailboxKind::Primary => { + format!( + "{}/mailFolders?$top={top}&includeHiddenFolders=true", + self.me_or_user() + ) + } + MailboxKind::Archive => { + format!( + "{}/mailFolders/archive/childFolders?$top={top}&includeHiddenFolders=true", + self.me_or_user() + ) + } + } + } + + pub fn mail_folder_child_folders(&self, folder_id: &str, top: usize) -> String { + format!( + "{}/mailFolders/{}/childFolders?$top={top}&includeHiddenFolders=true", + self.me_or_user(), + url_escape(folder_id) + ) + } + + pub fn folder_messages_ids(&self, folder_id: &str, top: usize) -> String { + format!( + "{}/mailFolders/{}/messages?$top={top}&$select=id", + self.me_or_user(), + url_escape(folder_id) + ) + } + + pub fn message_mime(&self, message_id: &str) -> String { + format!( + "{}/messages/{}/$value", + self.me_or_user(), + url_escape(message_id) + ) + } + + pub fn well_known_folder(&self, well_known: &str) -> String { + format!( + "{}/mailFolders/{}?$select=id", + self.me_or_user(), + well_known + ) + } + + pub fn calendars(&self, top: usize) -> String { + format!("{}/calendars?$top={top}", self.me_or_user()) + } + + pub fn calendar_events_ids(&self, calendar_id: &str, top: usize) -> String { + format!( + "{}/calendars/{}/events?$top={top}&$select=id,type,seriesMasterId", + self.me_or_user(), + url_escape(calendar_id) + ) + } + + pub fn event(&self, event_id: &str) -> String { + format!("{}/events/{}", self.me_or_user(), url_escape(event_id)) + } + + pub fn contact_folders(&self, top: usize) -> String { + format!("{}/contactFolders?$top={top}", self.me_or_user()) + } + + pub fn contact_folder_children(&self, folder_id: &str, top: usize) -> String { + format!( + "{}/contactFolders/{}/childFolders?$top={top}", + self.me_or_user(), + url_escape(folder_id) + ) + } + + pub fn contact_folder_contacts_ids(&self, folder_id: &str, top: usize) -> String { + format!( + "{}/contactFolders/{}/contacts?$top={top}&$select=id", + self.me_or_user(), + url_escape(folder_id) + ) + } + + pub fn contact(&self, contact_id: &str) -> String { + format!("{}/contacts/{}", self.me_or_user(), url_escape(contact_id)) + } + + pub fn mailbox_settings_timezone(&self) -> String { + format!("{}/mailboxSettings?$select=timeZone", self.me_or_user()) + } + + pub fn me_select_id_upn(&self) -> String { + format!( + "{}?$select=id,userPrincipalName,displayName,mail", + self.me_or_user() + ) + } +} + +fn url_escape(segment: &str) -> String { + let mut out = String::with_capacity(segment.len()); + for b in segment.bytes() { + match b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(b as char); + } + _ => { + use std::fmt::Write; + let _ = write!(out, "%{b:02X}"); + } + } + } + out +} + +pub fn paged_collect( + client: &GraphClient, + initial_url: &str, + prefer: &[&str], + mut on_page: F, +) -> Result<(), GraphError> +where + F: FnMut(&Value) -> Result<(), GraphError>, +{ + let mut url = initial_url.to_owned(); + loop { + let body = client.get_json_with_prefer(&url, prefer)?; + on_page(&body)?; + match body.get("@odata.nextLink").and_then(Value::as_str) { + Some(next) => url = next.to_owned(), + None => return Ok(()), + } + } +} + +pub fn collect_all_ids( + client: &GraphClient, + initial_url: &str, + prefer: &[&str], +) -> Result, GraphError> { + let mut out = Vec::new(); + paged_collect(client, initial_url, prefer, |page| { + if let Some(arr) = page.get("value").and_then(Value::as_array) { + for item in arr { + if let Some(id) = item.get("id").and_then(Value::as_str) { + out.push(id.to_owned()); + } + } + } + Ok(()) + })?; + Ok(out) +} + +pub fn collect_all_values( + client: &GraphClient, + initial_url: &str, + prefer: &[&str], +) -> Result, GraphError> { + let mut out = Vec::new(); + paged_collect(client, initial_url, prefer, |page| { + if let Some(arr) = page.get("value").and_then(Value::as_array) { + for item in arr { + out.push(item.clone()); + } + } + Ok(()) + })?; + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn primary_mail_folders_root_uses_me_with_hidden() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + let url = e.mail_folders_root(MailboxKind::Primary, 100); + assert!(url.starts_with("https://graph.microsoft.com/v1.0/me/mailFolders?")); + assert!(url.contains("includeHiddenFolders=true")); + assert!(url.contains("$top=100")); + } + + #[test] + fn archive_mail_folders_root_uses_archive_child_folders() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + let url = e.mail_folders_root(MailboxKind::Archive, 50); + assert!( + url.starts_with( + "https://graph.microsoft.com/v1.0/me/mailFolders/archive/childFolders?" + ) + ); + assert!(url.contains("$top=50")); + } + + #[test] + fn for_user_replaces_me_segment() { + let e = Endpoints::for_user(DEFAULT_API_BASE, "uid"); + let url = e.mail_folders_root(MailboxKind::Primary, 100); + assert!(url.contains("/users/uid/mailFolders")); + assert!(!url.contains("/me/")); + } + + #[test] + fn top_threads_through_calendar_and_contact_endpoints() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + assert!(e.calendars(75).contains("$top=75")); + assert!(e.contact_folders(75).contains("$top=75")); + assert!(e.contact_folder_children("F", 75).contains("$top=75")); + assert!(e.mail_folder_child_folders("F", 75).contains("$top=75")); + } + + #[test] + fn url_escape_encodes_unsafe_bytes_only() { + assert_eq!(url_escape("AAkAAA="), "AAkAAA%3D"); + assert_eq!(url_escape("plain.id_123-x"), "plain.id_123-x"); + } + + #[test] + fn message_mime_endpoint_uses_value() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + let url = e.message_mime("AAkA=="); + assert!(url.ends_with("/messages/AAkA%3D%3D/$value")); + } + + #[test] + fn well_known_folder_uses_short_name() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + let url = e.well_known_folder("inbox"); + assert!(url.ends_with("/mailFolders/inbox?$select=id")); + } + + #[test] + fn event_ids_endpoint_requests_routing_fields() { + let e = Endpoints::for_me(DEFAULT_API_BASE); + let url = e.calendar_events_ids("CAL", 100); + assert!(url.contains("$select=id,type,seriesMasterId")); + assert!(url.contains("$top=100")); + } +} diff --git a/src/exchange_graph/calendar_map.rs b/src/exchange_graph/calendar_map.rs new file mode 100644 index 0000000..9cfdd0e --- /dev/null +++ b/src/exchange_graph/calendar_map.rs @@ -0,0 +1,735 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value, json}; + +use crate::exchange::tz::windows_to_iana; +use crate::exchange_graph::error::GraphError; +use crate::exchange_graph::recurrence::convert_patterned_recurrence_rule; + +pub fn graph_calendar_color_to_hex(value: &str) -> Option<&'static str> { + match value { + "lightBlue" => Some("#A6CEE3"), + "lightGreen" => Some("#B2DF8A"), + "lightOrange" => Some("#FDBF6F"), + "lightGray" => Some("#CCCCCC"), + "lightYellow" => Some("#FFFF99"), + "lightTeal" => Some("#A0E7E5"), + "lightPink" => Some("#FB9A99"), + "lightBrown" => Some("#B15928"), + "lightRed" => Some("#FB9A99"), + _ => None, + } +} + +pub fn windows_or_iana_to_iana(value: &str) -> Option { + if value.is_empty() { + return None; + } + if let Some(iana) = windows_to_iana(value) { + return Some(iana.to_owned()); + } + if value.contains('/') { + return Some(value.to_owned()); + } + None +} + +#[derive(Debug, Clone)] +pub struct ConvertedEvent { + pub uid: String, + pub data: Value, + pub is_draft: bool, + pub use_default_alerts: bool, + pub series_master_id: Option, + pub event_type: EventType, + pub original_start: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EventType { + SingleInstance, + SeriesMaster, + Occurrence, + Exception, +} + +pub fn classify_event_type(value: &Value) -> EventType { + match value.get("type").and_then(Value::as_str).unwrap_or("") { + "seriesMaster" => EventType::SeriesMaster, + "occurrence" => EventType::Occurrence, + "exception" => EventType::Exception, + _ => EventType::SingleInstance, + } +} + +pub fn convert_event( + graph_event: &Value, + fallback_calendar_tz: Option<&str>, +) -> Result { + let uid = graph_event + .get("iCalUId") + .and_then(Value::as_str) + .or_else(|| graph_event.get("id").and_then(Value::as_str)) + .ok_or_else(|| GraphError::Malformed("event has neither iCalUId nor id".to_owned()))? + .to_owned(); + + let event_type = classify_event_type(graph_event); + let series_master_id = graph_event + .get("seriesMasterId") + .and_then(Value::as_str) + .map(str::to_owned); + let original_start = graph_event + .get("originalStart") + .and_then(Value::as_str) + .map(str::to_owned); + let is_draft = graph_event + .get("isDraft") + .and_then(Value::as_bool) + .unwrap_or(false); + + let mut card = Map::new(); + card.insert("@type".to_owned(), Value::from("Event")); + card.insert("version".to_owned(), Value::from("2.0")); + card.insert("uid".to_owned(), Value::from(uid.clone())); + + if let Some(subject) = graph_event.get("subject").and_then(Value::as_str) + && !subject.is_empty() + { + card.insert("title".to_owned(), Value::from(subject.to_owned())); + } + + if let Some(body) = graph_event.get("body").and_then(Value::as_object) + && let Some(content) = body.get("content").and_then(Value::as_str) + && !content.is_empty() + { + card.insert("description".to_owned(), Value::from(content.to_owned())); + } + + let start_dt = extract_local_datetime(graph_event.get("start")).map(strip_fractional); + let end_dt = extract_local_datetime(graph_event.get("end")).map(strip_fractional); + + if let Some(start) = start_dt.as_deref() { + card.insert("start".to_owned(), Value::from(start.to_owned())); + } + + if let (Some(start), Some(end)) = (start_dt.as_deref(), end_dt.as_deref()) + && let Some(dur) = duration_iso8601(start, end) + { + card.insert("duration".to_owned(), Value::from(dur)); + } + + let event_tz = graph_event + .get("originalStartTimeZone") + .and_then(Value::as_str) + .and_then(windows_or_iana_to_iana) + .or_else(|| { + graph_event + .get("start") + .and_then(|s| s.get("timeZone")) + .and_then(Value::as_str) + .and_then(windows_or_iana_to_iana) + }) + .or_else(|| fallback_calendar_tz.map(str::to_owned)); + if let Some(tz) = event_tz { + card.insert("timeZone".to_owned(), Value::from(tz)); + } + + if let Some(all_day) = graph_event.get("isAllDay").and_then(Value::as_bool) + && all_day + { + card.insert("showWithoutTime".to_owned(), Value::Bool(true)); + } + + if let Some(true) = graph_event.get("isCancelled").and_then(Value::as_bool) { + card.insert("status".to_owned(), Value::from("cancelled")); + } else { + card.insert("status".to_owned(), Value::from("confirmed")); + } + + if let Some(sens) = graph_event.get("sensitivity").and_then(Value::as_str) { + card.insert("privacy".to_owned(), Value::from(privacy_for(sens))); + } + + if let Some(imp) = graph_event.get("importance").and_then(Value::as_str) { + card.insert("priority".to_owned(), Value::from(priority_for(imp))); + } + + if let Some(show) = graph_event.get("showAs").and_then(Value::as_str) { + card.insert( + "freeBusyStatus".to_owned(), + Value::from(free_busy_for(show)), + ); + } + + if let Some(cats) = graph_event.get("categories").and_then(Value::as_array) + && !cats.is_empty() + { + let mut map = Map::new(); + for cat in cats.iter().filter_map(Value::as_str) { + map.insert(cat.to_owned(), Value::Bool(true)); + } + if !map.is_empty() { + card.insert("keywords".to_owned(), Value::Object(map)); + } + } + + if let Some(locs) = graph_event.get("locations").and_then(Value::as_array) + && !locs.is_empty() + { + let mut locations = Map::new(); + let width = pad_width(locs.len()); + for (i, loc) in locs.iter().enumerate() { + let key = format!("loc-{:0width$}", i + 1, width = width); + let mut object = Map::new(); + object.insert("@type".to_owned(), Value::from("Location")); + let display_name = loc + .get("displayName") + .and_then(Value::as_str) + .filter(|s| !s.is_empty()); + let address_str = loc + .get("address") + .and_then(Value::as_object) + .map(|addr| { + ["street", "city", "state", "postalCode", "countryOrRegion"] + .iter() + .filter_map(|f| addr.get(*f).and_then(Value::as_str)) + .filter(|s| !s.is_empty()) + .collect::>() + .join(", ") + }) + .filter(|s| !s.is_empty()); + let name = match (display_name, address_str.as_deref()) { + (Some(dn), Some(addr)) if dn != addr => Some(format!("{dn}, {addr}")), + (Some(dn), _) => Some(dn.to_owned()), + (None, Some(addr)) => Some(addr.to_owned()), + (None, None) => None, + }; + if let Some(n) = name { + object.insert("name".to_owned(), Value::from(n)); + } + locations.insert(key, Value::Object(object)); + } + if !locations.is_empty() { + card.insert("locations".to_owned(), Value::Object(locations)); + } + } + + if let Some(recurrence) = graph_event.get("recurrence") + && !recurrence.is_null() + { + let rule = convert_patterned_recurrence_rule(recurrence)?; + card.insert("recurrenceRule".to_owned(), Value::Object(rule)); + } + + let mut participants: Vec<(String, Value)> = Vec::new(); + if let Some(org) = graph_event + .get("organizer") + .and_then(|o| o.get("emailAddress")) + { + if let Some(addr) = org.get("address").and_then(Value::as_str) + && !addr.is_empty() + { + card.insert( + "organizerCalendarAddress".to_owned(), + Value::from(format!("mailto:{addr}")), + ); + } + let p = build_participant(org, &["owner", "chair"], None, None); + participants.push(("organizer".to_owned(), p)); + } + if let Some(attendees) = graph_event.get("attendees").and_then(Value::as_array) { + let width = pad_width(attendees.len()); + for (i, att) in attendees.iter().enumerate() { + let key = format!("att-{:0width$}", i + 1, width = width); + let Some(email) = att.get("emailAddress") else { + continue; + }; + let role = att + .get("type") + .and_then(Value::as_str) + .map(attendee_type_to_role) + .unwrap_or("required"); + let status = att + .get("status") + .and_then(|s| s.get("response")) + .and_then(Value::as_str) + .and_then(attendee_response_to_status); + let p = build_participant(email, &[role], status, Some(true)); + participants.push((key, p)); + } + } + if !participants.is_empty() { + let map: Map = participants.into_iter().collect(); + card.insert("participants".to_owned(), Value::Object(map)); + } + + if let Some(created) = graph_event.get("createdDateTime").and_then(Value::as_str) { + card.insert( + "created".to_owned(), + Value::from(strip_fractional_utc(created)), + ); + } + if let Some(updated) = graph_event + .get("lastModifiedDateTime") + .and_then(Value::as_str) + { + card.insert( + "updated".to_owned(), + Value::from(strip_fractional_utc(updated)), + ); + } + + let use_default_alerts = false; + if let Some(true) = graph_event.get("isReminderOn").and_then(Value::as_bool) + && let Some(mins) = graph_event + .get("reminderMinutesBeforeStart") + .and_then(Value::as_i64) + { + let trigger = format!("-PT{}M", mins.unsigned_abs()); + let alert = json!({ + "@type": "Alert", + "trigger": { + "@type": "OffsetTrigger", + "relativeTo": "start", + "offset": trigger + } + }); + let mut alerts = Map::new(); + alerts.insert("alert-1".to_owned(), alert); + card.insert("alerts".to_owned(), Value::Object(alerts)); + } + + if let Some(cancels) = graph_event + .get("cancelledOccurrences") + .and_then(Value::as_array) + && !cancels.is_empty() + { + let mut overrides = Map::new(); + for entry in cancels.iter().filter_map(Value::as_str) { + overrides.insert(entry.to_owned(), json!({"excluded": true})); + } + if !overrides.is_empty() { + card.insert("recurrenceOverrides".to_owned(), Value::Object(overrides)); + } + } + + Ok(ConvertedEvent { + uid, + data: Value::Object(card), + is_draft, + use_default_alerts, + series_master_id, + event_type, + original_start, + }) +} + +fn extract_local_datetime(slot: Option<&Value>) -> Option { + let slot = slot?; + let dt = slot.get("dateTime").and_then(Value::as_str)?; + Some(dt.to_owned()) +} + +fn build_participant( + email: &Value, + roles: &[&str], + participation_status: Option<&'static str>, + expect_reply: Option, +) -> Value { + let mut map = Map::new(); + map.insert("@type".to_owned(), Value::from("Participant")); + if let Some(addr) = email.get("address").and_then(Value::as_str) { + map.insert( + "calendarAddress".to_owned(), + Value::from(format!("mailto:{addr}")), + ); + map.insert("email".to_owned(), Value::from(addr.to_owned())); + } + if let Some(name) = email.get("name").and_then(Value::as_str) + && !name.is_empty() + { + map.insert("name".to_owned(), Value::from(name.to_owned())); + } + let role_map: Map = roles + .iter() + .map(|r| ((*r).to_owned(), Value::Bool(true))) + .collect(); + map.insert("roles".to_owned(), Value::Object(role_map)); + if let Some(status) = participation_status { + map.insert( + "participationStatus".to_owned(), + Value::from(status.to_owned()), + ); + } + if let Some(b) = expect_reply { + map.insert("expectReply".to_owned(), Value::Bool(b)); + } + Value::Object(map) +} + +fn attendee_type_to_role(t: &str) -> &'static str { + match t { + "optional" => "optional", + "resource" => "informational", + _ => "required", + } +} + +fn attendee_response_to_status(r: &str) -> Option<&'static str> { + match r { + "accepted" | "organizer" => Some("accepted"), + "declined" => Some("declined"), + "tentativelyAccepted" => Some("tentative"), + "notResponded" | "none" => Some("needs-action"), + _ => None, + } +} + +fn pad_width(n: usize) -> usize { + let mut digits = 1; + let mut k = n; + while k >= 10 { + digits += 1; + k /= 10; + } + digits +} + +fn privacy_for(value: &str) -> &'static str { + match value { + "private" => "private", + "confidential" => "secret", + _ => "public", + } +} + +fn priority_for(value: &str) -> i64 { + match value { + "high" => 1, + "low" => 9, + _ => 5, + } +} + +fn free_busy_for(value: &str) -> &'static str { + match value { + "free" => "free", + _ => "busy", + } +} + +fn duration_iso8601(start: &str, end: &str) -> Option { + let s = parse_naive_seconds(start)?; + let e = parse_naive_seconds(end)?; + if e <= s { + return None; + } + let total = e - s; + let hours = total / 3600; + let minutes = (total % 3600) / 60; + let seconds = total % 60; + let mut out = "PT".to_owned(); + use std::fmt::Write; + if hours > 0 { + let _ = write!(out, "{hours}H"); + } + if minutes > 0 || (hours > 0 && seconds > 0) { + let _ = write!(out, "{minutes}M"); + } + if seconds > 0 || (hours == 0 && minutes == 0) { + let _ = write!(out, "{seconds}S"); + } + Some(out) +} + +fn strip_fractional(s: String) -> String { + if let Some(dot) = s.find('.') { + s[..dot].to_owned() + } else { + s + } +} + +fn strip_fractional_utc(raw: &str) -> String { + let trailing_z = raw.ends_with('Z'); + let trimmed = raw.trim_end_matches('Z'); + let base = match trimmed.find('.') { + Some(dot) => &trimmed[..dot], + None => trimmed, + }; + if trailing_z { + format!("{base}Z") + } else { + base.to_owned() + } +} + +fn parse_naive_seconds(s: &str) -> Option { + let (date, time) = s.split_once('T')?; + let mut date_parts = date.split('-'); + let year: i64 = date_parts.next()?.parse().ok()?; + let month: i64 = date_parts.next()?.parse().ok()?; + let day: i64 = date_parts.next()?.parse().ok()?; + let time = time.split('.').next()?; + let time = time.trim_end_matches('Z'); + let mut time_parts = time.split(':'); + let hh: i64 = time_parts.next()?.parse().ok()?; + let mm: i64 = time_parts.next()?.parse().ok()?; + let ss: i64 = time_parts.next().unwrap_or("0").parse().ok()?; + Some(days_from_civil(year, month, day) * 86400 + hh * 3600 + mm * 60 + ss) +} + +fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { + let y = if m <= 2 { y - 1 } else { y }; + let era = (if y >= 0 { y } else { y - 399 }) / 400; + let yoe = (y - era * 400) as u64; + let m_adj: i64 = if m > 2 { m - 3 } else { m + 9 }; + let doy = ((153 * m_adj + 2) / 5 + d - 1) as u64; + let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + era * 146097 + (doe as i64) - 719468 +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn sample() -> Value { + json!({ + "id": "AAA", + "iCalUId": "uid-1", + "type": "singleInstance", + "subject": "Sync up", + "body": {"contentType": "text", "content": "Agenda"}, + "start": {"dateTime": "2026-05-27T10:00:00.0000000", "timeZone": "UTC"}, + "end": {"dateTime": "2026-05-27T11:00:00.0000000", "timeZone": "UTC"}, + "isAllDay": false, + "isCancelled": false, + "sensitivity": "private", + "importance": "high", + "showAs": "busy", + "categories": ["Red", "Internal"], + "createdDateTime": "2026-05-01T08:00:00Z", + "lastModifiedDateTime": "2026-05-02T08:00:00Z", + "isReminderOn": true, + "reminderMinutesBeforeStart": 15, + "isDraft": false, + "organizer": {"emailAddress": {"name": "Alice", "address": "alice@x.com"}}, + "attendees": [ + {"emailAddress": {"name": "Bob", "address": "bob@x.com"}, "type": "required"} + ] + }) + } + + #[test] + fn classify_event_type_dispatch() { + assert_eq!( + classify_event_type(&json!({"type": "seriesMaster"})), + EventType::SeriesMaster + ); + assert_eq!( + classify_event_type(&json!({"type": "occurrence"})), + EventType::Occurrence + ); + assert_eq!( + classify_event_type(&json!({"type": "exception"})), + EventType::Exception + ); + assert_eq!(classify_event_type(&json!({})), EventType::SingleInstance); + } + + #[test] + fn single_instance_converts_to_event_value() { + let conv = convert_event(&sample(), Some("UTC")).unwrap(); + assert_eq!(conv.uid, "uid-1"); + assert_eq!(conv.data["@type"], "Event"); + assert_eq!(conv.data["title"], "Sync up"); + assert_eq!(conv.data["description"], "Agenda"); + assert_eq!(conv.data["start"], "2026-05-27T10:00:00"); + assert_eq!(conv.data["duration"], "PT1H"); + assert_eq!(conv.data["status"], "confirmed"); + assert_eq!(conv.data["privacy"], "private"); + assert_eq!(conv.data["priority"], 1); + assert_eq!(conv.data["freeBusyStatus"], "busy"); + let kws = conv.data["keywords"].as_object().unwrap(); + assert!(kws.contains_key("Red")); + assert!(kws.contains_key("Internal")); + assert!(conv.data.get("categories").is_none()); + assert_eq!(conv.data["version"], "2.0"); + let alerts = conv.data["alerts"].as_object().unwrap(); + assert_eq!(alerts["alert-1"]["trigger"]["offset"], "-PT15M"); + } + + #[test] + fn cancelled_event_status_is_cancelled() { + let mut v = sample(); + v["isCancelled"] = Value::Bool(true); + let conv = convert_event(&v, None).unwrap(); + assert_eq!(conv.data["status"], "cancelled"); + } + + #[test] + fn all_day_sets_show_without_time() { + let mut v = sample(); + v["isAllDay"] = Value::Bool(true); + let conv = convert_event(&v, None).unwrap(); + assert_eq!(conv.data["showWithoutTime"], true); + } + + #[test] + fn priority_mapping_covers_three_levels() { + let conv = convert_event(&sample(), None).unwrap(); + assert_eq!(conv.data["priority"], 1); + let mut v = sample(); + v["importance"] = Value::from("normal"); + assert_eq!(convert_event(&v, None).unwrap().data["priority"], 5); + v["importance"] = Value::from("low"); + assert_eq!(convert_event(&v, None).unwrap().data["priority"], 9); + } + + #[test] + fn duration_handles_subhour() { + assert_eq!( + duration_iso8601("2026-05-27T10:00:00", "2026-05-27T10:30:00").as_deref(), + Some("PT30M") + ); + } + + #[test] + fn duration_handles_multi_day() { + assert_eq!( + duration_iso8601("2026-05-27T10:00:00", "2026-05-28T11:30:45").as_deref(), + Some("PT25H30M45S") + ); + } + + #[test] + fn windows_tz_fallback_returns_iana() { + assert_eq!( + windows_or_iana_to_iana("Pacific Standard Time").as_deref(), + Some("America/Los_Angeles") + ); + assert_eq!( + windows_or_iana_to_iana("America/New_York").as_deref(), + Some("America/New_York") + ); + assert!(windows_or_iana_to_iana("Made Up Zone").is_none()); + } + + #[test] + fn organizer_and_attendees_become_participants() { + let conv = convert_event(&sample(), None).unwrap(); + let participants = conv.data["participants"].as_object().unwrap(); + assert!(participants.contains_key("organizer")); + assert_eq!(participants["organizer"]["email"], "alice@x.com"); + assert_eq!( + participants["organizer"]["calendarAddress"], + "mailto:alice@x.com" + ); + assert_eq!(participants["organizer"]["roles"]["owner"], true); + assert_eq!(participants["organizer"]["roles"]["chair"], true); + assert!(participants.contains_key("att-1")); + assert_eq!(participants["att-1"]["email"], "bob@x.com"); + assert_eq!(participants["att-1"]["roles"]["required"], true); + } + + #[test] + fn optional_attendee_keeps_role_and_uses_status_when_present() { + let mut v = sample(); + v["attendees"] = json!([ + { + "emailAddress": {"name": "Carol", "address": "carol@x.com"}, + "type": "optional", + "status": {"response": "tentativelyAccepted"} + } + ]); + let conv = convert_event(&v, None).unwrap(); + let p = &conv.data["participants"]["att-1"]; + assert_eq!(p["roles"]["optional"], true); + assert!(p["roles"].get("required").is_none()); + assert_eq!(p["participationStatus"], "tentative"); + } + + #[test] + fn many_attendees_zero_pad_keys_for_stable_sort() { + let mut v = sample(); + let mut atts = Vec::new(); + for i in 1..=11 { + atts.push(json!({ + "emailAddress": {"address": format!("u{i}@x.com")}, + "type": "required" + })); + } + v["attendees"] = Value::Array(atts); + let conv = convert_event(&v, None).unwrap(); + let map = conv.data["participants"].as_object().unwrap(); + assert!(map.contains_key("att-01")); + assert!(map.contains_key("att-11")); + } + + #[test] + fn original_start_time_zone_is_preserved() { + let mut v = sample(); + v["originalStartTimeZone"] = Value::from("Pacific Standard Time"); + v["start"]["timeZone"] = Value::from("UTC"); + let conv = convert_event(&v, None).unwrap(); + assert_eq!(conv.data["timeZone"], "America/Los_Angeles"); + } + + #[test] + fn duration_h_and_s_no_minutes_includes_zero_m() { + assert_eq!( + duration_iso8601("2026-05-27T10:00:00", "2026-05-27T11:00:05").as_deref(), + Some("PT1H0M5S") + ); + } + + #[test] + fn fractional_seconds_are_stripped_from_timestamps() { + let mut v = sample(); + v["createdDateTime"] = Value::from("2026-05-01T08:00:00.0000000Z"); + v["lastModifiedDateTime"] = Value::from("2026-05-02T08:00:00.123Z"); + let conv = convert_event(&v, None).unwrap(); + assert_eq!(conv.data["created"], "2026-05-01T08:00:00Z"); + assert_eq!(conv.data["updated"], "2026-05-02T08:00:00Z"); + } + + #[test] + fn organizer_calendar_address_is_set_alongside_participants() { + let conv = convert_event(&sample(), None).unwrap(); + assert_eq!(conv.data["organizerCalendarAddress"], "mailto:alice@x.com"); + } + + #[test] + fn recurrence_emits_singular_recurrence_rule_object() { + let mut v = sample(); + v["recurrence"] = json!({ + "pattern": {"type": "daily", "interval": 1}, + "range": {"type": "noEnd"} + }); + let conv = convert_event(&v, None).unwrap(); + assert!(conv.data.get("recurrenceRules").is_none()); + let rule = &conv.data["recurrenceRule"]; + assert!(rule.is_object()); + assert_eq!(rule["@type"], "RecurrenceRule"); + assert_eq!(rule["frequency"], "daily"); + } + + #[test] + fn location_with_address_only_uses_name_property() { + let mut v = sample(); + v["locations"] = json!([{ + "address": {"street": "1 Infinite Loop", "city": "Cupertino"} + }]); + let conv = convert_event(&v, None).unwrap(); + let locs = conv.data["locations"].as_object().unwrap(); + let only = locs.values().next().unwrap(); + assert_eq!(only["@type"], "Location"); + assert_eq!(only["name"], "1 Infinite Loop, Cupertino"); + assert!(only.get("description").is_none()); + } +} diff --git a/src/exchange_graph/client.rs b/src/exchange_graph/client.rs new file mode 100644 index 0000000..0c54104 --- /dev/null +++ b/src/exchange_graph/client.rs @@ -0,0 +1,454 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::atomic::{AtomicU8, AtomicU64, Ordering}; +use std::time::Duration; + +use serde_json::Value; +use ureq::Agent; +use ureq::config::{Config, RedirectAuthHeaders}; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::exchange_graph::error::GraphError; +use crate::exchange_graph::retry::{HttpClass, classify_http_status, is_throttled}; +use crate::jmap::http::{RetryPolicy, retry_after_header}; +use crate::jmap::retry::{self, RateLimitState}; +use crate::logging::{LEVEL_BODIES, LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; + +const MAX_BODY: u64 = 256 * 1024 * 1024; +const LONG_RETRY_THRESHOLD: Duration = Duration::from_secs(10); + +#[derive(Debug, Clone, Copy)] +pub enum Accept { + Json, + Text, +} + +impl Accept { + fn header_value(self) -> &'static str { + match self { + Accept::Json => "application/json", + Accept::Text => "text/plain", + } + } +} + +#[derive(Debug, Clone)] +pub struct GraphResponse { + pub status: u16, + pub body: Vec, + pub content_type: Option, +} + +impl GraphResponse { + pub fn as_str(&self) -> Result<&str, GraphError> { + std::str::from_utf8(&self.body) + .map_err(|e| GraphError::Malformed(format!("response is not utf-8: {e}"))) + } + + pub fn json(&self) -> Result { + serde_json::from_slice(&self.body) + .map_err(|e| GraphError::Malformed(format!("response is not valid json: {e}"))) + } +} + +struct Inner { + agent: Agent, + bearer: Mutex, + retry: RetryPolicy, + rate_limit: RateLimitState, + log_level: AtomicU8, + retries_total: AtomicU64, + retry_after_sleeps: AtomicU64, + requests_total: AtomicU64, + user_agent: String, +} + +#[derive(Clone)] +pub struct GraphClient { + inner: Arc, +} + +enum Attempt { + Ok { + status: u16, + body: Vec, + retry_after: Option, + content_type: Option, + }, + Transport(GraphError), +} + +impl GraphClient { + pub fn new(bearer: String, retry: RetryPolicy, allow_invalid_certs: bool) -> GraphClient { + let config: Config = Config::builder() + .http_status_as_error(false) + .redirect_auth_headers(RedirectAuthHeaders::SameHost) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + GraphClient { + inner: Arc::new(Inner { + agent: config.new_agent(), + bearer: Mutex::new(bearer), + retry, + rate_limit: RateLimitState::new(), + log_level: AtomicU8::new(LEVEL_DEFAULT), + retries_total: AtomicU64::new(0), + retry_after_sleeps: AtomicU64::new(0), + requests_total: AtomicU64::new(0), + user_agent: format!("vandelay/{}", env!("CARGO_PKG_VERSION")), + }), + } + } + + pub fn set_bearer(&self, bearer: String) { + let mut g = match self.inner.bearer.lock() { + Ok(g) => g, + Err(p) => p.into_inner(), + }; + *g = bearer; + } + + pub fn set_logger(&self, logger: Logger) { + self.inner + .log_level + .store(logger.level(), Ordering::Relaxed); + } + + fn logger(&self) -> Logger { + Logger::new(self.inner.log_level.load(Ordering::Relaxed)) + } + + fn bearer(&self) -> String { + match self.inner.bearer.lock() { + Ok(g) => g.clone(), + Err(p) => p.into_inner().clone(), + } + } + + fn auth_header(&self) -> String { + format!("Bearer {}", self.bearer()) + } + + pub fn retries_observed(&self) -> u64 { + self.inner.retries_total.load(Ordering::Relaxed) + } + + pub fn retry_after_sleeps(&self) -> u64 { + self.inner.retry_after_sleeps.load(Ordering::Relaxed) + } + + pub fn requests_observed(&self) -> u64 { + self.inner.requests_total.load(Ordering::Relaxed) + } + + pub fn rate_limit(&self) -> &RateLimitState { + &self.inner.rate_limit + } + + pub fn get(&self, url: &str, accept: Accept) -> Result { + self.execute("GET", url, accept, &[]) + } + + pub fn get_with_prefer( + &self, + url: &str, + accept: Accept, + prefer: &[&str], + ) -> Result { + self.execute("GET", url, accept, prefer) + } + + pub fn get_json(&self, url: &str) -> Result { + self.get(url, Accept::Json).and_then(|r| r.json()) + } + + pub fn get_json_with_prefer(&self, url: &str, prefer: &[&str]) -> Result { + self.get_with_prefer(url, Accept::Json, prefer) + .and_then(|r| r.json()) + } + + fn execute( + &self, + method: &str, + url: &str, + accept: Accept, + extra_prefer: &[&str], + ) -> Result { + let policy = self.inner.retry; + let logger = self.logger(); + let mut attempt: u32 = 0; + loop { + self.inner.rate_limit.cooldown().wait(); + self.inner.requests_total.fetch_add(1, Ordering::Relaxed); + let outcome = self.one_attempt(method, url, accept, extra_prefer); + match outcome { + Attempt::Ok { + status, + body, + retry_after, + content_type, + } => { + let class = classify_http_status(status); + match class { + HttpClass::Success => { + self.inner.rate_limit.on_success(); + return Ok(GraphResponse { + status, + body, + content_type, + }); + } + HttpClass::Vanished => { + return Err(GraphError::Vanished); + } + HttpClass::Auth => { + return Err(GraphError::Auth(format!( + "server returned {status}: {}", + truncate(&body) + ))); + } + HttpClass::Fatal => { + return Err(GraphError::HttpStatus { + status, + body: truncate(&body), + }); + } + HttpClass::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(GraphError::RetriesExhausted(format!( + "{method} {url} kept returning http {status}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let delay = if is_throttled(status) { + self.inner.rate_limit.on_throttle(&policy, retry_after) + } else { + match retry_after { + Some(d) => d, + None => retry::backoff_delay(&policy, attempt), + } + }; + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "graph rate-limited (http {status}); waiting {} before retry {}/{} (shared level {})", + format_retry_wait(delay), + attempt, + policy.max_retries, + self.inner.rate_limit.level(), + )); + } + self.log_retry( + &logger, + RetryLog { + method, + url, + attempt, + delay, + status, + body: &body, + }, + ); + std::thread::sleep(delay); + } + } + } + Attempt::Transport(err) => { + if matches!(err, GraphError::Connect(_)) { + return Err(err); + } + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(GraphError::RetriesExhausted(format!( + "{method} {url}: {err}" + ))); + } + let delay = retry::backoff_delay(&policy, attempt); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "transient transport failure ({err}); waiting {} before retry {}/{}", + format_retry_wait(delay), + attempt, + policy.max_retries + )); + } + std::thread::sleep(delay); + } + } + } + } + + fn one_attempt( + &self, + method: &str, + url: &str, + accept: Accept, + extra_prefer: &[&str], + ) -> Attempt { + let mut req = match method { + "GET" => self.inner.agent.get(url), + other => { + return Attempt::Transport(GraphError::Connect(format!( + "unsupported method {other} (graph importer is read-only)" + ))); + } + }; + req = req + .header("Authorization", self.auth_header()) + .header("Accept", accept.header_value()) + .header("User-Agent", self.inner.user_agent.as_str()); + req = req.header("Prefer", "IdType=\"ImmutableId\""); + for value in extra_prefer { + req = req.header("Prefer", *value); + } + match req.call() { + Ok(mut resp) => { + let status = resp.status().as_u16(); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .map(str::to_owned); + match resp.body_mut().with_config().limit(MAX_BODY).read_to_vec() { + Ok(bytes) => Attempt::Ok { + status, + body: bytes, + retry_after, + content_type, + }, + Err(e) => Attempt::Transport(GraphError::Transport(format!( + "reading response body: {e}" + ))), + } + } + Err(e) => Attempt::Transport(map_ureq_error(e)), + } + } + + fn log_retry(&self, logger: &Logger, info: RetryLog<'_>) { + let RetryLog { + method, + url, + attempt, + delay, + status, + body, + } = info; + if logger.enabled(LEVEL_BODIES) { + eprintln!( + "retry {}/{} {} {} after {:?} (http {status}) body={}", + attempt, + self.inner.retry.max_retries, + method, + url, + delay, + truncate(body) + ); + } else if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "retry {}/{} (http {status})", + attempt, self.inner.retry.max_retries + ); + } + } +} + +struct RetryLog<'a> { + method: &'a str, + url: &'a str, + attempt: u32, + delay: Duration, + status: u16, + body: &'a [u8], +} + +fn map_ureq_error(err: ureq::Error) -> GraphError { + match err { + ureq::Error::Io(e) => GraphError::Transport(format!("io: {e}")), + ureq::Error::Timeout(t) => GraphError::Transport(format!("timeout: {t}")), + ureq::Error::HostNotFound => GraphError::Transport("host not found".to_owned()), + ureq::Error::ConnectionFailed => GraphError::Transport("connection failed".to_owned()), + ureq::Error::BodyStalled => GraphError::Transport("body stalled".to_owned()), + ureq::Error::Tls(m) => GraphError::Transport(format!("tls: {m}")), + ureq::Error::TooManyRedirects => GraphError::Connect("too many redirects".to_owned()), + ureq::Error::RedirectFailed => GraphError::Connect("redirect failed".to_owned()), + ureq::Error::TlsRequired => { + GraphError::Connect("server requires TLS but transport is unsecured".to_owned()) + } + other => GraphError::Connect(other.to_string()), + } +} + +fn truncate(body: &[u8]) -> String { + let text = String::from_utf8_lossy(body); + if text.len() <= 512 { + return text.into_owned(); + } + let end = text.floor_char_boundary(512); + format!("{}...", &text[..end]) +} + +fn format_retry_wait(d: Duration) -> String { + let total = d.as_secs(); + if total < 60 { + format!("{total}s") + } else if total < 3600 { + format!("{}m{:02}s", total / 60, total % 60) + } else { + format!( + "{}h{:02}m{:02}s", + total / 3600, + (total % 3600) / 60, + total % 60 + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_construct() { + let c = GraphClient::new("token".to_owned(), RetryPolicy::new(3), false); + assert_eq!(c.retries_observed(), 0); + assert_eq!(c.retry_after_sleeps(), 0); + assert_eq!(c.requests_observed(), 0); + assert_eq!(c.auth_header(), "Bearer token"); + } + + #[test] + fn bearer_can_be_swapped_at_runtime() { + let c = GraphClient::new("old".to_owned(), RetryPolicy::new(0), false); + c.set_bearer("new".to_owned()); + assert_eq!(c.auth_header(), "Bearer new"); + } + + #[test] + fn accept_header_values_are_what_the_spec_requires() { + assert_eq!(Accept::Json.header_value(), "application/json"); + assert_eq!(Accept::Text.header_value(), "text/plain"); + } +} diff --git a/src/exchange_graph/contact_map.rs b/src/exchange_graph/contact_map.rs new file mode 100644 index 0000000..947e2ce --- /dev/null +++ b/src/exchange_graph/contact_map.rs @@ -0,0 +1,586 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value, json}; + +use crate::exchange_graph::error::GraphError; + +#[derive(Debug, Clone)] +pub struct ConvertedContact { + pub uid: String, + pub data: Value, +} + +pub fn convert_contact(graph_contact: &Value) -> Result { + let graph_id = graph_contact + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| GraphError::Malformed("contact has no id".to_owned()))?; + let uid = synthetic_uid(graph_id); + + let mut card = Map::new(); + card.insert("@type".to_owned(), Value::from("Card")); + card.insert("version".to_owned(), Value::from("1.0")); + card.insert("uid".to_owned(), Value::from(uid.clone())); + card.insert("kind".to_owned(), Value::from("individual")); + + let mut name_object = Map::new(); + name_object.insert("@type".to_owned(), Value::from("Name")); + if let Some(display) = graph_contact.get("displayName").and_then(Value::as_str) + && !display.is_empty() + { + name_object.insert("full".to_owned(), Value::from(display.to_owned())); + } + let name_slots = [ + ("title", graph_contact.get("title").and_then(Value::as_str)), + ( + "given", + graph_contact.get("givenName").and_then(Value::as_str), + ), + ( + "given2", + graph_contact.get("middleName").and_then(Value::as_str), + ), + ( + "surname", + graph_contact.get("surname").and_then(Value::as_str), + ), + ( + "generation", + graph_contact.get("generation").and_then(Value::as_str), + ), + ]; + let mut components: Vec = Vec::new(); + let mut slot_index: std::collections::HashMap<&'static str, usize> = + std::collections::HashMap::new(); + for (kind, value) in name_slots { + let Some(v) = value else { continue }; + if v.is_empty() { + continue; + } + slot_index.insert(kind, components.len()); + components.push(json!({"@type": "NameComponent", "kind": kind, "value": v})); + } + if !components.is_empty() { + name_object.insert("components".to_owned(), Value::Array(components)); + } + if !name_object.is_empty() { + card.insert("name".to_owned(), Value::Object(name_object)); + } + + if let Some(nick) = graph_contact.get("nickName").and_then(Value::as_str) + && !nick.is_empty() + { + let mut nicks = Map::new(); + nicks.insert( + "nick-1".to_owned(), + json!({"@type": "Nickname", "name": nick}), + ); + card.insert("nicknames".to_owned(), Value::Object(nicks)); + } + + let mut emails = Map::new(); + if let Some(addrs) = graph_contact + .get("emailAddresses") + .and_then(Value::as_array) + { + for (i, e) in addrs.iter().enumerate() { + let Some(addr) = e.get("address").and_then(Value::as_str) else { + continue; + }; + if addr.is_empty() { + continue; + } + let key = format!("email-{}", i + 1); + let mut entry = Map::new(); + entry.insert("@type".to_owned(), Value::from("EmailAddress")); + entry.insert("address".to_owned(), Value::from(addr.to_owned())); + if i == 0 { + entry.insert("pref".to_owned(), Value::from(1)); + } + emails.insert(key, Value::Object(entry)); + } + } + if !emails.is_empty() { + card.insert("emails".to_owned(), Value::Object(emails)); + } + + let mut phones = Map::new(); + push_phones( + &mut phones, + graph_contact + .get("businessPhones") + .and_then(Value::as_array), + "work", + ); + push_phones( + &mut phones, + graph_contact.get("homePhones").and_then(Value::as_array), + "private", + ); + if let Some(mobile) = graph_contact.get("mobilePhone").and_then(Value::as_str) + && !mobile.is_empty() + { + let key = format!("phone-{}", phones.len() + 1); + phones.insert( + key, + json!({ + "@type": "Phone", + "number": mobile, + "features": {"mobile": true, "voice": true} + }), + ); + } + if !phones.is_empty() { + card.insert("phones".to_owned(), Value::Object(phones)); + } + + let mut addresses = Map::new(); + push_address(&mut addresses, graph_contact.get("homeAddress"), "private"); + push_address(&mut addresses, graph_contact.get("businessAddress"), "work"); + push_address(&mut addresses, graph_contact.get("otherAddress"), "other"); + if !addresses.is_empty() { + card.insert("addresses".to_owned(), Value::Object(addresses)); + } + + let mut organizations = Map::new(); + let has_company = graph_contact + .get("companyName") + .and_then(Value::as_str) + .is_some_and(|s| !s.is_empty()); + let has_dept = graph_contact + .get("department") + .and_then(Value::as_str) + .is_some_and(|s| !s.is_empty()); + let has_office = graph_contact + .get("officeLocation") + .and_then(Value::as_str) + .is_some_and(|s| !s.is_empty()); + let has_title = graph_contact + .get("jobTitle") + .and_then(Value::as_str) + .is_some_and(|s| !s.is_empty()); + if has_company || has_dept || has_office { + let mut org = Map::new(); + org.insert("@type".to_owned(), Value::from("Organization")); + if let Some(name) = graph_contact.get("companyName").and_then(Value::as_str) + && !name.is_empty() + { + org.insert("name".to_owned(), Value::from(name.to_owned())); + } + let mut units: Vec = Vec::new(); + if let Some(dept) = graph_contact.get("department").and_then(Value::as_str) + && !dept.is_empty() + { + units.push(json!({"@type": "OrgUnit", "name": dept})); + } + if let Some(office) = graph_contact.get("officeLocation").and_then(Value::as_str) + && !office.is_empty() + { + units.push(json!({"@type": "OrgUnit", "name": office})); + } + if !units.is_empty() { + org.insert("units".to_owned(), Value::Array(units)); + } + organizations.insert("org-1".to_owned(), Value::Object(org)); + } + if has_title + && let Some(title) = graph_contact.get("jobTitle").and_then(Value::as_str) + && !title.is_empty() + { + let mut titles = Map::new(); + let mut title_obj = Map::new(); + title_obj.insert("@type".to_owned(), Value::from("Title")); + title_obj.insert("name".to_owned(), Value::from(title.to_owned())); + if organizations.contains_key("org-1") { + title_obj.insert("organizationId".to_owned(), Value::from("org-1")); + } + titles.insert("title-1".to_owned(), Value::Object(title_obj)); + card.insert("titles".to_owned(), Value::Object(titles)); + } + let has_org_one = organizations.contains_key("org-1"); + if !organizations.is_empty() { + card.insert("organizations".to_owned(), Value::Object(organizations)); + } + + if let Some(notes) = graph_contact.get("personalNotes").and_then(Value::as_str) + && !notes.is_empty() + { + let mut map = Map::new(); + map.insert("note-1".to_owned(), json!({"@type": "Note", "note": notes})); + card.insert("notes".to_owned(), Value::Object(map)); + } + + let mut localizations = Map::new(); + let mut ja = Map::new(); + if let Some(name) = graph_contact.get("yomiCompanyName").and_then(Value::as_str) + && !name.is_empty() + && has_org_one + { + ja.insert( + "organizations/org-1/name".to_owned(), + Value::from(name.to_owned()), + ); + } + if let Some(name) = graph_contact.get("yomiGivenName").and_then(Value::as_str) + && !name.is_empty() + && let Some(idx) = slot_index.get("given") + { + ja.insert( + format!("name/components/{idx}/value"), + Value::from(name.to_owned()), + ); + } + if let Some(name) = graph_contact.get("yomiSurname").and_then(Value::as_str) + && !name.is_empty() + && let Some(idx) = slot_index.get("surname") + { + ja.insert( + format!("name/components/{idx}/value"), + Value::from(name.to_owned()), + ); + } + if !ja.is_empty() { + localizations.insert("ja".to_owned(), Value::Object(ja)); + } + if !localizations.is_empty() { + card.insert("localizations".to_owned(), Value::Object(localizations)); + } + + if let Some(birthday) = graph_contact.get("birthday").and_then(Value::as_str) + && let Some(date) = parse_partial_date(birthday) + { + let mut anniversaries = Map::new(); + anniversaries.insert( + "anniversary-1".to_owned(), + json!({ + "@type": "Anniversary", + "kind": "birth", + "date": date, + }), + ); + card.insert("anniversaries".to_owned(), Value::Object(anniversaries)); + } + + let mut related = Map::new(); + if let Some(m) = graph_contact.get("manager").and_then(Value::as_str) + && !m.is_empty() + { + related.insert( + m.to_owned(), + json!({"@type": "Relation", "relation": {"co-worker": true}}), + ); + } + if let Some(s) = graph_contact.get("spouseName").and_then(Value::as_str) + && !s.is_empty() + { + related.insert( + s.to_owned(), + json!({"@type": "Relation", "relation": {"spouse": true}}), + ); + } + if let Some(kids) = graph_contact.get("children").and_then(Value::as_array) { + for kid in kids.iter().filter_map(Value::as_str) { + related.insert( + kid.to_owned(), + json!({"@type": "Relation", "relation": {"child": true}}), + ); + } + } + if !related.is_empty() { + card.insert("relatedTo".to_owned(), Value::Object(related)); + } + + if let Some(link) = graph_contact + .get("businessHomePage") + .and_then(Value::as_str) + && !link.is_empty() + { + let mut links = Map::new(); + links.insert( + "link-1".to_owned(), + json!({"@type": "Link", "uri": link, "contexts": {"work": true}}), + ); + card.insert("links".to_owned(), Value::Object(links)); + } + + if let Some(created) = graph_contact.get("createdDateTime").and_then(Value::as_str) { + card.insert("created".to_owned(), Value::from(created.to_owned())); + } + if let Some(updated) = graph_contact + .get("lastModifiedDateTime") + .and_then(Value::as_str) + { + card.insert("updated".to_owned(), Value::from(updated.to_owned())); + } + + Ok(ConvertedContact { + uid, + data: Value::Object(card), + }) +} + +pub fn synthetic_uid(graph_id: &str) -> String { + let hash = blake3::hash(graph_id.as_bytes()); + format!("vandelay-graph-{}", hash.to_hex()) +} + +fn parse_partial_date(raw: &str) -> Option { + let date_part = raw.split('T').next().unwrap_or(raw); + let mut iter = date_part.split('-'); + let year: u64 = iter.next()?.parse().ok()?; + let mut obj = Map::new(); + obj.insert("@type".to_owned(), Value::from("PartialDate")); + obj.insert("year".to_owned(), Value::from(year)); + if let Some(month_str) = iter.next() + && let Ok(month) = month_str.parse::() + && (1..=12).contains(&month) + { + obj.insert("month".to_owned(), Value::from(month)); + if let Some(day_str) = iter.next() + && let Ok(day) = day_str.parse::() + && (1..=31).contains(&day) + { + obj.insert("day".to_owned(), Value::from(day)); + } + } + Some(Value::Object(obj)) +} + +fn push_phones(target: &mut Map, list: Option<&Vec>, context: &str) { + let Some(list) = list else { return }; + for n in list.iter().filter_map(Value::as_str) { + if n.is_empty() { + continue; + } + let key = format!("phone-{}", target.len() + 1); + target.insert( + key, + json!({ + "@type": "Phone", + "number": n, + "contexts": {context: true} + }), + ); + } +} + +fn push_address(target: &mut Map, source: Option<&Value>, context: &str) { + let Some(source) = source else { return }; + let Some(map) = source.as_object() else { + return; + }; + let street = map + .get("street") + .and_then(Value::as_str) + .filter(|s| !s.is_empty()); + let component_fields: Vec<(&str, &str)> = [ + ("city", "locality"), + ("state", "region"), + ("postalCode", "postcode"), + ("countryOrRegion", "country"), + ] + .into_iter() + .filter_map(|(graph_key, jsc_kind)| { + map.get(graph_key) + .and_then(Value::as_str) + .filter(|s| !s.is_empty()) + .map(|s| (jsc_kind, s)) + }) + .collect(); + if street.is_none() && component_fields.is_empty() { + return; + } + let mut address = Map::new(); + address.insert("@type".to_owned(), Value::from("Address")); + address.insert("contexts".to_owned(), json!({context: true})); + if let Some(s) = street { + address.insert("full".to_owned(), Value::from(s.to_owned())); + } + if !component_fields.is_empty() { + let components: Vec = component_fields + .iter() + .map(|(kind, value)| json!({"@type": "AddressComponent", "kind": kind, "value": value})) + .collect(); + address.insert("components".to_owned(), Value::Array(components)); + } + let key = format!("addr-{}", target.len() + 1); + target.insert(key, Value::Object(address)); +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn minimal_contact_yields_card_with_synthetic_uid() { + let c = json!({"id": "GRAPH-1", "displayName": "Alice"}); + let conv = convert_contact(&c).unwrap(); + assert!(conv.uid.starts_with("vandelay-graph-")); + assert_eq!(conv.data["@type"], "Card"); + assert_eq!(conv.data["kind"], "individual"); + assert_eq!(conv.data["name"]["full"], "Alice"); + } + + #[test] + fn synthetic_uid_is_stable_per_graph_id() { + let a = synthetic_uid("AAA"); + let b = synthetic_uid("AAA"); + let c = synthetic_uid("BBB"); + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn emails_map_with_first_as_pref() { + let c = json!({ + "id": "X", + "emailAddresses": [ + {"address": "a@x.com", "name": "Alice"}, + {"address": "b@x.com", "name": "Alice B"} + ] + }); + let conv = convert_contact(&c).unwrap(); + let emails = conv.data["emails"].as_object().unwrap(); + assert_eq!(emails["email-1"]["address"], "a@x.com"); + assert_eq!(emails["email-1"]["pref"], 1); + assert_eq!(emails["email-2"]["address"], "b@x.com"); + assert!(emails["email-2"].get("pref").is_none()); + } + + #[test] + fn phones_carry_context_per_source_field() { + let c = json!({ + "id": "X", + "businessPhones": ["+1-555-2000"], + "homePhones": ["+1-555-1000"], + "mobilePhone": "+1-555-3000" + }); + let conv = convert_contact(&c).unwrap(); + let phones = conv.data["phones"].as_object().unwrap(); + assert_eq!(phones.len(), 3); + let has_work = phones + .values() + .any(|v| v["contexts"]["work"] == Value::Bool(true)); + let has_private = phones + .values() + .any(|v| v["contexts"]["private"] == Value::Bool(true)); + let has_mobile = phones + .values() + .any(|v| v["features"]["mobile"] == Value::Bool(true)); + assert!(has_work && has_private && has_mobile); + } + + #[test] + fn addresses_map_three_contexts() { + let c = json!({ + "id": "X", + "homeAddress": {"city": "Home City"}, + "businessAddress": {"city": "Work City"}, + "otherAddress": {"city": "Other City"} + }); + let conv = convert_contact(&c).unwrap(); + let addresses = conv.data["addresses"].as_object().unwrap(); + assert_eq!(addresses.len(), 3); + } + + #[test] + fn org_and_title_pair_into_separate_jscontact_objects() { + let c = json!({ + "id": "X", + "companyName": "Acme", + "department": "Engineering", + "officeLocation": "Building 9", + "jobTitle": "Architect" + }); + let conv = convert_contact(&c).unwrap(); + let orgs = conv.data["organizations"].as_object().unwrap(); + assert!(orgs.contains_key("org-1")); + assert_eq!(orgs["org-1"]["name"], "Acme"); + let titles = conv.data["titles"].as_object().unwrap(); + assert_eq!(titles["title-1"]["name"], "Architect"); + assert_eq!(titles["title-1"]["organizationId"], "org-1"); + } + + #[test] + fn yomi_fields_populate_japanese_localization() { + let c = json!({ + "id": "X", + "displayName": "Yamada Taro", + "givenName": "Taro", + "surname": "Yamada", + "companyName": "Acme", + "yomiCompanyName": "アクメ", + "yomiGivenName": "タロウ", + "yomiSurname": "ヤマダ" + }); + let conv = convert_contact(&c).unwrap(); + let ja = conv.data["localizations"]["ja"].as_object().unwrap(); + assert_eq!(ja["organizations/org-1/name"], "アクメ"); + } + + #[test] + fn birthday_emits_partialdate_object() { + let c = json!({"id": "X", "birthday": "1980-04-12T00:00:00Z"}); + let conv = convert_contact(&c).unwrap(); + let ann = conv.data["anniversaries"].as_object().unwrap(); + let date = &ann["anniversary-1"]["date"]; + assert_eq!(date["@type"], "PartialDate"); + assert_eq!(date["year"], 1980); + assert_eq!(date["month"], 4); + assert_eq!(date["day"], 12); + assert_eq!(ann["anniversary-1"]["kind"], "birth"); + } + + #[test] + fn mobile_phone_uses_features_not_contexts() { + let c = json!({"id": "X", "mobilePhone": "+1-555-3000"}); + let conv = convert_contact(&c).unwrap(); + let phones = conv.data["phones"].as_object().unwrap(); + let phone = phones.values().next().unwrap(); + assert_eq!(phone["features"]["mobile"], true); + assert!(phone.get("contexts").is_none()); + } + + #[test] + fn generation_maps_to_generation_kind() { + let c = json!({"id": "X", "generation": "Jr."}); + let conv = convert_contact(&c).unwrap(); + let comps = conv.data["name"]["components"].as_array().unwrap(); + assert!( + comps + .iter() + .any(|c| c["kind"] == "generation" && c["value"] == "Jr.") + ); + } + + #[test] + fn only_jobtitle_does_not_emit_empty_organization() { + let c = json!({"id": "X", "jobTitle": "Architect"}); + let conv = convert_contact(&c).unwrap(); + assert!(conv.data.get("organizations").is_none()); + let titles = conv.data["titles"].as_object().unwrap(); + assert_eq!(titles["title-1"]["name"], "Architect"); + assert!(titles["title-1"].get("organizationId").is_none()); + } + + #[test] + fn manager_and_children_become_related_to() { + let c = json!({ + "id": "X", + "manager": "alice@x.com", + "spouseName": "Bob", + "children": ["Kid A", "Kid B"] + }); + let conv = convert_contact(&c).unwrap(); + let related = conv.data["relatedTo"].as_object().unwrap(); + assert!(related.contains_key("alice@x.com")); + assert!(related.contains_key("Bob")); + assert!(related.contains_key("Kid A")); + assert!(related.contains_key("Kid B")); + } +} diff --git a/src/exchange_graph/error.rs b/src/exchange_graph/error.rs new file mode 100644 index 0000000..84a6de2 --- /dev/null +++ b/src/exchange_graph/error.rs @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; + +#[derive(Debug, thiserror::Error)] +pub enum GraphError { + #[error("oauth error: {0}")] + OAuth(String), + + #[error("graph http {status}: {body}")] + HttpStatus { status: u16, body: String }, + + #[error("graph auth failure: {0}")] + Auth(String), + + #[error("graph transport error: {0}")] + Transport(String), + + #[error("graph connect error: {0}")] + Connect(String), + + #[error("retries exhausted: {0}")] + RetriesExhausted(String), + + #[error("malformed graph response: {0}")] + Malformed(String), + + #[error("graph item vanished")] + Vanished, +} + +impl From for Error { + fn from(e: GraphError) -> Self { + match e { + GraphError::OAuth(m) => Error::Connection(format!("oauth: {m}")), + GraphError::Auth(m) => Error::Connection(format!("auth: {m}")), + GraphError::Connect(m) => Error::Connection(m), + GraphError::Transport(m) => Error::Connection(m), + GraphError::RetriesExhausted(m) => Error::Connection(m), + GraphError::HttpStatus { status, body } => { + Error::Connection(format!("http {status}: {body}")) + } + GraphError::Malformed(m) => Error::Partial(format!("malformed: {m}")), + GraphError::Vanished => Error::Partial("graph item vanished".to_owned()), + } + } +} diff --git a/src/exchange_graph/mod.rs b/src/exchange_graph/mod.rs new file mode 100644 index 0000000..cebeef7 --- /dev/null +++ b/src/exchange_graph/mod.rs @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod api; +pub mod calendar_map; +pub mod client; +pub mod contact_map; +pub mod error; +pub mod oauth; +pub mod recurrence; +pub mod retry; +pub mod types; + +pub use client::{GraphClient, GraphResponse}; +pub use error::GraphError; +pub use types::{EventBodyFormat, MailboxKind, ResolvedPrincipal}; diff --git a/src/exchange_graph/oauth.rs b/src/exchange_graph/oauth.rs new file mode 100644 index 0000000..d32f68b --- /dev/null +++ b/src/exchange_graph/oauth.rs @@ -0,0 +1,534 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{self, Write}; +use std::time::{Duration, Instant}; + +use base64::Engine; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use serde_json::Value; +use ureq::config::Config; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::exchange_graph::error::GraphError; + +pub const SCOPES: &str = + "offline_access User.Read Mail.Read MailboxSettings.Read Calendars.Read Contacts.Read"; + +pub fn default_authority(tenant: &str) -> String { + format!("https://login.microsoftonline.com/{tenant}") +} + +pub fn device_code_endpoint(authority: &str) -> String { + format!("{authority}/oauth2/v2.0/devicecode") +} + +pub fn token_endpoint(authority: &str) -> String { + format!("{authority}/oauth2/v2.0/token") +} + +#[derive(Debug, Clone)] +pub enum OAuthFlow { + PreAcquired { + token: String, + }, + DeviceCode { + authority: String, + client_id: String, + }, +} + +#[derive(Debug, Clone, Default)] +pub struct JwtClaims { + pub tenant_id: Option, + pub upn: Option, + pub name: Option, + pub exp: Option, +} + +pub fn decode_jwt_claims(token: &str) -> Option { + let mut parts = token.split('.'); + let _header = parts.next()?; + let payload = parts.next()?; + let bytes = URL_SAFE_NO_PAD + .decode(payload) + .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(payload)) + .or_else(|_| base64::engine::general_purpose::STANDARD.decode(payload)) + .ok()?; + let value: Value = serde_json::from_slice(&bytes).ok()?; + Some(JwtClaims { + tenant_id: value.get("tid").and_then(Value::as_str).map(str::to_owned), + upn: value + .get("upn") + .and_then(Value::as_str) + .or_else(|| value.get("preferred_username").and_then(Value::as_str)) + .map(str::to_owned), + name: value.get("name").and_then(Value::as_str).map(str::to_owned), + exp: value.get("exp").and_then(Value::as_u64), + }) +} + +#[derive(Debug, Clone)] +pub struct AcquiredToken { + pub access_token: String, + pub refresh_token: Option, + pub expires_in: Option, + pub tenant_id: Option, + pub upn: Option, + pub name: Option, +} + +fn build_agent(allow_invalid_certs: bool) -> ureq::Agent { + let config: Config = Config::builder() + .http_status_as_error(false) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + config.new_agent() +} + +pub fn acquire(flow: &OAuthFlow, allow_invalid_certs: bool) -> Result { + match flow { + OAuthFlow::PreAcquired { token } => Ok(token_from_string(token.clone())), + OAuthFlow::DeviceCode { + authority, + client_id, + } => device_code_flow(authority, client_id, allow_invalid_certs), + } +} + +fn token_from_string(token: String) -> AcquiredToken { + let claims = decode_jwt_claims(&token).unwrap_or_default(); + AcquiredToken { + access_token: token, + refresh_token: None, + expires_in: None, + tenant_id: claims.tenant_id, + upn: claims.upn, + name: claims.name, + } +} + +#[derive(Debug, Clone)] +pub struct DeviceCodeResponse { + pub device_code: String, + pub user_code: String, + pub verification_uri: String, + pub interval: u64, + pub expires_in: u64, + pub message: Option, +} + +pub fn parse_device_code_response(json: &Value) -> Result { + let device_code = json + .get("device_code") + .and_then(Value::as_str) + .ok_or_else(|| GraphError::OAuth("device_code missing".to_owned()))? + .to_owned(); + let user_code = json + .get("user_code") + .and_then(Value::as_str) + .unwrap_or("?") + .to_owned(); + let verification_uri = json + .get("verification_uri") + .and_then(Value::as_str) + .unwrap_or("https://microsoft.com/devicelogin") + .to_owned(); + let interval = json.get("interval").and_then(Value::as_u64).unwrap_or(5); + let expires_in = json + .get("expires_in") + .and_then(Value::as_u64) + .unwrap_or(900); + let message = json + .get("message") + .and_then(Value::as_str) + .map(str::to_owned); + Ok(DeviceCodeResponse { + device_code, + user_code, + verification_uri, + interval, + expires_in, + message, + }) +} + +#[derive(Debug, Clone)] +pub enum TokenResponse { + Ok(AcquiredToken), + Pending, + SlowDown, + Expired, + Declined, + BadCode, + Other { error: String, description: String }, +} + +pub fn parse_token_response(status: u16, json: &Value) -> TokenResponse { + if (200..300).contains(&status) { + let Some(access) = json.get("access_token").and_then(Value::as_str) else { + return TokenResponse::Other { + error: "missing_access_token".to_owned(), + description: String::new(), + }; + }; + let claims = decode_jwt_claims(access).unwrap_or_default(); + return TokenResponse::Ok(AcquiredToken { + access_token: access.to_owned(), + refresh_token: json + .get("refresh_token") + .and_then(Value::as_str) + .map(str::to_owned), + expires_in: json.get("expires_in").and_then(Value::as_u64), + tenant_id: claims.tenant_id, + upn: claims.upn, + name: claims.name, + }); + } + let error = json + .get("error") + .and_then(Value::as_str) + .unwrap_or("") + .to_owned(); + let description = json + .get("error_description") + .and_then(Value::as_str) + .unwrap_or("") + .to_owned(); + match error.as_str() { + "authorization_pending" => TokenResponse::Pending, + "slow_down" => TokenResponse::SlowDown, + "expired_token" => TokenResponse::Expired, + "authorization_declined" => TokenResponse::Declined, + "bad_verification_code" => TokenResponse::BadCode, + _ => TokenResponse::Other { error, description }, + } +} + +fn device_code_flow( + authority: &str, + client_id: &str, + allow_invalid_certs: bool, +) -> Result { + let agent = build_agent(allow_invalid_certs); + let body = form_encode(&[("client_id", client_id), ("scope", SCOPES)]); + let endpoint = device_code_endpoint(authority); + let mut resp = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| GraphError::OAuth(format!("device code endpoint: {e}")))?; + let json: Value = resp + .body_mut() + .read_json() + .map_err(|e| GraphError::OAuth(format!("device code body: {e}")))?; + let response = parse_device_code_response(&json)?; + if let Some(m) = response.message.as_deref() { + eprintln!("{m}"); + } else { + eprintln!( + "To sign in, open {} and enter the code {}", + response.verification_uri, response.user_code + ); + } + let _ = io::stdout().flush(); + let token_endpoint = token_endpoint(authority); + let deadline = Instant::now() + Duration::from_secs(response.expires_in); + let mut delay = Duration::from_secs(response.interval); + loop { + std::thread::sleep(delay); + if Instant::now() >= deadline { + return Err(GraphError::OAuth( + "device code expired before user completed sign-in".to_owned(), + )); + } + let body = form_encode(&[ + ("client_id", client_id), + ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"), + ("device_code", response.device_code.as_str()), + ]); + let mut resp = agent + .post(&token_endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()) + .map_err(|e| GraphError::OAuth(format!("device code polling: {e}")))?; + let status = resp.status().as_u16(); + let payload: Value = resp.body_mut().read_json().unwrap_or(Value::Null); + match parse_token_response(status, &payload) { + TokenResponse::Ok(t) => return Ok(t), + TokenResponse::Pending => continue, + TokenResponse::SlowDown => { + delay = delay.saturating_add(Duration::from_secs(5)); + } + TokenResponse::Expired => { + return Err(GraphError::OAuth( + "device code expired; restart the import".to_owned(), + )); + } + TokenResponse::Declined => { + return Err(GraphError::OAuth( + "the user declined the device-code sign-in request".to_owned(), + )); + } + TokenResponse::BadCode => { + return Err(GraphError::OAuth("device code rejected".to_owned())); + } + TokenResponse::Other { error, description } => { + return Err(GraphError::OAuth(format!( + "device code polling: {error}: {description}" + ))); + } + } + } +} + +pub fn refresh_access_token( + authority: &str, + client_id: &str, + refresh_token: &str, + allow_invalid_certs: bool, +) -> Result { + let agent = build_agent(allow_invalid_certs); + let body = form_encode(&[ + ("client_id", client_id), + ("grant_type", "refresh_token"), + ("refresh_token", refresh_token), + ("scope", SCOPES), + ]); + let endpoint = token_endpoint(authority); + let mut delay = Duration::from_millis(500); + let mut attempts: u32 = 0; + loop { + attempts += 1; + let send = agent + .post(&endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .send(body.as_bytes()); + let mut resp = match send { + Ok(r) => r, + Err(e) if attempts < 4 => { + std::thread::sleep(delay); + delay = (delay * 2).min(Duration::from_secs(8)); + eprintln!("graph token refresh transport error: {e}; retrying"); + continue; + } + Err(e) => return Err(GraphError::OAuth(format!("refresh endpoint: {e}"))), + }; + let status = resp.status().as_u16(); + let payload: Value = resp.body_mut().read_json().unwrap_or(Value::Null); + if matches!(status, 500..=599) && attempts < 4 { + std::thread::sleep(delay); + delay = (delay * 2).min(Duration::from_secs(8)); + continue; + } + return match parse_token_response(status, &payload) { + TokenResponse::Ok(t) => Ok(t), + other => Err(GraphError::OAuth(format!( + "refresh failed (http {status}): {other:?}" + ))), + }; + } +} + +fn form_encode(pairs: &[(&str, &str)]) -> String { + let mut out = String::new(); + for (i, (k, v)) in pairs.iter().enumerate() { + if i > 0 { + out.push('&'); + } + out.push_str(&urlencode(k)); + out.push('='); + out.push_str(&urlencode(v)); + } + out +} + +fn urlencode(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for b in s.bytes() { + match b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(b as char); + } + _ => { + use std::fmt::Write; + let _ = write!(out, "%{b:02X}"); + } + } + } + out +} + +pub fn run_device_code_polling_against( + authority: &str, + client_id: &str, + response: &DeviceCodeResponse, + mut step: F, +) -> Result +where + F: FnMut(&str, &str) -> Result<(u16, Value), GraphError>, +{ + let token_endpoint = token_endpoint(authority); + let deadline = Instant::now() + Duration::from_secs(response.expires_in.max(1)); + let mut delay = Duration::from_millis(20); + loop { + if Instant::now() >= deadline { + return Err(GraphError::OAuth( + "device code expired before user completed sign-in".to_owned(), + )); + } + std::thread::sleep(delay); + let body = form_encode(&[ + ("client_id", client_id), + ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"), + ("device_code", response.device_code.as_str()), + ]); + let (status, payload) = step(&token_endpoint, &body)?; + match parse_token_response(status, &payload) { + TokenResponse::Ok(t) => return Ok(t), + TokenResponse::Pending => continue, + TokenResponse::SlowDown => { + delay = delay.saturating_add(Duration::from_millis(20)); + } + TokenResponse::Expired => { + return Err(GraphError::OAuth("device code expired".to_owned())); + } + TokenResponse::Declined => { + return Err(GraphError::OAuth("device code declined by user".to_owned())); + } + TokenResponse::BadCode => { + return Err(GraphError::OAuth("device code rejected".to_owned())); + } + TokenResponse::Other { error, description } => { + return Err(GraphError::OAuth(format!("{error}: {description}"))); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_jwt(tid: &str, upn: &str, exp: u64) -> String { + let header = URL_SAFE_NO_PAD.encode(b"{\"alg\":\"none\"}"); + let claims = format!(r#"{{"tid":"{tid}","upn":"{upn}","exp":{exp}}}"#); + let payload = URL_SAFE_NO_PAD.encode(claims.as_bytes()); + format!("{header}.{payload}.") + } + + #[test] + fn jwt_claim_extraction() { + let token = make_jwt("t-1", "alice@x.com", 9999999999); + let claims = decode_jwt_claims(&token).unwrap(); + assert_eq!(claims.tenant_id.as_deref(), Some("t-1")); + assert_eq!(claims.upn.as_deref(), Some("alice@x.com")); + assert_eq!(claims.exp, Some(9999999999)); + } + + #[test] + fn malformed_token_returns_none() { + assert!(decode_jwt_claims("garbage").is_none()); + } + + #[test] + fn device_code_response_parses_required_fields() { + let json = serde_json::json!({ + "device_code": "ABCDEF", + "user_code": "XXX-YYY", + "verification_uri": "https://microsoft.com/devicelogin", + "interval": 5, + "expires_in": 900, + "message": "go sign in" + }); + let r = parse_device_code_response(&json).unwrap(); + assert_eq!(r.device_code, "ABCDEF"); + assert_eq!(r.user_code, "XXX-YYY"); + assert_eq!(r.interval, 5); + assert_eq!(r.expires_in, 900); + } + + #[test] + fn token_response_pending() { + let json = serde_json::json!({ + "error": "authorization_pending", + "error_description": "user has not yet completed" + }); + assert!(matches!( + parse_token_response(400, &json), + TokenResponse::Pending + )); + } + + #[test] + fn token_response_declined() { + let json = serde_json::json!({"error": "authorization_declined"}); + assert!(matches!( + parse_token_response(400, &json), + TokenResponse::Declined + )); + } + + #[test] + fn token_response_bad_code() { + let json = serde_json::json!({"error": "bad_verification_code"}); + assert!(matches!( + parse_token_response(400, &json), + TokenResponse::BadCode + )); + } + + #[test] + fn token_response_expired() { + let json = serde_json::json!({"error": "expired_token"}); + assert!(matches!( + parse_token_response(400, &json), + TokenResponse::Expired + )); + } + + #[test] + fn token_response_success_includes_refresh() { + let token = make_jwt("t-2", "bob@x.com", 9999999999); + let json = serde_json::json!({ + "access_token": token, + "refresh_token": "REFRESH", + "expires_in": 3599 + }); + match parse_token_response(200, &json) { + TokenResponse::Ok(t) => { + assert!(t.access_token.starts_with("eyJhbGciOiJub25lIn0")); + assert_eq!(t.refresh_token.as_deref(), Some("REFRESH")); + assert_eq!(t.expires_in, Some(3599)); + assert_eq!(t.upn.as_deref(), Some("bob@x.com")); + } + other => panic!("expected Ok, got {other:?}"), + } + } + + #[test] + fn urlencode_escapes_special_chars() { + assert_eq!(urlencode("a b/c"), "a%20b%2Fc"); + assert_eq!(urlencode("plain.123_-"), "plain.123_-"); + } + + #[test] + fn default_authority_uses_login_microsoftonline_com() { + assert_eq!( + default_authority("common"), + "https://login.microsoftonline.com/common" + ); + assert_eq!( + default_authority("contoso.onmicrosoft.com"), + "https://login.microsoftonline.com/contoso.onmicrosoft.com" + ); + } +} diff --git a/src/exchange_graph/recurrence.rs b/src/exchange_graph/recurrence.rs new file mode 100644 index 0000000..2c44dd7 --- /dev/null +++ b/src/exchange_graph/recurrence.rs @@ -0,0 +1,277 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Value, json}; + +use crate::exchange_graph::error::GraphError; + +pub fn convert_patterned_recurrence(pr: &Value) -> Result { + Ok(Value::Object(convert_patterned_recurrence_rule(pr)?)) +} + +pub fn convert_patterned_recurrence_rule( + pr: &Value, +) -> Result, GraphError> { + let pattern = pr + .get("pattern") + .ok_or_else(|| GraphError::Malformed("recurrence.pattern missing".to_owned()))?; + let range = pr + .get("range") + .ok_or_else(|| GraphError::Malformed("recurrence.range missing".to_owned()))?; + + let mut rule = serde_json::Map::new(); + rule.insert("@type".to_owned(), Value::from("RecurrenceRule")); + rule.insert("frequency".to_owned(), Value::from(frequency_for(pattern))); + + if let Some(interval) = pattern.get("interval").and_then(Value::as_u64) + && interval != 1 + { + rule.insert("interval".to_owned(), Value::from(interval)); + } + + if let Some(first_day) = pattern.get("firstDayOfWeek").and_then(Value::as_str) + && let Some(short) = day_short(first_day) + { + rule.insert("firstDayOfWeek".to_owned(), Value::from(short)); + } + + if let Some(days) = pattern.get("daysOfWeek").and_then(Value::as_array) { + let by_day: Vec = days + .iter() + .filter_map(Value::as_str) + .filter_map(day_short) + .map(|d| json!({"@type": "NDay", "day": d})) + .collect(); + if !by_day.is_empty() { + rule.insert("byDay".to_owned(), Value::Array(by_day)); + } + } + + if let Some(dom) = pattern.get("dayOfMonth").and_then(Value::as_i64) + && dom != 0 + { + rule.insert( + "byMonthDay".to_owned(), + Value::Array(vec![Value::from(dom)]), + ); + } + + if let Some(month) = pattern.get("month").and_then(Value::as_i64) + && month != 0 + { + rule.insert( + "byMonth".to_owned(), + Value::Array(vec![Value::String(month.to_string())]), + ); + } + + if let Some(index) = pattern.get("index").and_then(Value::as_str) + && let Some(setpos) = set_position_for(index) + { + rule.insert( + "bySetPosition".to_owned(), + Value::Array(vec![Value::from(setpos)]), + ); + } + + let range_type = range.get("type").and_then(Value::as_str).unwrap_or("noEnd"); + match range_type { + "endDate" => { + if let Some(end) = range.get("endDate").and_then(Value::as_str) { + let local = if end.contains('T') { + end.to_owned() + } else { + format!("{end}T23:59:59") + }; + rule.insert("until".to_owned(), Value::from(local)); + } + } + "numbered" => { + if let Some(count) = range.get("numberOfOccurrences").and_then(Value::as_u64) { + rule.insert("count".to_owned(), Value::from(count)); + } + } + _ => {} + } + + Ok(rule) +} + +fn frequency_for(pattern: &Value) -> &'static str { + match pattern.get("type").and_then(Value::as_str).unwrap_or("") { + "daily" => "daily", + "weekly" => "weekly", + "absoluteMonthly" | "relativeMonthly" => "monthly", + "absoluteYearly" | "relativeYearly" => "yearly", + _ => "daily", + } +} + +fn day_short(name: &str) -> Option<&'static str> { + match name.to_ascii_lowercase().as_str() { + "monday" => Some("mo"), + "tuesday" => Some("tu"), + "wednesday" => Some("we"), + "thursday" => Some("th"), + "friday" => Some("fr"), + "saturday" => Some("sa"), + "sunday" => Some("su"), + _ => None, + } +} + +fn set_position_for(index: &str) -> Option { + match index.to_ascii_lowercase().as_str() { + "first" => Some(1), + "second" => Some(2), + "third" => Some(3), + "fourth" => Some(4), + "last" => Some(-1), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn daily_with_interval() { + let pr = json!({ + "pattern": {"type": "daily", "interval": 3}, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "daily"); + assert_eq!(rule["interval"], 3); + assert!(rule.get("until").is_none()); + assert!(rule.get("count").is_none()); + } + + #[test] + fn weekly_with_days_of_week() { + let pr = json!({ + "pattern": { + "type": "weekly", + "interval": 1, + "daysOfWeek": ["monday", "wednesday", "friday"], + "firstDayOfWeek": "monday" + }, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "weekly"); + assert_eq!(rule["firstDayOfWeek"], "mo"); + let by_day = rule["byDay"].as_array().unwrap(); + assert_eq!(by_day.len(), 3); + assert_eq!(by_day[0]["day"], "mo"); + assert_eq!(by_day[1]["day"], "we"); + assert_eq!(by_day[2]["day"], "fr"); + } + + #[test] + fn absolute_monthly_uses_by_month_day() { + let pr = json!({ + "pattern": {"type": "absoluteMonthly", "interval": 1, "dayOfMonth": 15}, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "monthly"); + assert_eq!(rule["byMonthDay"][0], 15); + } + + #[test] + fn relative_monthly_emits_by_set_position() { + let pr = json!({ + "pattern": { + "type": "relativeMonthly", + "interval": 1, + "daysOfWeek": ["thursday"], + "index": "third" + }, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "monthly"); + assert_eq!(rule["bySetPosition"][0], 3); + assert_eq!(rule["byDay"][0]["day"], "th"); + } + + #[test] + fn absolute_yearly_with_month_and_day() { + let pr = json!({ + "pattern": {"type": "absoluteYearly", "interval": 1, "month": 7, "dayOfMonth": 4}, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "yearly"); + assert_eq!(rule["byMonth"][0], "7"); + assert_eq!(rule["byMonthDay"][0], 4); + } + + #[test] + fn relative_yearly_uses_setpos_and_byday() { + let pr = json!({ + "pattern": { + "type": "relativeYearly", "interval": 1, "daysOfWeek": ["monday"], + "index": "last", "month": 5 + }, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + let rule = &out; + assert_eq!(rule["frequency"], "yearly"); + assert_eq!(rule["bySetPosition"][0], -1); + assert_eq!(rule["byMonth"][0], "5"); + } + + #[test] + fn range_end_date_maps_to_until_local_datetime() { + let pr = json!({ + "pattern": {"type": "daily", "interval": 1}, + "range": {"type": "endDate", "endDate": "2026-12-31"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["until"], "2026-12-31T23:59:59"); + } + + #[test] + fn range_end_datetime_preserved() { + let pr = json!({ + "pattern": {"type": "daily", "interval": 1}, + "range": {"type": "endDate", "endDate": "2026-12-31T23:59:59"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["until"], "2026-12-31T23:59:59"); + } + + #[test] + fn range_numbered_maps_to_count() { + let pr = json!({ + "pattern": {"type": "weekly", "interval": 1, "daysOfWeek": ["monday"]}, + "range": {"type": "numbered", "numberOfOccurrences": 10} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["count"], 10); + } + + #[test] + fn no_end_range_omits_until_and_count() { + let pr = json!({ + "pattern": {"type": "daily", "interval": 1}, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert!(out.get("until").is_none()); + assert!(out.get("count").is_none()); + } +} diff --git a/src/exchange_graph/retry.rs b/src/exchange_graph/retry.rs new file mode 100644 index 0000000..c9b69f9 --- /dev/null +++ b/src/exchange_graph/retry.rs @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HttpClass { + Success, + Retryable, + Auth, + Vanished, + Fatal, +} + +pub fn classify_http_status(status: u16) -> HttpClass { + match status { + 200..=299 => HttpClass::Success, + 401 => HttpClass::Auth, + 403 => HttpClass::Auth, + 404 | 410 => HttpClass::Vanished, + 429 | 500 | 502 | 503 | 504 => HttpClass::Retryable, + _ => HttpClass::Fatal, + } +} + +pub fn is_throttled(status: u16) -> bool { + matches!(status, 429 | 503) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rate_limit_status_is_retryable() { + assert_eq!(classify_http_status(429), HttpClass::Retryable); + assert!(is_throttled(429)); + } + + #[test] + fn service_unavailable_is_retryable_and_throttle_marked() { + assert_eq!(classify_http_status(503), HttpClass::Retryable); + assert!(is_throttled(503)); + } + + #[test] + fn bad_gateway_and_timeout_retry() { + assert_eq!(classify_http_status(502), HttpClass::Retryable); + assert_eq!(classify_http_status(504), HttpClass::Retryable); + } + + #[test] + fn insufficient_storage_is_per_item_fatal() { + assert_eq!(classify_http_status(507), HttpClass::Fatal); + } + + #[test] + fn auth_failure_is_marked_distinctly() { + assert_eq!(classify_http_status(401), HttpClass::Auth); + assert_eq!(classify_http_status(403), HttpClass::Auth); + } + + #[test] + fn not_found_and_gone_are_vanished() { + assert_eq!(classify_http_status(404), HttpClass::Vanished); + assert_eq!(classify_http_status(410), HttpClass::Vanished); + } + + #[test] + fn unknown_4xx_is_fatal() { + assert_eq!(classify_http_status(400), HttpClass::Fatal); + assert_eq!(classify_http_status(405), HttpClass::Fatal); + assert_eq!(classify_http_status(408), HttpClass::Fatal); + assert_eq!(classify_http_status(422), HttpClass::Fatal); + assert_eq!(classify_http_status(423), HttpClass::Fatal); + } +} diff --git a/src/exchange_graph/types.rs b/src/exchange_graph/types.rs new file mode 100644 index 0000000..a0210c2 --- /dev/null +++ b/src/exchange_graph/types.rs @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MailboxKind { + Primary, + Archive, +} + +impl MailboxKind { + pub fn parse(value: &str) -> Result { + match value { + "primary" => Ok(MailboxKind::Primary), + "archive" => Ok(MailboxKind::Archive), + "public-folders" => Err(Error::Usage( + "Microsoft Graph does not expose public folders. Run `vandelay import exchange-ews --mailbox-kind public-folders` instead.".to_owned(), + )), + other => Err(Error::Usage(format!( + "--mailbox-kind must be primary | archive, got {other:?}" + ))), + } + } + + pub fn as_str(self) -> &'static str { + match self { + MailboxKind::Primary => "primary", + MailboxKind::Archive => "archive", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EventBodyFormat { + Text, + Html, +} + +impl EventBodyFormat { + pub fn parse(value: &str) -> Result { + match value { + "text" => Ok(EventBodyFormat::Text), + "html" => Ok(EventBodyFormat::Html), + other => Err(Error::Usage(format!( + "--event-body-format must be text | html, got {other:?}" + ))), + } + } + + pub fn prefer_value(self) -> &'static str { + match self { + EventBodyFormat::Text => "outlook.body-content-type=\"text\"", + EventBodyFormat::Html => "outlook.body-content-type=\"html\"", + } + } +} + +#[derive(Debug, Clone)] +pub struct ResolvedPrincipal { + pub id: String, + pub user_principal_name: String, +} + +pub fn synthetic_account_id(directory_id: &str, kind: MailboxKind) -> String { + match kind { + MailboxKind::Primary => directory_id.to_owned(), + MailboxKind::Archive => format!("{directory_id}#archive"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_primary_and_archive() { + assert_eq!(MailboxKind::parse("primary").unwrap(), MailboxKind::Primary); + assert_eq!(MailboxKind::parse("archive").unwrap(), MailboxKind::Archive); + } + + #[test] + fn public_folders_is_redirected_to_ews() { + let err = MailboxKind::parse("public-folders").unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("public folders")); + assert!(msg.contains("exchange-ews")); + } + + #[test] + fn synthetic_id_matches_ews_pattern() { + assert_eq!( + synthetic_account_id("u-uuid", MailboxKind::Primary), + "u-uuid" + ); + assert_eq!( + synthetic_account_id("u-uuid", MailboxKind::Archive), + "u-uuid#archive" + ); + } + + #[test] + fn body_format_prefer_value() { + assert_eq!( + EventBodyFormat::Text.prefer_value(), + "outlook.body-content-type=\"text\"" + ); + assert_eq!( + EventBodyFormat::Html.prefer_value(), + "outlook.body-content-type=\"html\"" + ); + } +} diff --git a/src/imap/automap.rs b/src/imap/automap.rs new file mode 100644 index 0000000..52d1a94 --- /dev/null +++ b/src/imap/automap.rs @@ -0,0 +1,236 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub fn role_from_special_use(attributes: &[String]) -> Option<&'static str> { + for attr in attributes { + match attr.to_ascii_lowercase().as_str() { + "\\all" => return Some("all"), + "\\archive" => return Some("archive"), + "\\drafts" => return Some("drafts"), + "\\flagged" => return Some("flagged"), + "\\important" => return Some("important"), + "\\junk" => return Some("junk"), + "\\sent" => return Some("sent"), + "\\trash" => return Some("trash"), + _ => {} + } + } + None +} + +pub fn role_for_folder( + folder_name: &str, + attributes: &[String], + namespace_prefix: &str, + automap_enabled: bool, +) -> Option<&'static str> { + if folder_name.eq_ignore_ascii_case("INBOX") { + return Some("inbox"); + } + if let Some(r) = role_from_special_use(attributes) { + return Some(r); + } + if !automap_enabled { + return None; + } + let stripped = strip_prefix_case_insensitive(folder_name, namespace_prefix); + let leaf = leaf_component(stripped); + role_from_name(stripped, leaf) +} + +fn role_from_name(full: &str, leaf: &str) -> Option<&'static str> { + for (role, patterns) in ROLE_PATTERNS { + for p in *patterns { + if leaf.eq_ignore_ascii_case(p) || full.eq_ignore_ascii_case(p) { + return Some(role); + } + } + } + None +} + +fn strip_prefix_case_insensitive<'a>(name: &'a str, prefix: &str) -> &'a str { + if prefix.is_empty() { + return name; + } + if name.len() < prefix.len() { + return name; + } + if name[..prefix.len()].eq_ignore_ascii_case(prefix) { + &name[prefix.len()..] + } else { + name + } +} + +fn leaf_component(name: &str) -> &str { + if let Some(idx) = name.rfind(['/', '.']) { + &name[idx + 1..] + } else { + name + } +} + +const ROLE_PATTERNS: &[(&str, &[&str])] = &[ + ( + "sent", + &[ + "Sent", + "Sent Mail", + "Sent Items", + "Sent Messages", + "Sent-Mail", + "[Gmail]/Sent Mail", + "[Gmail]/Sent", + ], + ), + ( + "drafts", + &["Drafts", "Draft", "Brouillons", "[Gmail]/Drafts"], + ), + ( + "trash", + &[ + "Trash", + "Deleted", + "Deleted Items", + "Deleted Messages", + "[Gmail]/Trash", + "[Gmail]/Bin", + ], + ), + ( + "junk", + &["Junk", "Spam", "Junk E-mail", "Junk Email", "[Gmail]/Spam"], + ), + ("all", &["[Gmail]/All Mail"]), + ("archive", &["Archive", "Archives"]), +]; + +#[cfg(test)] +mod tests { + use super::*; + + fn attrs(s: &[&str]) -> Vec { + s.iter().map(|x| (*x).to_owned()).collect() + } + + #[test] + fn inbox_always_maps_to_inbox() { + assert_eq!(role_for_folder("INBOX", &[], "", true), Some("inbox")); + assert_eq!(role_for_folder("inbox", &[], "", true), Some("inbox")); + assert_eq!(role_for_folder("Inbox", &[], "", false), Some("inbox")); + } + + #[test] + fn special_use_drives_role() { + let r = role_for_folder("Whatever", &attrs(&["\\Sent"]), "", false); + assert_eq!(r, Some("sent")); + let r = role_for_folder("Anywhere", &attrs(&["\\Drafts"]), "", true); + assert_eq!(r, Some("drafts")); + } + + #[test] + fn special_use_table_covers_all_iana_roles() { + for (attr, expected) in [ + ("\\All", "all"), + ("\\Archive", "archive"), + ("\\Drafts", "drafts"), + ("\\Flagged", "flagged"), + ("\\Important", "important"), + ("\\Junk", "junk"), + ("\\Sent", "sent"), + ("\\Trash", "trash"), + ] { + let r = role_from_special_use(&attrs(&[attr])); + assert_eq!(r, Some(expected), "for {attr}"); + } + } + + #[test] + fn special_use_attrs_unrelated_to_roles_yield_none() { + for s in [ + "\\Subscribed", + "\\HasChildren", + "\\HasNoChildren", + "\\Marked", + "\\Unmarked", + "\\Noselect", + "\\Noinferiors", + "\\Remote", + ] { + assert_eq!(role_from_special_use(&attrs(&[s])), None); + } + } + + #[test] + fn name_heuristic_when_no_special_use() { + assert_eq!(role_for_folder("Sent", &[], "", true), Some("sent")); + assert_eq!(role_for_folder("Sent Items", &[], "", true), Some("sent")); + assert_eq!(role_for_folder("Drafts", &[], "", true), Some("drafts")); + assert_eq!(role_for_folder("Trash", &[], "", true), Some("trash")); + assert_eq!( + role_for_folder("Deleted Items", &[], "", true), + Some("trash") + ); + assert_eq!(role_for_folder("Junk", &[], "", true), Some("junk")); + assert_eq!(role_for_folder("Spam", &[], "", true), Some("junk")); + assert_eq!(role_for_folder("Archive", &[], "", true), Some("archive")); + assert_eq!( + role_for_folder("[Gmail]/All Mail", &[], "", true), + Some("all") + ); + assert_eq!(role_for_folder("Archives", &[], "", true), Some("archive")); + } + + #[test] + fn name_heuristic_disabled_when_automap_off() { + assert_eq!(role_for_folder("Sent", &[], "", false), None); + assert_eq!(role_for_folder("Trash", &[], "", false), None); + } + + #[test] + fn namespace_prefix_stripped_before_name_match() { + assert_eq!( + role_for_folder("INBOX.Sent", &[], "INBOX.", true), + Some("sent") + ); + assert_eq!( + role_for_folder("INBOX.Drafts", &[], "INBOX.", true), + Some("drafts") + ); + } + + #[test] + fn unknown_folder_yields_none() { + assert_eq!(role_for_folder("My Custom Folder", &[], "", true), None); + assert_eq!(role_for_folder("Projects/Alpha", &[], "", true), None); + } + + #[test] + fn case_insensitive_name_match() { + assert_eq!(role_for_folder("sent", &[], "", true), Some("sent")); + assert_eq!(role_for_folder("DRAFTS", &[], "", true), Some("drafts")); + } + + #[test] + fn all_special_use_wins_over_heuristic_archive() { + let r = role_for_folder("[Gmail]/All Mail", &attrs(&["\\All"]), "", true); + assert_eq!(r, Some("all")); + } + + #[test] + fn gmail_all_mail_no_attribute_is_all_not_archive() { + let r = role_for_folder("[Gmail]/All Mail", &[], "", true); + assert_eq!(r, Some("all")); + } + + #[test] + fn plain_archive_name_resolves_to_archive() { + assert_eq!(role_for_folder("Archive", &[], "", true), Some("archive")); + assert_eq!(role_for_folder("Archives", &[], "", true), Some("archive")); + } +} diff --git a/src/imap/client.rs b/src/imap/client.rs new file mode 100644 index 0000000..89ae2a6 --- /dev/null +++ b/src/imap/client.rs @@ -0,0 +1,652 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::BTreeSet; +use std::io::{BufReader, Read, Write}; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; + +use super::command::{self, CommandBuilder}; +use super::error::{ImapError, NoError}; +use super::response::{Response, Status, StatusLine, Untagged, parse_response}; +use super::transport::{Connector, ImapStream}; + +pub struct ImapClient { + reader: BufReader>, + tags: CommandBuilder, + pub capabilities: BTreeSet, + pub host: String, + closed: bool, + utf8_accept: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConnectMode { + ImplicitTls, + Plain, + StartTls, +} + +#[derive(Debug, Clone)] +pub struct Greeting { + pub status: Status, + pub text: String, + pub code: Option, +} + +impl ImapClient { + pub fn connect( + connector: &Connector, + host: &str, + port: u16, + mode: ConnectMode, + ) -> Result { + let stream: Box = match mode { + ConnectMode::ImplicitTls => connector.connect_tls(host, port)?, + ConnectMode::Plain | ConnectMode::StartTls => connector.connect_plain(host, port)?, + }; + let mut client = ImapClient { + reader: BufReader::new(stream), + tags: CommandBuilder::new(), + capabilities: BTreeSet::new(), + host: host.to_owned(), + closed: false, + utf8_accept: false, + }; + client.read_greeting()?; + client.send_capability()?; + if matches!(mode, ConnectMode::StartTls) { + if !client.has_capability("STARTTLS") { + return Err(ImapError::Unsupported( + "server does not advertise STARTTLS but cleartext credentials are forbidden" + .into(), + )); + } + client.starttls(connector)?; + client.send_capability()?; + } + Ok(client) + } + + pub fn has_capability(&self, name: &str) -> bool { + self.capabilities + .iter() + .any(|c| c.eq_ignore_ascii_case(name)) + } + + fn read_greeting(&mut self) -> Result { + let resp = parse_response(&mut self.reader)?; + match resp { + Response::Untagged(Untagged::StatusLine(line)) => { + if let Some(code) = &line.code + && code == "CAPABILITY" + && let Some(args) = &line.code_args + { + self.capabilities = parse_caps(args); + } + Ok(Greeting { + status: line.status, + text: line.text, + code: line.code, + }) + } + Response::Untagged(Untagged::Bye(text)) => Err(ImapError::Bye(text)), + other => Err(ImapError::Protocol(format!( + "expected greeting, got {other:?}" + ))), + } + } + + pub fn refresh_capabilities(&mut self) -> Result<(), ImapError> { + self.send_capability() + } + + fn send_capability(&mut self) -> Result<(), ImapError> { + let resp = self.run_collect(command::capability())?; + for u in resp.untagged { + if let Untagged::Capability(caps) = u { + self.capabilities = caps.into_iter().collect(); + } + } + if self.capabilities.is_empty() { + return Err(ImapError::Protocol( + "server returned no CAPABILITY response".into(), + )); + } + Ok(()) + } + + fn starttls(&mut self, connector: &Connector) -> Result<(), ImapError> { + self.run_collect(command::starttls())?; + let stream = self.reader.get_ref(); + if stream.is_tls() { + return Err(ImapError::Protocol( + "STARTTLS issued but stream already TLS".into(), + )); + } + let buffered = std::mem::replace(&mut self.reader, BufReader::new(noop_stream())); + let inner = buffered.into_inner(); + let upgraded = inner.upgrade_tls(connector, &self.host)?; + self.reader = BufReader::new(upgraded); + Ok(()) + } + + pub fn utf8_accept(&self) -> bool { + self.utf8_accept + } + + pub fn enable(&mut self, extensions: &[&str]) -> Result, ImapError> { + if !self.has_capability("ENABLE") { + return Err(ImapError::Unsupported( + "server does not advertise ENABLE".into(), + )); + } + let resp = self.run_collect(&command::enable(extensions))?; + let mut enabled: Vec = Vec::new(); + for u in resp.untagged { + if let Untagged::Enabled(exts) = u { + enabled.extend(exts); + } + } + if enabled + .iter() + .any(|e| e.eq_ignore_ascii_case("UTF8=ACCEPT")) + { + self.utf8_accept = true; + } + Ok(enabled) + } + + pub fn compress_deflate(&mut self) -> Result<(), ImapError> { + if !self.has_capability("COMPRESS=DEFLATE") { + return Err(ImapError::Unsupported( + "server does not advertise COMPRESS=DEFLATE".into(), + )); + } + self.run_collect(command::compress_deflate())?; + let buffered = std::mem::replace(&mut self.reader, BufReader::new(noop_stream())); + let primer = buffered.buffer().to_vec(); + let inner = buffered.into_inner(); + let deflated = super::transport::DeflateImapStream::wrap(inner, &primer); + self.reader = BufReader::new(deflated); + Ok(()) + } + + pub fn login(&mut self, user: &str, password: &str) -> Result<(), ImapError> { + if self.capabilities.iter().any(|c| c == "LOGINDISABLED") { + return Err(ImapError::Unsupported( + "server advertises LOGINDISABLED".into(), + )); + } + let cmd = command::login(user, password); + match self.run_collect(&cmd) { + Ok(_) => Ok(()), + Err(ImapError::No(no)) if no.is_auth_failed() => Err(ImapError::AuthFailed(no.text)), + Err(e) => Err(e), + } + } + + pub fn authenticate_plain(&mut self, authcid: &str, password: &str) -> Result<(), ImapError> { + let mut payload = Vec::with_capacity(authcid.len() + password.len() + 3); + payload.push(0); + payload.extend_from_slice(authcid.as_bytes()); + payload.push(0); + payload.extend_from_slice(password.as_bytes()); + let encoded = BASE64.encode(&payload); + if self.has_capability("SASL-IR") { + let cmd = command::authenticate_with_ir("PLAIN", &encoded); + return match self.run_collect(&cmd) { + Ok(_) => Ok(()), + Err(ImapError::No(no)) if no.is_auth_failed() => { + Err(ImapError::AuthFailed(no.text)) + } + Err(e) => Err(e), + }; + } + let (tag, bytes) = self.tags.build(&command::authenticate("PLAIN")); + self.write_all(&bytes)?; + loop { + let resp = parse_response(&mut self.reader)?; + match resp { + Response::Continuation(_) => { + let mut line = encoded.clone().into_bytes(); + line.extend_from_slice(b"\r\n"); + self.write_all(&line)?; + } + Response::Tagged { tag: t, line } if t == tag => { + self.update_capabilities_from_code(&line); + return self.finish_tagged(line); + } + Response::Untagged(Untagged::Capability(caps)) => { + self.capabilities = caps.into_iter().collect(); + } + _ => {} + } + } + } + + pub fn authenticate_xoauth2(&mut self, user: &str, token: &str) -> Result<(), ImapError> { + let mut payload = String::with_capacity(user.len() + token.len() + 32); + payload.push_str("user="); + payload.push_str(user); + payload.push('\x01'); + payload.push_str("auth=Bearer "); + payload.push_str(token); + payload.push('\x01'); + payload.push('\x01'); + self.drive_bearer_sasl("XOAUTH2", payload.as_bytes()) + } + + pub fn authenticate_oauthbearer(&mut self, user: &str, token: &str) -> Result<(), ImapError> { + let mut payload = String::with_capacity(user.len() + token.len() + 32); + payload.push_str("n,a="); + payload.push_str(user); + payload.push(','); + payload.push('\x01'); + payload.push_str("auth=Bearer "); + payload.push_str(token); + payload.push('\x01'); + payload.push('\x01'); + self.drive_bearer_sasl("OAUTHBEARER", payload.as_bytes()) + } + + fn drive_bearer_sasl(&mut self, mechanism: &str, payload: &[u8]) -> Result<(), ImapError> { + let encoded = BASE64.encode(payload); + if self.has_capability("SASL-IR") { + let cmd = command::authenticate_with_ir(mechanism, &encoded); + return match self.run_collect(&cmd) { + Ok(_) => Ok(()), + Err(ImapError::No(no)) if no.is_auth_failed() => { + Err(ImapError::AuthFailed(no.text)) + } + Err(e) => Err(e), + }; + } + let (tag, bytes) = self.tags.build(&command::authenticate(mechanism)); + self.write_all(&bytes)?; + let mut sent = false; + loop { + let resp = parse_response(&mut self.reader)?; + match resp { + Response::Continuation(_) => { + if sent { + self.write_all(b"\r\n")?; + } else { + let mut line = encoded.clone().into_bytes(); + line.extend_from_slice(b"\r\n"); + self.write_all(&line)?; + sent = true; + } + } + Response::Tagged { tag: t, line } if t == tag => { + self.update_capabilities_from_code(&line); + return self.finish_tagged(line); + } + Response::Untagged(Untagged::Capability(caps)) => { + self.capabilities = caps.into_iter().collect(); + } + _ => {} + } + } + } + + fn update_capabilities_from_code(&mut self, line: &StatusLine) { + if line.code.as_deref() == Some("CAPABILITY") + && let Some(args) = &line.code_args + { + self.capabilities = parse_caps(args); + } + } + + pub fn noop(&mut self) -> Result { + self.run_collect(command::noop()) + } + + pub fn logout(&mut self) -> Result<(), ImapError> { + if self.closed { + return Ok(()); + } + let _ = self.run_collect(command::logout()); + self.closed = true; + Ok(()) + } + + pub fn run(&mut self, command: &str) -> Result { + self.run_collect(command) + } + + pub fn run_streamed( + &mut self, + command: &str, + mut on_untagged: F, + ) -> Result + where + F: FnMut(Untagged), + { + let (tag, bytes) = self.tags.build(command); + if command::contains_literal(command.as_bytes()) + && !self.has_capability("LITERAL+") + && !self.has_capability("LITERAL-") + { + return Err(ImapError::Unsupported( + "command contains a non-ASCII literal but server does not advertise LITERAL+ or LITERAL-" + .into(), + )); + } + self.write_all(&bytes)?; + loop { + let resp = parse_response(&mut self.reader)?; + match resp { + Response::Tagged { tag: t, line } if t == tag => { + self.update_capabilities_from_code(&line); + return match line.status { + Status::Ok => Ok(line), + Status::No => Err(ImapError::No(line.into_no_error())), + Status::Bad => Err(ImapError::Bad(line.text)), + Status::Bye => Err(ImapError::Bye(line.text)), + Status::PreAuth => Err(ImapError::Protocol( + "unexpected PREAUTH on tagged response".into(), + )), + }; + } + Response::Tagged { tag: other, .. } => { + return Err(ImapError::Protocol(format!( + "expected tag {tag}, got {other}" + ))); + } + Response::Untagged(Untagged::Bye(text)) => { + self.closed = true; + return Err(ImapError::Bye(text)); + } + Response::Untagged(u) => { + if let Untagged::Capability(caps) = &u { + self.capabilities = caps.iter().cloned().collect(); + } + on_untagged(u); + } + Response::Continuation(_) => { + return Err(ImapError::Protocol( + "unexpected continuation outside AUTHENTICATE".into(), + )); + } + } + } + } + + pub fn run_collect(&mut self, command: &str) -> Result { + let (tag, bytes) = self.tags.build(command); + if command::contains_literal(command.as_bytes()) + && !self.has_capability("LITERAL+") + && !self.has_capability("LITERAL-") + { + return Err(ImapError::Unsupported( + "command contains a non-ASCII literal but server does not advertise LITERAL+ or LITERAL-" + .into(), + )); + } + self.write_all(&bytes)?; + let mut untagged = Vec::new(); + loop { + let resp = parse_response(&mut self.reader)?; + match resp { + Response::Tagged { tag: t, line } if t == tag => { + self.update_capabilities_from_code(&line); + return match line.status { + Status::Ok => Ok(CollectedResponse { + tag, + line, + untagged, + }), + Status::No => Err(ImapError::No(line.into_no_error())), + Status::Bad => Err(ImapError::Bad(line.text)), + Status::Bye => Err(ImapError::Bye(line.text)), + Status::PreAuth => Err(ImapError::Protocol( + "unexpected PREAUTH on tagged response".into(), + )), + }; + } + Response::Tagged { tag: other, .. } => { + return Err(ImapError::Protocol(format!( + "expected tag {tag}, got {other}" + ))); + } + Response::Untagged(Untagged::Bye(text)) => { + self.closed = true; + return Err(ImapError::Bye(text)); + } + Response::Untagged(u) => { + if let Untagged::Capability(caps) = &u { + self.capabilities = caps.iter().cloned().collect(); + } + untagged.push(u); + } + Response::Continuation(_) => { + return Err(ImapError::Protocol( + "unexpected continuation outside AUTHENTICATE".into(), + )); + } + } + } + } + + fn finish_tagged(&mut self, line: StatusLine) -> Result<(), ImapError> { + match line.status { + Status::Ok => Ok(()), + Status::No => { + let no = NoError::new(line.text, line.code); + if no.is_auth_failed() { + Err(ImapError::AuthFailed(no.text)) + } else { + Err(ImapError::No(no)) + } + } + Status::Bad => Err(ImapError::Bad(line.text)), + Status::Bye => Err(ImapError::Bye(line.text)), + Status::PreAuth => Err(ImapError::Protocol( + "unexpected PREAUTH on tagged response".into(), + )), + } + } + + fn write_all(&mut self, bytes: &[u8]) -> Result<(), ImapError> { + let stream = self.reader.get_mut(); + stream.write_all(bytes)?; + stream.flush()?; + Ok(()) + } +} + +impl Drop for ImapClient { + fn drop(&mut self) { + if !self.closed { + let _ = self.logout(); + } + } +} + +#[derive(Debug)] +pub struct CollectedResponse { + pub tag: String, + pub line: StatusLine, + pub untagged: Vec, +} + +fn parse_caps(s: &str) -> BTreeSet { + s.split_ascii_whitespace().map(|w| w.to_owned()).collect() +} + +fn noop_stream() -> Box { + Box::new(NoopStream) +} + +struct NoopStream; + +impl Read for NoopStream { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +impl Write for NoopStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl ImapStream for NoopStream { + fn upgrade_tls( + self: Box, + _connector: &Connector, + _host: &str, + ) -> Result, ImapError> { + Ok(self) + } + fn is_tls(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + struct MockStream { + reader: Cursor>, + written: Vec, + } + + impl MockStream { + fn boxed(server_says: &[u8]) -> Box { + Box::new(MockStream { + reader: Cursor::new(server_says.to_vec()), + written: Vec::new(), + }) + } + } + + impl Read for MockStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } + } + + impl Write for MockStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.written.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + impl ImapStream for MockStream { + fn upgrade_tls( + self: Box, + _c: &Connector, + _h: &str, + ) -> Result, ImapError> { + Ok(self) + } + fn is_tls(&self) -> bool { + false + } + } + + fn client_with(server: &[u8]) -> ImapClient { + ImapClient { + reader: BufReader::new(MockStream::boxed(server)), + tags: CommandBuilder::new(), + capabilities: BTreeSet::new(), + host: "test.example".to_owned(), + closed: false, + utf8_accept: false, + } + } + + #[test] + fn run_collect_drains_untagged_and_returns_ok() { + let server = b"* CAPABILITY IMAP4rev1 STARTTLS\r\nA0001 OK done\r\n"; + let mut c = client_with(server); + let r = c.run_collect("CAPABILITY").unwrap(); + assert_eq!(r.untagged.len(), 1); + assert!(matches!(r.line.status, Status::Ok)); + } + + #[test] + fn run_collect_propagates_no_as_imaperror_no() { + let server = b"A0001 NO mailbox not found\r\n"; + let mut c = client_with(server); + let err = c.run_collect("SELECT bogus").unwrap_err(); + match err { + ImapError::No(no) => assert_eq!(no.text, "mailbox not found"), + other => panic!("expected No, got {other:?}"), + } + } + + #[test] + fn run_collect_propagates_bad_as_imaperror_bad() { + let server = b"A0001 BAD syntax error\r\n"; + let mut c = client_with(server); + let err = c.run_collect("BOGUS").unwrap_err(); + assert!(matches!(err, ImapError::Bad(_))); + } + + #[test] + fn run_collect_translates_authenticationfailed_via_login_path() { + let server = b"A0001 NO [AUTHENTICATIONFAILED] bad creds\r\n"; + let mut c = client_with(server); + let err = c.login("alice", "wrong").unwrap_err(); + assert!(matches!(err, ImapError::AuthFailed(_))); + } + + #[test] + fn login_refused_when_logindisabled() { + let server = b"A0001 OK any\r\n"; + let mut c = client_with(server); + c.capabilities.insert("LOGINDISABLED".to_owned()); + let err = c.login("a", "b").unwrap_err(); + assert!(matches!(err, ImapError::Unsupported(_))); + } + + #[test] + fn bye_mid_response_closes_client() { + let server = b"* BYE going away\r\n"; + let mut c = client_with(server); + let err = c.run_collect("NOOP").unwrap_err(); + assert!(matches!(err, ImapError::Bye(_))); + assert!(c.closed); + } + + #[test] + fn authenticate_plain_sasl_ir_path() { + let server = b"A0001 OK auth done\r\n"; + let mut c = client_with(server); + c.capabilities.insert("SASL-IR".to_owned()); + c.authenticate_plain("alice", "p@ss").unwrap(); + } + + #[test] + fn authenticate_plain_continuation_path() { + let server = b"+ \r\nA0001 OK auth done\r\n"; + let mut c = client_with(server); + c.authenticate_plain("alice", "p@ss").unwrap(); + } + + #[test] + fn has_capability_is_case_insensitive() { + let mut c = client_with(b""); + c.capabilities.insert("STARTTLS".to_owned()); + assert!(c.has_capability("starttls")); + assert!(c.has_capability("STARTTLS")); + assert!(!c.has_capability("MOVE")); + } +} diff --git a/src/imap/command.rs b/src/imap/command.rs new file mode 100644 index 0000000..0056bff --- /dev/null +++ b/src/imap/command.rs @@ -0,0 +1,384 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::fmt::Write as _; + +pub struct CommandBuilder { + next_tag: u32, +} + +impl Default for CommandBuilder { + fn default() -> Self { + Self::new() + } +} + +impl CommandBuilder { + pub fn new() -> Self { + Self { next_tag: 1 } + } + + pub fn next_tag(&mut self) -> String { + let tag = format!("A{:04}", self.next_tag); + self.next_tag = self.next_tag.wrapping_add(1).max(1); + tag + } + + pub fn build(&mut self, command: &str) -> (String, Vec) { + let tag = self.next_tag(); + let mut out = String::with_capacity(tag.len() + command.len() + 4); + out.push_str(&tag); + out.push(' '); + out.push_str(command); + out.push_str("\r\n"); + (tag, out.into_bytes()) + } +} + +pub fn quote_astring(s: &str) -> String { + let needs_literal = s.bytes().any(|b| !(0x20..=0x7e).contains(&b)); + if needs_literal { + format!("{{{}+}}\r\n{}", s.len(), s) + } else { + let mut out = String::with_capacity(s.len() + 2); + out.push('"'); + for c in s.chars() { + if c == '"' || c == '\\' { + out.push('\\'); + } + out.push(c); + } + out.push('"'); + out + } +} + +pub fn contains_literal(bytes: &[u8]) -> bool { + let mut i = 0; + while i + 2 < bytes.len() { + if bytes[i] == b'{' { + let mut j = i + 1; + while j < bytes.len() && bytes[j].is_ascii_digit() { + j += 1; + } + if j < bytes.len() && bytes[j] == b'+' { + j += 1; + } + if j < bytes.len() && bytes[j] == b'}' { + return true; + } + } + i += 1; + } + false +} + +pub fn login(user: &str, password: &str) -> String { + format!("LOGIN {} {}", quote_astring(user), quote_astring(password)) +} + +pub fn authenticate(mechanism: &str) -> String { + format!("AUTHENTICATE {mechanism}") +} + +pub fn authenticate_with_ir(mechanism: &str, initial_response: &str) -> String { + format!("AUTHENTICATE {mechanism} {initial_response}") +} + +pub fn capability() -> &'static str { + "CAPABILITY" +} + +pub fn starttls() -> &'static str { + "STARTTLS" +} + +pub fn compress_deflate() -> &'static str { + "COMPRESS DEFLATE" +} + +pub fn noop() -> &'static str { + "NOOP" +} + +pub fn logout() -> &'static str { + "LOGOUT" +} + +pub fn namespace() -> &'static str { + "NAMESPACE" +} + +pub fn enable(extensions: &[&str]) -> String { + let mut out = String::from("ENABLE"); + for ext in extensions { + out.push(' '); + out.push_str(ext); + } + out +} + +pub fn list(reference: &str, pattern: &str) -> String { + format!( + "LIST {} {}", + quote_astring(reference), + quote_astring(pattern) + ) +} + +pub fn list_extended( + reference: &str, + patterns: &[&str], + selection: &[&str], + return_options: &[&str], +) -> String { + let mut out = String::from("LIST"); + if !selection.is_empty() { + out.push_str(" ("); + for (i, s) in selection.iter().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str(s); + } + out.push(')'); + } + out.push(' '); + out.push_str("e_astring(reference)); + if patterns.len() == 1 { + out.push(' '); + out.push_str("e_astring(patterns[0])); + } else { + out.push_str(" ("); + for (i, p) in patterns.iter().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str("e_astring(p)); + } + out.push(')'); + } + if !return_options.is_empty() { + out.push_str(" RETURN ("); + for (i, r) in return_options.iter().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str(r); + } + out.push(')'); + } + out +} + +pub fn lsub(reference: &str, pattern: &str) -> String { + format!( + "LSUB {} {}", + quote_astring(reference), + quote_astring(pattern) + ) +} + +pub fn select(mailbox: &str) -> String { + format!("SELECT {}", quote_astring(mailbox)) +} + +pub fn examine(mailbox: &str) -> String { + format!("EXAMINE {}", quote_astring(mailbox)) +} + +pub fn status(mailbox: &str, items: &[&str]) -> String { + let mut out = String::from("STATUS "); + out.push_str("e_astring(mailbox)); + out.push_str(" ("); + for (i, s) in items.iter().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str(s); + } + out.push(')'); + out +} + +pub fn uid_search_esearch_all() -> &'static str { + "UID SEARCH RETURN (ALL) ALL" +} + +pub fn uid_search_all() -> &'static str { + "UID SEARCH ALL" +} + +pub fn uid_fetch_all_uids() -> &'static str { + "UID FETCH 1:* (UID)" +} + +pub fn uid_fetch(set: &str, items: &[&str]) -> String { + let mut out = String::from("UID FETCH "); + out.push_str(set); + out.push_str(" ("); + for (i, s) in items.iter().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str(s); + } + out.push(')'); + out +} + +pub fn format_uid_set(uids: &[u32], is_sorted: bool) -> String { + if uids.is_empty() { + return String::new(); + } + let owned_sorted: Vec; + let slice: &[u32] = if is_sorted { + uids + } else { + let mut s = uids.to_vec(); + s.sort_unstable(); + s.dedup(); + owned_sorted = s; + &owned_sorted[..] + }; + + let mut out = String::with_capacity(slice.len() * 8); + let mut i = 0; + let mut first = true; + while i < slice.len() { + let start = slice[i]; + let mut end = start; + let mut j = i + 1; + while j < slice.len() && slice[j] == end + 1 { + end = slice[j]; + j += 1; + } + if !first { + out.push(','); + } + if start == end { + let _ = write!(&mut out, "{start}"); + } else { + let _ = write!(&mut out, "{start}:{end}"); + } + first = false; + i = j; + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn next_tag_increments() { + let mut b = CommandBuilder::new(); + assert_eq!(b.next_tag(), "A0001"); + assert_eq!(b.next_tag(), "A0002"); + assert_eq!(b.next_tag(), "A0003"); + } + + #[test] + fn build_appends_crlf() { + let mut b = CommandBuilder::new(); + let (tag, bytes) = b.build("NOOP"); + assert_eq!(tag, "A0001"); + assert_eq!(bytes, b"A0001 NOOP\r\n"); + } + + #[test] + fn quote_astring_quotes_safe_text() { + assert_eq!(quote_astring("hello"), "\"hello\""); + assert_eq!(quote_astring("INBOX"), "\"INBOX\""); + } + + #[test] + fn quote_astring_escapes_quote_and_backslash() { + assert_eq!(quote_astring("a\"b"), "\"a\\\"b\""); + assert_eq!(quote_astring("a\\b"), "\"a\\\\b\""); + } + + #[test] + fn quote_astring_switches_to_literal_for_non_ascii() { + let q = quote_astring("éclair"); + assert!(q.starts_with("{")); + assert!(q.contains("\r\n")); + assert!(q.ends_with("éclair")); + } + + #[test] + fn login_command_format() { + let s = login("alice", "p@ss"); + assert_eq!(s, "LOGIN \"alice\" \"p@ss\""); + } + + #[test] + fn list_extended_with_return_clause() { + let s = list_extended("", &["*"], &[], &["SPECIAL-USE", "SUBSCRIBED"]); + assert_eq!(s, "LIST \"\" \"*\" RETURN (SPECIAL-USE SUBSCRIBED)"); + } + + #[test] + fn list_extended_with_selection_and_multi_pattern() { + let s = list_extended("", &["INBOX", "Sent"], &["SUBSCRIBED"], &[]); + assert_eq!(s, "LIST (SUBSCRIBED) \"\" (\"INBOX\" \"Sent\")"); + } + + #[test] + fn status_items_serialise_in_order() { + let s = status("INBOX", &["UIDVALIDITY", "UIDNEXT", "MESSAGES"]); + assert_eq!(s, "STATUS \"INBOX\" (UIDVALIDITY UIDNEXT MESSAGES)"); + } + + #[test] + fn uid_fetch_with_items() { + let s = uid_fetch("1:100", &["UID", "FLAGS", "INTERNALDATE", "RFC822.SIZE"]); + assert_eq!(s, "UID FETCH 1:100 (UID FLAGS INTERNALDATE RFC822.SIZE)"); + } + + #[test] + fn format_uid_set_collapses_consecutive_sorted_input() { + assert_eq!(format_uid_set(&[], true), ""); + assert_eq!(format_uid_set(&[5], true), "5"); + assert_eq!(format_uid_set(&[1, 2, 3, 5, 7, 8], true), "1:3,5,7:8"); + } + + #[test] + fn format_uid_set_sorts_when_caller_unsure() { + assert_eq!(format_uid_set(&[3, 1, 2], false), "1:3"); + assert_eq!(format_uid_set(&[1, 1, 1, 2, 2, 3], false), "1:3"); + } + + #[test] + fn enable_with_multiple_extensions() { + assert_eq!( + enable(&["UTF8=ACCEPT", "CONDSTORE"]), + "ENABLE UTF8=ACCEPT CONDSTORE" + ); + } + + #[test] + fn quote_astring_uses_literal_plus_for_non_ascii() { + let q = quote_astring("éclair"); + assert!(q.starts_with("{")); + assert!(q.contains("+}\r\n")); + assert!(q.ends_with("éclair")); + } + + #[test] + fn contains_literal_detects_literal_plus_and_sync() { + assert!(contains_literal(b"LOGIN \"u\" {6+}\r\nsecret")); + assert!(contains_literal(b"LOGIN \"u\" {6}\r\nsecret")); + assert!(!contains_literal(b"LOGIN \"u\" \"p\"")); + } + + #[test] + fn format_uid_set_trusts_sorted_flag() { + let s = format_uid_set(&[1, 2, 3, 7, 9], true); + assert_eq!(s, "1:3,7,9"); + } +} diff --git a/src/imap/error.rs b/src/imap/error.rs new file mode 100644 index 0000000..8dec7a1 --- /dev/null +++ b/src/imap/error.rs @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::fmt; +use std::io; + +#[derive(Debug, thiserror::Error)] +pub enum ImapError { + #[error("transport error: {0}")] + Io(#[from] io::Error), + + #[error("tls error: {0}")] + Tls(String), + + #[error("protocol error: {0}")] + Protocol(String), + + #[error("response parse error: {0}")] + Parse(String), + + #[error("authentication failed: {0}")] + AuthFailed(String), + + #[error("server said BAD: {0}")] + Bad(String), + + #[error("server said NO: {0}")] + No(NoError), + + #[error("server closed connection: {0}")] + Bye(String), + + #[error("connection is no longer usable")] + Disconnected, + + #[error("unsupported by server: {0}")] + Unsupported(String), +} + +#[derive(Debug, Clone)] +pub struct NoError { + pub text: String, + pub code: Option, +} + +impl NoError { + pub fn new(text: impl Into, code: Option) -> Self { + Self { + text: text.into(), + code, + } + } + + pub fn is_transient(&self) -> bool { + let lower = self.text.to_ascii_lowercase(); + const TRANSIENT_MARKERS: &[&str] = &[ + "temp", + "try again", + "try later", + "unavailable", + "over quota", + "overquota", + "throttle", + "rate limit", + "too many", + ]; + TRANSIENT_MARKERS.iter().any(|m| lower.contains(m)) + } + + pub fn is_auth_failed(&self) -> bool { + matches!( + self.code.as_deref(), + Some("AUTHENTICATIONFAILED") | Some("PRIVACYREQUIRED") | Some("AUTHORIZATIONFAILED") + ) + } +} + +impl fmt::Display for NoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.code { + Some(code) => write!(f, "[{code}] {}", self.text), + None => f.write_str(&self.text), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn transient_markers_detected_case_insensitively() { + for s in [ + "Temporary failure", + "Try again later", + "Server unavailable", + "OVER QUOTA", + "user is rate limited", + "Too many connections", + ] { + assert!( + NoError::new(s, None).is_transient(), + "should be transient: {s}" + ); + } + } + + #[test] + fn non_transient_markers_not_flagged() { + for s in [ + "Mailbox does not exist", + "Permission denied", + "Invalid argument", + ] { + assert!( + !NoError::new(s, None).is_transient(), + "should NOT be transient: {s}" + ); + } + } + + #[test] + fn auth_failed_codes_detected() { + assert!(NoError::new("x", Some("AUTHENTICATIONFAILED".into())).is_auth_failed()); + assert!(NoError::new("x", Some("PRIVACYREQUIRED".into())).is_auth_failed()); + assert!(!NoError::new("x", Some("ALERT".into())).is_auth_failed()); + assert!(!NoError::new("x", None).is_auth_failed()); + } +} diff --git a/src/imap/mod.rs b/src/imap/mod.rs new file mode 100644 index 0000000..af211de --- /dev/null +++ b/src/imap/mod.rs @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod automap; +pub mod client; +pub mod command; +pub mod error; +pub mod name; +pub mod response; +pub mod retry; +pub mod transport; + +pub use client::{CollectedResponse, ConnectMode, Greeting, ImapClient}; +pub use error::ImapError; diff --git a/src/imap/name.rs b/src/imap/name.rs new file mode 100644 index 0000000..a4a3ad9 --- /dev/null +++ b/src/imap/name.rs @@ -0,0 +1,235 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use super::error::ImapError; + +const MODIFIED_UTF7_ALPHABET: &[u8] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; + +pub fn canonicalise_inbox(name: &str) -> String { + if name.eq_ignore_ascii_case("INBOX") { + "INBOX".to_owned() + } else { + name.to_owned() + } +} + +pub fn decode_mailbox_name(input: &str) -> Result { + decode_mailbox_name_with(input, false) +} + +pub fn decode_mailbox_name_with(input: &str, utf8_accept: bool) -> Result { + if utf8_accept { + return Ok(input.to_owned()); + } + if input.is_ascii() && !input.contains('&') { + return Ok(input.to_owned()); + } + let bytes = input.as_bytes(); + let mut out = String::with_capacity(input.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] != b'&' { + out.push(bytes[i] as char); + i += 1; + continue; + } + if i + 1 < bytes.len() && bytes[i + 1] == b'-' { + out.push('&'); + i += 2; + continue; + } + let mut end = i + 1; + while end < bytes.len() && bytes[end] != b'-' { + end += 1; + } + if end == bytes.len() { + return Err(ImapError::Parse( + "unterminated modified UTF-7 sequence".into(), + )); + } + let encoded = &bytes[i + 1..end]; + let decoded = decode_b64_modified(encoded)?; + let u16s: Vec = decoded + .chunks(2) + .filter(|c| c.len() == 2) + .map(|c| u16::from_be_bytes([c[0], c[1]])) + .collect(); + let s = String::from_utf16(&u16s).map_err(|e| ImapError::Parse(format!("utf-16: {e}")))?; + out.push_str(&s); + i = end + 1; + } + Ok(out) +} + +pub fn encode_mailbox_name(input: &str) -> String { + encode_mailbox_name_with(input, false) +} + +pub fn encode_mailbox_name_with(input: &str, utf8_accept: bool) -> String { + if utf8_accept { + return input.to_owned(); + } + let mut out = String::with_capacity(input.len()); + let mut pending: Vec = Vec::new(); + let flush = |pending: &mut Vec, out: &mut String| { + if pending.is_empty() { + return; + } + out.push('&'); + let mut raw = Vec::with_capacity(pending.len() * 2); + for w in pending.iter() { + raw.extend_from_slice(&w.to_be_bytes()); + } + out.push_str(&encode_b64_modified(&raw)); + out.push('-'); + pending.clear(); + }; + for c in input.chars() { + let cp = c as u32; + if c == '&' { + flush(&mut pending, &mut out); + out.push_str("&-"); + } else if (0x20..=0x7E).contains(&cp) { + flush(&mut pending, &mut out); + out.push(c); + } else { + let mut buf = [0u16; 2]; + let units = c.encode_utf16(&mut buf); + pending.extend_from_slice(units); + } + } + flush(&mut pending, &mut out); + out +} + +fn decode_b64_modified(bytes: &[u8]) -> Result, ImapError> { + let mut out = Vec::with_capacity(bytes.len() * 3 / 4); + let mut acc: u32 = 0; + let mut bits: u32 = 0; + for &b in bytes { + let v = match MODIFIED_UTF7_ALPHABET.iter().position(|&x| x == b) { + Some(idx) => idx as u32, + None => { + return Err(ImapError::Parse(format!( + "invalid modified UTF-7 byte {b:#04x}" + ))); + } + }; + acc = (acc << 6) | v; + bits += 6; + if bits >= 8 { + bits -= 8; + let byte = ((acc >> bits) & 0xFF) as u8; + out.push(byte); + } + } + Ok(out) +} + +fn encode_b64_modified(bytes: &[u8]) -> String { + let mut out = String::with_capacity(bytes.len() * 4 / 3 + 1); + let mut acc: u32 = 0; + let mut bits: u32 = 0; + for &b in bytes { + acc = (acc << 8) | b as u32; + bits += 8; + while bits >= 6 { + bits -= 6; + let idx = ((acc >> bits) & 0x3F) as usize; + out.push(MODIFIED_UTF7_ALPHABET[idx] as char); + } + } + if bits > 0 { + let idx = ((acc << (6 - bits)) & 0x3F) as usize; + out.push(MODIFIED_UTF7_ALPHABET[idx] as char); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ascii_passes_through_decode() { + assert_eq!(decode_mailbox_name("INBOX").unwrap(), "INBOX"); + assert_eq!(decode_mailbox_name("Sent/Items").unwrap(), "Sent/Items"); + } + + #[test] + fn ampersand_dash_decodes_to_literal_amp() { + assert_eq!(decode_mailbox_name("R&-D").unwrap(), "R&D"); + assert_eq!(decode_mailbox_name("&-").unwrap(), "&"); + } + + #[test] + fn non_ascii_decode_japanese() { + let decoded = decode_mailbox_name("~peter/mail/&ZeVnLIqe-/&U,BTFw-").unwrap(); + assert_eq!(decoded, "~peter/mail/日本語/台北"); + } + + #[test] + fn unterminated_sequence_errors() { + let err = decode_mailbox_name("&ZeVnLIqe").unwrap_err(); + assert!(matches!(err, ImapError::Parse(_))); + } + + #[test] + fn ascii_passes_through_encode() { + assert_eq!(encode_mailbox_name("INBOX"), "INBOX"); + assert_eq!(encode_mailbox_name("Sent Items"), "Sent Items"); + } + + #[test] + fn literal_amp_encodes_to_amp_dash() { + assert_eq!(encode_mailbox_name("R&D"), "R&-D"); + assert_eq!(encode_mailbox_name("&"), "&-"); + } + + #[test] + fn non_ascii_encode_japanese() { + let encoded = encode_mailbox_name("~peter/mail/日本語/台北"); + assert_eq!(encoded, "~peter/mail/&ZeVnLIqe-/&U,BTFw-"); + } + + #[test] + fn encode_decode_roundtrip_various() { + for s in [ + "INBOX", + "Sent Mail", + "R&D", + "日本語", + "Hé llo", + "INBOX.Projects.Alpha", + "Junk E-mail", + ] { + let enc = encode_mailbox_name(s); + let dec = decode_mailbox_name(&enc).unwrap(); + assert_eq!(dec, s, "roundtrip failed for {s:?}, enc={enc:?}"); + } + } + + #[test] + fn inbox_canonical_uppercase() { + assert_eq!(canonicalise_inbox("inbox"), "INBOX"); + assert_eq!(canonicalise_inbox("Inbox"), "INBOX"); + assert_eq!(canonicalise_inbox("INBOX"), "INBOX"); + assert_eq!(canonicalise_inbox("Inbox/Sub"), "Inbox/Sub"); + } + + #[test] + fn utf8_accept_skips_mutf7_decode_so_ampersand_passes_through() { + assert_eq!(decode_mailbox_name_with("R&D", true).unwrap(), "R&D"); + assert_eq!(decode_mailbox_name_with("日本語", true).unwrap(), "日本語"); + } + + #[test] + fn utf8_accept_skips_mutf7_encode_so_input_passes_through() { + assert_eq!(encode_mailbox_name_with("日本語", true), "日本語"); + assert_eq!(encode_mailbox_name_with("R&D", true), "R&D"); + } +} diff --git a/src/imap/response.rs b/src/imap/response.rs new file mode 100644 index 0000000..d5302a0 --- /dev/null +++ b/src/imap/response.rs @@ -0,0 +1,1093 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::BTreeMap; +use std::io::BufRead; + +use super::error::{ImapError, NoError}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Status { + Ok, + No, + Bad, + Bye, + PreAuth, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StatusLine { + pub status: Status, + pub code: Option, + pub code_args: Option, + pub text: String, +} + +impl StatusLine { + pub fn into_no_error(self) -> NoError { + NoError::new(self.text, self.code) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + Atom(String), + Str(String), + Bytes(Vec), + Number(u64), + Nil, + List(Vec), +} + +impl Value { + pub fn as_str(&self) -> Option<&str> { + match self { + Value::Atom(s) | Value::Str(s) => Some(s), + _ => None, + } + } + + pub fn as_number(&self) -> Option { + match self { + Value::Number(n) => Some(*n), + _ => None, + } + } + + pub fn as_list(&self) -> Option<&[Value]> { + match self { + Value::List(items) => Some(items.as_slice()), + _ => None, + } + } + + pub fn into_bytes(self) -> Option> { + match self { + Value::Bytes(b) => Some(b), + Value::Str(s) => Some(s.into_bytes()), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Untagged { + Capability(Vec), + List { + attributes: Vec, + delimiter: Option, + name: String, + }, + Lsub { + attributes: Vec, + delimiter: Option, + name: String, + }, + Status { + mailbox: String, + items: BTreeMap, + }, + Search(Vec), + Esearch { + tag: Option, + all: Vec, + count: Option, + }, + Exists(u32), + Recent(u32), + Expunge(u32), + Fetch { + seq: u32, + items: Vec<(String, Value)>, + }, + Flags(Vec), + Namespace { + personal: Vec, + others: Vec, + shared: Vec, + }, + Enabled(Vec), + StatusLine(StatusLine), + Bye(String), + Raw(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NamespaceEntry { + pub prefix: String, + pub delimiter: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Response { + Tagged { tag: String, line: StatusLine }, + Untagged(Untagged), + Continuation(String), +} + +pub fn parse_response(reader: &mut R) -> Result { + let mut parser = Parser::new(reader); + parser.parse_response() +} + +struct Parser<'r, R: BufRead> { + reader: &'r mut R, + buf: Vec, + pos: usize, +} + +impl<'r, R: BufRead> Parser<'r, R> { + fn new(reader: &'r mut R) -> Self { + Self { + reader, + buf: Vec::new(), + pos: 0, + } + } + + fn parse_response(&mut self) -> Result { + self.read_line()?; + if self.buf.is_empty() { + return Err(ImapError::Disconnected); + } + if self.peek() == Some(b'+') { + self.bump(); + self.skip_ws(); + let text = self.read_until_crlf(); + return Ok(Response::Continuation(text)); + } + if self.peek() == Some(b'*') { + self.bump(); + self.skip_ws(); + return self.parse_untagged().map(Response::Untagged); + } + let tag = self.read_atom_string()?; + self.skip_ws(); + let line = self.parse_status_line()?; + Ok(Response::Tagged { tag, line }) + } + + fn parse_untagged(&mut self) -> Result { + let first = self.read_atom_string()?; + let upper = first.to_ascii_uppercase(); + match upper.as_str() { + "OK" | "NO" | "BAD" | "BYE" | "PREAUTH" => { + self.skip_ws(); + let mut line = self.parse_status_line_body()?; + line.status = match upper.as_str() { + "OK" => Status::Ok, + "NO" => Status::No, + "BAD" => Status::Bad, + "BYE" => Status::Bye, + "PREAUTH" => Status::PreAuth, + _ => unreachable!(), + }; + if matches!(line.status, Status::Bye) { + return Ok(Untagged::Bye(line.text)); + } + Ok(Untagged::StatusLine(line)) + } + "CAPABILITY" => { + let mut caps = Vec::new(); + while !self.at_end() { + self.skip_ws(); + if self.at_end() { + break; + } + caps.push(self.read_atom_string()?); + } + Ok(Untagged::Capability(caps)) + } + "LIST" | "LSUB" => { + self.skip_ws(); + self.expect(b'(')?; + let mut attrs = Vec::new(); + while self.peek() != Some(b')') { + self.skip_ws(); + if self.peek() == Some(b')') { + break; + } + attrs.push(self.read_atom_string()?); + } + self.expect(b')')?; + self.skip_ws(); + let delim_value = self.parse_value()?; + let delimiter = match delim_value { + Value::Nil => None, + Value::Str(s) | Value::Atom(s) => s.chars().next(), + Value::Bytes(b) if !b.is_empty() => Some(b[0] as char), + _ => None, + }; + self.skip_ws(); + let name_val = self.parse_value()?; + let name = match name_val { + Value::Str(s) | Value::Atom(s) => s, + Value::Bytes(b) => String::from_utf8_lossy(&b).into_owned(), + _ => return Err(ImapError::Parse("LIST mailbox name missing".into())), + }; + if upper == "LIST" { + Ok(Untagged::List { + attributes: attrs, + delimiter, + name, + }) + } else { + Ok(Untagged::Lsub { + attributes: attrs, + delimiter, + name, + }) + } + } + "STATUS" => { + self.skip_ws(); + let mailbox = match self.parse_value()? { + Value::Str(s) | Value::Atom(s) => s, + Value::Bytes(b) => String::from_utf8_lossy(&b).into_owned(), + _ => return Err(ImapError::Parse("STATUS mailbox missing".into())), + }; + self.skip_ws(); + self.expect(b'(')?; + let mut items = BTreeMap::new(); + while self.peek() != Some(b')') { + self.skip_ws(); + if self.peek() == Some(b')') { + break; + } + let key = self.read_atom_string()?.to_ascii_uppercase(); + self.skip_ws(); + let val = self.parse_value()?; + if let Some(n) = val.as_number() { + items.insert(key, n); + } + } + self.expect(b')')?; + Ok(Untagged::Status { mailbox, items }) + } + "SEARCH" => { + let mut ids = Vec::new(); + while !self.at_end() { + self.skip_ws(); + if self.at_end() { + break; + } + let v = self.parse_value()?; + if let Some(n) = v.as_number() { + ids.push(n as u32); + } + } + Ok(Untagged::Search(ids)) + } + "ESEARCH" => self.parse_esearch(), + "FLAGS" => { + self.skip_ws(); + let v = self.parse_value()?; + let flags = match v { + Value::List(items) => items + .into_iter() + .filter_map(|x| match x { + Value::Atom(s) | Value::Str(s) => Some(s), + _ => None, + }) + .collect(), + _ => Vec::new(), + }; + Ok(Untagged::Flags(flags)) + } + "NAMESPACE" => self.parse_namespace(), + "ENABLED" => { + let mut exts = Vec::new(); + while !self.at_end() { + self.skip_ws(); + if self.at_end() { + break; + } + exts.push(self.read_atom_string()?); + } + Ok(Untagged::Enabled(exts)) + } + _ => { + if let Ok(num) = upper.parse::() { + self.skip_ws(); + let kw = self.read_atom_string()?.to_ascii_uppercase(); + match kw.as_str() { + "EXISTS" => return Ok(Untagged::Exists(num)), + "RECENT" => return Ok(Untagged::Recent(num)), + "EXPUNGE" => return Ok(Untagged::Expunge(num)), + "FETCH" => return self.parse_fetch_body(num), + _ => { + let rest = self.read_until_crlf(); + return Ok(Untagged::Raw(format!("{num} {kw} {rest}"))); + } + } + } + let rest = self.read_until_crlf(); + Ok(Untagged::Raw(format!("{first} {rest}"))) + } + } + } + + fn parse_esearch(&mut self) -> Result { + let mut tag = None; + let mut all = Vec::new(); + let mut count = None; + if self.peek() == Some(b' ') { + self.skip_ws(); + } + if self.peek() == Some(b'(') { + self.bump(); + self.skip_ws(); + let key = self.read_atom_string()?.to_ascii_uppercase(); + if key == "TAG" { + self.skip_ws(); + if let Value::Str(s) = self.parse_value()? { + tag = Some(s); + } + } + self.skip_ws(); + self.expect(b')')?; + } + while !self.at_end() { + self.skip_ws(); + if self.at_end() { + break; + } + let key = self.read_atom_string()?.to_ascii_uppercase(); + match key.as_str() { + "UID" => {} + "ALL" => { + self.skip_ws(); + let s = self.read_seq_set_token(); + all = parse_seq_set(&s); + } + "COUNT" => { + self.skip_ws(); + if let Value::Number(n) = self.parse_value()? { + count = Some(n as u32); + } + } + _ => { + self.skip_ws(); + let _ = self.parse_value(); + } + } + } + Ok(Untagged::Esearch { tag, all, count }) + } + + fn parse_namespace(&mut self) -> Result { + self.skip_ws(); + let personal = self.parse_namespace_list()?; + self.skip_ws(); + let others = self.parse_namespace_list()?; + self.skip_ws(); + let shared = self.parse_namespace_list()?; + Ok(Untagged::Namespace { + personal, + others, + shared, + }) + } + + fn parse_namespace_list(&mut self) -> Result, ImapError> { + match self.parse_value()? { + Value::Nil => Ok(Vec::new()), + Value::List(entries) => { + let mut out = Vec::new(); + for e in entries { + if let Value::List(inner) = e + && let Some(prefix) = inner.first().and_then(|v| v.as_str()) + { + let delim = inner.get(1).and_then(|v| match v { + Value::Nil => None, + Value::Str(s) | Value::Atom(s) => s.chars().next(), + _ => None, + }); + out.push(NamespaceEntry { + prefix: prefix.to_owned(), + delimiter: delim, + }); + } + } + Ok(out) + } + _ => Ok(Vec::new()), + } + } + + fn parse_fetch_body(&mut self, seq: u32) -> Result { + self.skip_ws(); + self.expect(b'(')?; + let mut items = Vec::new(); + while self.peek() != Some(b')') { + self.skip_ws(); + if self.peek() == Some(b')') { + break; + } + let name = self.read_fetch_item_name()?; + self.skip_ws(); + let val = self.parse_value()?; + items.push((name, val)); + } + self.expect(b')')?; + Ok(Untagged::Fetch { seq, items }) + } + + fn read_fetch_item_name(&mut self) -> Result { + let start = self.pos; + while let Some(b) = self.peek() { + match b { + b' ' | b'\r' | b'\n' | b')' | b'(' => break, + _ => self.bump(), + } + } + let bytes = &self.buf[start..self.pos]; + std::str::from_utf8(bytes) + .map(|s| s.to_ascii_uppercase()) + .map_err(|e| ImapError::Parse(format!("non-utf8 fetch item name: {e}"))) + } + + fn parse_status_line(&mut self) -> Result { + let kw = self.read_atom_string()?; + self.skip_ws(); + let mut line = self.parse_status_line_body()?; + line.status = match kw.to_ascii_uppercase().as_str() { + "OK" => Status::Ok, + "NO" => Status::No, + "BAD" => Status::Bad, + "BYE" => Status::Bye, + "PREAUTH" => Status::PreAuth, + other => return Err(ImapError::Parse(format!("unknown status: {other}"))), + }; + Ok(line) + } + + fn parse_status_line_body(&mut self) -> Result { + let mut code = None; + let mut code_args = None; + if self.peek() == Some(b'[') { + self.bump(); + let start = self.pos; + let mut depth = 1usize; + while let Some(b) = self.peek() { + if b == b'[' { + depth += 1; + self.bump(); + } else if b == b']' { + depth -= 1; + if depth == 0 { + break; + } + self.bump(); + } else { + self.bump(); + } + } + let inside = &self.buf[start..self.pos]; + let inside_str = std::str::from_utf8(inside) + .map_err(|e| ImapError::Parse(format!("non-utf8 status code: {e}")))?; + let (head, tail) = match inside_str.split_once(' ') { + Some((h, t)) => (h.to_owned(), Some(t.to_owned())), + None => (inside_str.to_owned(), None), + }; + code = Some(head.to_ascii_uppercase()); + code_args = tail; + self.expect(b']')?; + self.skip_ws(); + } + let text = self.read_until_crlf(); + Ok(StatusLine { + status: Status::Ok, + code, + code_args, + text, + }) + } + + fn parse_value(&mut self) -> Result { + self.skip_ws(); + match self.peek() { + Some(b'"') => self.parse_quoted(), + Some(b'{') => self.parse_literal(), + Some(b'(') => self.parse_list(), + Some(b) + if b.is_ascii_digit() + || b == b'-' + || b == b'+' + || is_atom_byte(b) + || b == b'\\' => + { + self.parse_atom_or_number() + } + None => Err(ImapError::Parse("expected value, got EOL".into())), + Some(other) => Err(ImapError::Parse(format!( + "unexpected byte {other:#04x} in value" + ))), + } + } + + fn parse_quoted(&mut self) -> Result { + self.expect(b'"')?; + let mut s = String::new(); + while let Some(b) = self.peek() { + match b { + b'"' => { + self.bump(); + return Ok(Value::Str(s)); + } + b'\\' => { + self.bump(); + let next = self + .peek() + .ok_or_else(|| ImapError::Parse("trailing \\ in quoted".into()))?; + s.push(next as char); + self.bump(); + } + _ => { + s.push(b as char); + self.bump(); + } + } + } + Err(ImapError::Parse("unterminated quoted string".into())) + } + + fn parse_literal(&mut self) -> Result { + self.expect(b'{')?; + let mut len_str = String::new(); + while let Some(b) = self.peek() { + if b.is_ascii_digit() { + len_str.push(b as char); + self.bump(); + } else { + break; + } + } + if self.peek() == Some(b'+') { + self.bump(); + } + self.expect(b'}')?; + let len: usize = len_str + .parse() + .map_err(|e| ImapError::Parse(format!("literal length: {e}")))?; + if !self.at_end_of_line() { + return Err(ImapError::Parse("garbage after literal header".into())); + } + let mut data = vec![0u8; len]; + self.reader.read_exact(&mut data).map_err(ImapError::Io)?; + self.buf.clear(); + self.read_line()?; + if let Ok(s) = std::str::from_utf8(&data) { + Ok(Value::Str(s.to_owned())) + } else { + Ok(Value::Bytes(data)) + } + } + + fn parse_list(&mut self) -> Result { + self.expect(b'(')?; + let mut items = Vec::new(); + loop { + self.skip_ws(); + if self.peek() == Some(b')') { + self.bump(); + return Ok(Value::List(items)); + } + if self.peek().is_none() { + return Err(ImapError::Parse("unterminated list".into())); + } + items.push(self.parse_value()?); + } + } + + fn parse_atom_or_number(&mut self) -> Result { + let start = self.pos; + while let Some(b) = self.peek() { + if is_atom_byte(b) || b == b'\\' || b == b'*' || b == b']' || b == b'+' { + self.bump(); + } else { + break; + } + } + let raw = &self.buf[start..self.pos]; + let s = std::str::from_utf8(raw) + .map_err(|e| ImapError::Parse(format!("non-utf8 atom: {e}")))? + .to_owned(); + if s.is_empty() { + return Err(ImapError::Parse("empty atom".into())); + } + if s.eq_ignore_ascii_case("NIL") { + return Ok(Value::Nil); + } + if let Ok(n) = s.parse::() { + return Ok(Value::Number(n)); + } + Ok(Value::Atom(s)) + } + + fn read_atom_string(&mut self) -> Result { + let start = self.pos; + while let Some(b) = self.peek() { + if is_atom_byte(b) { + self.bump(); + } else { + break; + } + } + if start == self.pos { + return Err(ImapError::Parse("expected atom".into())); + } + let bytes = &self.buf[start..self.pos]; + std::str::from_utf8(bytes) + .map(|s| s.to_owned()) + .map_err(|e| ImapError::Parse(format!("non-utf8 atom: {e}"))) + } + + fn read_seq_set_token(&mut self) -> String { + let start = self.pos; + while let Some(b) = self.peek() { + if b == b' ' || b == b'\r' || b == b'\n' { + break; + } + self.bump(); + } + String::from_utf8_lossy(&self.buf[start..self.pos]).into_owned() + } + + fn read_until_crlf(&mut self) -> String { + let start = self.pos; + let end = self.buf.len().saturating_sub(2); + let end = end.max(start); + self.pos = self.buf.len(); + String::from_utf8_lossy(&self.buf[start..end]) + .trim_end() + .to_owned() + } + + fn read_line(&mut self) -> Result<(), ImapError> { + self.buf.clear(); + let n = self.reader.read_until(b'\n', &mut self.buf)?; + self.pos = 0; + if n == 0 { + return Ok(()); + } + Ok(()) + } + + fn peek(&self) -> Option { + let end = self.buf.len().saturating_sub(self.tail_crlf_len()); + if self.pos >= end { + None + } else { + Some(self.buf[self.pos]) + } + } + + fn bump(&mut self) { + self.pos += 1; + } + + fn skip_ws(&mut self) { + while self.peek() == Some(b' ') { + self.bump(); + } + } + + fn at_end(&self) -> bool { + self.peek().is_none() + } + + fn at_end_of_line(&self) -> bool { + let end = self.buf.len().saturating_sub(self.tail_crlf_len()); + self.pos >= end + } + + fn tail_crlf_len(&self) -> usize { + if self.buf.ends_with(b"\r\n") { + 2 + } else if self.buf.ends_with(b"\n") { + 1 + } else { + 0 + } + } + + fn expect(&mut self, b: u8) -> Result<(), ImapError> { + if self.peek() == Some(b) { + self.bump(); + Ok(()) + } else { + Err(ImapError::Parse(format!( + "expected {:?}, got {:?}", + b as char, + self.peek().map(|x| x as char) + ))) + } + } +} + +fn is_atom_byte(b: u8) -> bool { + matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' + | b'-' | b'.' | b'_' | b'!' | b'#' | b'$' | b'&' + | b'\'' | b'+' | b'/' | b';' | b'<' | b'=' | b'>' | b'?' + | b'@' | b'\\' | b'^' | b'`' | b'|' | b'~' | b'*') +} + +fn parse_seq_set(s: &str) -> Vec { + let mut out = Vec::new(); + for part in s.split(',') { + let part = part.trim(); + if part.is_empty() { + continue; + } + if let Some((lo, hi)) = part.split_once(':') { + let lo: u32 = match lo.parse() { + Ok(n) => n, + Err(_) => continue, + }; + let hi: u32 = match hi.parse() { + Ok(n) => n, + Err(_) => continue, + }; + let (a, b) = if lo <= hi { (lo, hi) } else { (hi, lo) }; + for n in a..=b { + out.push(n); + } + } else if let Ok(n) = part.parse::() { + out.push(n); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + fn parse(input: &[u8]) -> Response { + parse_response(&mut Cursor::new(input)).unwrap() + } + + #[test] + fn tagged_ok() { + let r = parse(b"A001 OK LOGIN completed\r\n"); + match r { + Response::Tagged { tag, line } => { + assert_eq!(tag, "A001"); + assert_eq!(line.status, Status::Ok); + assert_eq!(line.text, "LOGIN completed"); + } + _ => panic!("expected Tagged"), + } + } + + #[test] + fn tagged_no_with_code() { + let r = parse(b"A002 NO [AUTHENTICATIONFAILED] bad credentials\r\n"); + match r { + Response::Tagged { tag, line } => { + assert_eq!(tag, "A002"); + assert_eq!(line.status, Status::No); + assert_eq!(line.code.as_deref(), Some("AUTHENTICATIONFAILED")); + assert_eq!(line.text, "bad credentials"); + } + _ => panic!("expected Tagged"), + } + } + + #[test] + fn untagged_capability() { + let r = parse(b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN AUTH=LOGIN\r\n"); + match r { + Response::Untagged(Untagged::Capability(caps)) => { + assert_eq!( + caps, + vec!["IMAP4rev1", "STARTTLS", "AUTH=PLAIN", "AUTH=LOGIN"] + ); + } + _ => panic!("expected Capability"), + } + } + + #[test] + fn untagged_capability_with_literal_plus() { + let r = parse( + b"* CAPABILITY IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS AUTH=PLAIN AUTH=OAUTHBEARER AUTH=XOAUTH2\r\n", + ); + match r { + Response::Untagged(Untagged::Capability(caps)) => { + assert!(caps.iter().any(|c| c == "LITERAL+")); + assert!(caps.iter().any(|c| c == "AUTH=OAUTHBEARER")); + assert_eq!(caps.len(), 11); + } + _ => panic!("expected Capability"), + } + } + + #[test] + fn untagged_list_with_special_use() { + let r = parse(b"* LIST (\\HasNoChildren \\Sent) \"/\" \"Sent Mail\"\r\n"); + match r { + Response::Untagged(Untagged::List { + attributes, + delimiter, + name, + }) => { + assert_eq!(attributes, vec!["\\HasNoChildren", "\\Sent"]); + assert_eq!(delimiter, Some('/')); + assert_eq!(name, "Sent Mail"); + } + _ => panic!("expected List"), + } + } + + #[test] + fn untagged_list_with_nil_delimiter() { + let r = parse(b"* LIST () NIL INBOX\r\n"); + match r { + Response::Untagged(Untagged::List { + attributes, + delimiter, + name, + }) => { + assert!(attributes.is_empty()); + assert_eq!(delimiter, None); + assert_eq!(name, "INBOX"); + } + _ => panic!("expected List"), + } + } + + #[test] + fn untagged_status() { + let r = parse(b"* STATUS INBOX (UIDVALIDITY 12345 UIDNEXT 42 MESSAGES 7)\r\n"); + match r { + Response::Untagged(Untagged::Status { mailbox, items }) => { + assert_eq!(mailbox, "INBOX"); + assert_eq!(items.get("UIDVALIDITY"), Some(&12345)); + assert_eq!(items.get("UIDNEXT"), Some(&42)); + assert_eq!(items.get("MESSAGES"), Some(&7)); + } + _ => panic!("expected Status"), + } + } + + #[test] + fn untagged_search_with_uids() { + let r = parse(b"* SEARCH 1 3 5 7\r\n"); + match r { + Response::Untagged(Untagged::Search(ids)) => { + assert_eq!(ids, vec![1, 3, 5, 7]); + } + _ => panic!("expected Search"), + } + } + + #[test] + fn untagged_search_empty() { + let r = parse(b"* SEARCH\r\n"); + match r { + Response::Untagged(Untagged::Search(ids)) => { + assert!(ids.is_empty()); + } + _ => panic!("expected Search"), + } + } + + #[test] + fn untagged_esearch_all_set() { + let r = parse(b"* ESEARCH (TAG \"q1\") UID ALL 1:3,7\r\n"); + match r { + Response::Untagged(Untagged::Esearch { tag, all, .. }) => { + assert_eq!(tag.as_deref(), Some("q1")); + assert_eq!(all, vec![1, 2, 3, 7]); + } + _ => panic!("expected Esearch"), + } + } + + #[test] + fn untagged_exists_recent() { + let r = parse(b"* 42 EXISTS\r\n"); + assert!(matches!(r, Response::Untagged(Untagged::Exists(42)))); + let r = parse(b"* 3 RECENT\r\n"); + assert!(matches!(r, Response::Untagged(Untagged::Recent(3)))); + } + + #[test] + fn untagged_fetch_flags_and_uid() { + let r = parse( + b"* 17 FETCH (UID 100 FLAGS (\\Seen \\Flagged) RFC822.SIZE 4242 INTERNALDATE \"17-Jul-1996 02:44:25 -0700\")\r\n", + ); + match r { + Response::Untagged(Untagged::Fetch { seq, items }) => { + assert_eq!(seq, 17); + let uid = items.iter().find(|(n, _)| n == "UID").unwrap(); + assert_eq!(uid.1.as_number(), Some(100)); + let flags = items.iter().find(|(n, _)| n == "FLAGS").unwrap(); + let list = flags.1.as_list().unwrap(); + assert_eq!(list.len(), 2); + let size = items.iter().find(|(n, _)| n == "RFC822.SIZE").unwrap(); + assert_eq!(size.1.as_number(), Some(4242)); + let internaldate = items.iter().find(|(n, _)| n == "INTERNALDATE").unwrap(); + assert_eq!(internaldate.1.as_str(), Some("17-Jul-1996 02:44:25 -0700")); + } + _ => panic!("expected Fetch"), + } + } + + #[test] + fn untagged_fetch_with_body_literal() { + let input = b"* 1 FETCH (UID 5 BODY[] {11}\r\nHello world)\r\n"; + let r = parse(input); + match r { + Response::Untagged(Untagged::Fetch { seq, items }) => { + assert_eq!(seq, 1); + let body = items.iter().find(|(n, _)| n == "BODY[]").unwrap(); + match &body.1 { + Value::Str(s) => assert_eq!(s, "Hello world"), + Value::Bytes(b) => assert_eq!(b, b"Hello world"), + other => panic!("expected literal, got {other:?}"), + } + } + _ => panic!("expected Fetch"), + } + } + + #[test] + fn untagged_fetch_with_binary_literal() { + let mut input = b"* 1 FETCH (UID 5 BODY[] {4}\r\n".to_vec(); + input.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]); + input.extend_from_slice(b")\r\n"); + let r = parse_response(&mut Cursor::new(input)).unwrap(); + match r { + Response::Untagged(Untagged::Fetch { items, .. }) => { + let body = items.iter().find(|(n, _)| n == "BODY[]").unwrap(); + match &body.1 { + Value::Bytes(b) => assert_eq!(b, &[0xFF, 0xFE, 0x00, 0x01]), + other => panic!("expected Bytes, got {other:?}"), + } + } + _ => panic!("expected Fetch"), + } + } + + #[test] + fn untagged_flags() { + let r = parse(b"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)\r\n"); + match r { + Response::Untagged(Untagged::Flags(flags)) => { + assert_eq!(flags.len(), 5); + assert!(flags.contains(&"\\Seen".to_owned())); + } + _ => panic!("expected Flags"), + } + } + + #[test] + fn untagged_bye() { + let r = parse(b"* BYE server logging out\r\n"); + match r { + Response::Untagged(Untagged::Bye(text)) => { + assert_eq!(text, "server logging out"); + } + _ => panic!("expected Bye"), + } + } + + #[test] + fn untagged_namespace() { + let r = parse(b"* NAMESPACE ((\"INBOX.\" \".\")) NIL NIL\r\n"); + match r { + Response::Untagged(Untagged::Namespace { + personal, + others, + shared, + }) => { + assert_eq!(personal.len(), 1); + assert_eq!(personal[0].prefix, "INBOX."); + assert_eq!(personal[0].delimiter, Some('.')); + assert!(others.is_empty()); + assert!(shared.is_empty()); + } + _ => panic!("expected Namespace"), + } + } + + #[test] + fn continuation_request() { + let r = parse(b"+ Ready for additional command text\r\n"); + match r { + Response::Continuation(text) => { + assert_eq!(text, "Ready for additional command text"); + } + _ => panic!("expected Continuation"), + } + } + + #[test] + fn untagged_status_with_response_code() { + let r = parse(b"* OK [PERMANENTFLAGS (\\Seen \\Flagged \\*)] Limited\r\n"); + match r { + Response::Untagged(Untagged::StatusLine(line)) => { + assert_eq!(line.status, Status::Ok); + assert_eq!(line.code.as_deref(), Some("PERMANENTFLAGS")); + assert!(line.code_args.as_deref().is_some()); + assert_eq!(line.text, "Limited"); + } + _ => panic!("expected StatusLine"), + } + } + + #[test] + fn untagged_ok_with_uidvalidity() { + let r = parse(b"* OK [UIDVALIDITY 3857529045] UIDs valid\r\n"); + match r { + Response::Untagged(Untagged::StatusLine(line)) => { + assert_eq!(line.code.as_deref(), Some("UIDVALIDITY")); + assert_eq!(line.code_args.as_deref(), Some("3857529045")); + } + _ => panic!("expected StatusLine"), + } + } + + #[test] + fn quoted_string_with_escape() { + let r = parse(b"* LIST () \"/\" \"He said \\\"hi\\\"\"\r\n"); + match r { + Response::Untagged(Untagged::List { name, .. }) => { + assert_eq!(name, "He said \"hi\""); + } + _ => panic!("expected List"), + } + } + + #[test] + fn seq_set_parses_ranges_and_singletons() { + assert_eq!(parse_seq_set("1:3,5,7:8"), vec![1, 2, 3, 5, 7, 8]); + assert_eq!(parse_seq_set("42"), vec![42]); + assert!(parse_seq_set("").is_empty()); + } + + #[test] + fn empty_read_is_disconnected() { + let mut empty: &[u8] = b""; + let err = parse_response(&mut empty).unwrap_err(); + assert!(matches!(err, ImapError::Disconnected)); + } + + #[test] + fn untagged_enabled_parses() { + let r = parse(b"* ENABLED UTF8=ACCEPT CONDSTORE\r\n"); + match r { + Response::Untagged(Untagged::Enabled(exts)) => { + assert_eq!(exts, vec!["UTF8=ACCEPT", "CONDSTORE"]); + } + other => panic!("expected Enabled, got {other:?}"), + } + } +} diff --git a/src/imap/retry.rs b/src/imap/retry.rs new file mode 100644 index 0000000..eb2e615 --- /dev/null +++ b/src/imap/retry.rs @@ -0,0 +1,352 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::time::Duration; + +use super::error::ImapError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Disposition { + TransportDrop, + Transient, + Permanent, + PerMessageRecoverable, + PerFolderPermanent, + FeatureNegotiation, + AuthFailed, +} + +#[derive(Debug, Clone, Copy)] +pub struct RetryPolicy { + pub max_retries: u32, +} + +impl RetryPolicy { + pub fn new(max_retries: u32) -> Self { + Self { max_retries } + } +} + +const SHARED_BASE_MS: u64 = 1_000; +const SHARED_CAP_MS: u64 = 30_000; +const TRANSPORT_BASE_MS: u64 = 500; +const TRANSPORT_CAP_MS: u64 = 30_000; +const LEVEL_CAP: u32 = 16; + +#[derive(Default, Clone)] +pub struct BackoffState { + inner: Arc, +} + +#[derive(Default)] +struct BackoffInner { + level: AtomicU32, + transient_retries: AtomicU32, + transport_retries: AtomicU32, +} + +impl BackoffState { + pub fn new() -> Self { + Self::default() + } + + pub fn reset(&self) { + self.inner.level.store(0, Ordering::Relaxed); + } + + pub fn current_level(&self) -> u32 { + self.inner.level.load(Ordering::Relaxed) + } + + pub fn transient_retries(&self) -> u32 { + self.inner.transient_retries.load(Ordering::Relaxed) + } + + pub fn transport_retries(&self) -> u32 { + self.inner.transport_retries.load(Ordering::Relaxed) + } + + pub fn total_retries(&self) -> u64 { + self.transient_retries() as u64 + self.transport_retries() as u64 + } + + pub fn next_shared_delay(&self) -> Duration { + let prev = self.inner.level.fetch_add(1, Ordering::Relaxed); + self.inner.transient_retries.fetch_add(1, Ordering::Relaxed); + let n = prev.saturating_add(1).min(LEVEL_CAP); + let shift = n.saturating_sub(1).min(15); + let scaled = SHARED_BASE_MS.saturating_mul(1u64 << shift); + let scaled = scaled.min(SHARED_CAP_MS); + let half = scaled / 2; + Duration::from_millis(jitter_in_range(half, scaled)) + } + + pub fn transport_delay(&self, attempt: u32) -> Duration { + self.inner.transport_retries.fetch_add(1, Ordering::Relaxed); + let n = attempt.min(LEVEL_CAP); + let shift = n.saturating_sub(1).min(15); + let scaled = TRANSPORT_BASE_MS.saturating_mul(1u64 << shift); + let scaled = scaled.min(TRANSPORT_CAP_MS); + Duration::from_millis(jitter_in_range(0, scaled)) + } +} + +fn jitter_in_range(lo: u64, hi: u64) -> u64 { + if hi <= lo { + return lo; + } + let span = hi - lo; + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.subsec_nanos() as u64) + .unwrap_or(0); + lo + (nanos % span) +} + +pub fn is_negotiation_failure(err: &ImapError) -> bool { + match err { + ImapError::Bad(_) => true, + ImapError::No(no) => !no.is_transient(), + ImapError::Unsupported(_) => true, + _ => false, + } +} + +pub fn classify(err: &ImapError) -> Disposition { + match err { + ImapError::Io(_) => Disposition::TransportDrop, + ImapError::Tls(_) => Disposition::Permanent, + ImapError::Disconnected => Disposition::TransportDrop, + ImapError::Bye(_) => Disposition::TransportDrop, + ImapError::AuthFailed(_) => Disposition::AuthFailed, + ImapError::Bad(_) => Disposition::FeatureNegotiation, + ImapError::No(no) => { + if no.is_auth_failed() { + Disposition::AuthFailed + } else if no.is_transient() { + Disposition::Transient + } else { + Disposition::Permanent + } + } + ImapError::Protocol(_) => Disposition::Permanent, + ImapError::Parse(_) => Disposition::Permanent, + ImapError::Unsupported(_) => Disposition::Permanent, + } +} + +pub fn folder_disposition(err: &ImapError) -> Disposition { + let base = classify(err); + if matches!( + base, + Disposition::Permanent | Disposition::FeatureNegotiation + ) { + Disposition::PerFolderPermanent + } else { + base + } +} + +pub fn message_disposition(err: &ImapError) -> Disposition { + let base = classify(err); + if matches!( + base, + Disposition::Permanent | Disposition::FeatureNegotiation + ) { + Disposition::PerMessageRecoverable + } else { + base + } +} + +#[cfg(test)] +mod tests { + use super::super::error::NoError; + use super::*; + use std::io::{self, ErrorKind}; + + #[test] + fn transport_errors_are_transport_drop() { + for k in [ + ErrorKind::ConnectionReset, + ErrorKind::ConnectionAborted, + ErrorKind::BrokenPipe, + ErrorKind::UnexpectedEof, + ErrorKind::TimedOut, + ] { + let e = ImapError::Io(io::Error::new(k, "x")); + assert_eq!(classify(&e), Disposition::TransportDrop, "{k:?}"); + } + } + + #[test] + fn bye_is_transport_drop() { + assert_eq!( + classify(&ImapError::Bye("going away".into())), + Disposition::TransportDrop + ); + } + + #[test] + fn auth_failed_classified_separately() { + let e = ImapError::AuthFailed("bad creds".into()); + assert_eq!(classify(&e), Disposition::AuthFailed); + let e = ImapError::No(NoError::new("x", Some("AUTHENTICATIONFAILED".into()))); + assert_eq!(classify(&e), Disposition::AuthFailed); + } + + #[test] + fn transient_no_text_marked_transient() { + let e = ImapError::No(NoError::new("Try again later", None)); + assert_eq!(classify(&e), Disposition::Transient); + let e = ImapError::No(NoError::new("user over quota", None)); + assert_eq!(classify(&e), Disposition::Transient); + } + + #[test] + fn non_transient_no_is_permanent() { + let e = ImapError::No(NoError::new("mailbox does not exist", None)); + assert_eq!(classify(&e), Disposition::Permanent); + } + + #[test] + fn bad_is_feature_negotiation() { + let e = ImapError::Bad("unrecognised command".into()); + assert_eq!(classify(&e), Disposition::FeatureNegotiation); + } + + #[test] + fn protocol_and_parse_are_permanent() { + assert_eq!( + classify(&ImapError::Protocol("x".into())), + Disposition::Permanent + ); + assert_eq!( + classify(&ImapError::Parse("x".into())), + Disposition::Permanent + ); + } + + #[test] + fn folder_disposition_lifts_permanent_to_per_folder() { + let e = ImapError::No(NoError::new("no such mailbox", None)); + assert_eq!(folder_disposition(&e), Disposition::PerFolderPermanent); + } + + #[test] + fn folder_disposition_keeps_transient_class() { + let e = ImapError::No(NoError::new("try again", None)); + assert_eq!(folder_disposition(&e), Disposition::Transient); + } + + #[test] + fn message_disposition_demotes_permanent_to_per_message() { + let e = ImapError::Parse("bad fetch".into()); + assert_eq!(message_disposition(&e), Disposition::PerMessageRecoverable); + } + + #[test] + fn shared_delay_grows_with_consecutive_throttles() { + let s = BackoffState::new(); + let d1 = s.next_shared_delay(); + let d3 = { + let _ = s.next_shared_delay(); + s.next_shared_delay() + }; + assert!(d1 <= Duration::from_millis(SHARED_BASE_MS)); + assert!( + d3 >= d1, + "expected non-decreasing delay, got {d1:?} -> {d3:?}" + ); + assert!(d3 <= Duration::from_millis(SHARED_CAP_MS)); + } + + #[test] + fn shared_delay_caps_at_cap_ms() { + let s = BackoffState::new(); + for _ in 0..40 { + let _ = s.next_shared_delay(); + } + let d = s.next_shared_delay(); + assert!(d <= Duration::from_millis(SHARED_CAP_MS)); + } + + #[test] + fn reset_clears_level() { + let s = BackoffState::new(); + let _ = s.next_shared_delay(); + let _ = s.next_shared_delay(); + assert!(s.current_level() > 0); + s.reset(); + assert_eq!(s.current_level(), 0); + } + + #[test] + fn transport_delay_does_not_touch_shared_level() { + let s = BackoffState::new(); + let _ = s.transport_delay(1); + let _ = s.transport_delay(2); + let _ = s.transport_delay(3); + assert_eq!(s.current_level(), 0, "transport delays are local"); + } + + #[test] + fn transport_delay_bounded() { + let s = BackoffState::new(); + let d1 = s.transport_delay(1); + let d6 = s.transport_delay(6); + assert!(d1 <= Duration::from_millis(TRANSPORT_BASE_MS)); + assert!(d6 <= Duration::from_millis(TRANSPORT_CAP_MS)); + } + + #[test] + fn shared_state_is_cloneable_and_shared() { + let a = BackoffState::new(); + let b = a.clone(); + let _ = a.next_shared_delay(); + assert_eq!(a.current_level(), b.current_level()); + } + + #[test] + fn is_negotiation_failure_matches_bad_unsupported_and_nontransient_no() { + assert!(is_negotiation_failure(&ImapError::Bad("x".into()))); + assert!(is_negotiation_failure(&ImapError::Unsupported("x".into()))); + assert!(is_negotiation_failure(&ImapError::No(NoError::new( + "no such mailbox", + None + )))); + } + + #[test] + fn is_negotiation_failure_excludes_transient_no_and_others() { + assert!(!is_negotiation_failure(&ImapError::No(NoError::new( + "try again later", + None + )))); + assert!(!is_negotiation_failure(&ImapError::Bye("x".into()))); + assert!(!is_negotiation_failure(&ImapError::Disconnected)); + } + + #[test] + fn transient_retries_counter_increments_on_shared_delay() { + let s = BackoffState::new(); + assert_eq!(s.transient_retries(), 0); + let _ = s.next_shared_delay(); + let _ = s.next_shared_delay(); + assert_eq!(s.transient_retries(), 2); + } + + #[test] + fn transport_retries_counter_increments_on_transport_delay() { + let s = BackoffState::new(); + assert_eq!(s.transport_retries(), 0); + let _ = s.transport_delay(1); + let _ = s.transport_delay(2); + assert_eq!(s.transport_retries(), 2); + } +} diff --git a/src/imap/transport.rs b/src/imap/transport.rs new file mode 100644 index 0000000..a538f8e --- /dev/null +++ b/src/imap/transport.rs @@ -0,0 +1,435 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{self, Read, Write}; +use std::net::TcpStream; +use std::sync::Arc; +use std::time::Duration; + +use rustls::pki_types::ServerName; +use rustls::{ClientConfig, ClientConnection, StreamOwned}; +use rustls_platform_verifier::BuilderVerifierExt; + +use super::error::ImapError; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); + +pub trait ImapStream: Read + Write + Send { + fn upgrade_tls( + self: Box, + connector: &Connector, + host: &str, + ) -> Result, ImapError>; + + fn is_tls(&self) -> bool; +} + +pub struct Connector { + config: Arc, +} + +impl Connector { + pub fn new(allow_invalid_certs: bool) -> Result { + let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let builder = ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions() + .map_err(|e| ImapError::Tls(format!("rustls protocol versions: {e}")))?; + let config = if allow_invalid_certs { + builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoVerify)) + .with_no_client_auth() + } else { + builder + .with_platform_verifier() + .map_err(|e| ImapError::Tls(format!("rustls platform verifier: {e}")))? + .with_no_client_auth() + }; + Ok(Connector { + config: Arc::new(config), + }) + } + + pub fn connect_plain(&self, host: &str, port: u16) -> Result, ImapError> { + let tcp = tcp_connect(host, port)?; + Ok(Box::new(TcpImapStream { tcp })) + } + + pub fn connect_tls(&self, host: &str, port: u16) -> Result, ImapError> { + let tcp = tcp_connect(host, port)?; + self.wrap_tls(tcp, host) + } + + fn wrap_tls(&self, tcp: TcpStream, host: &str) -> Result, ImapError> { + let server_name = ServerName::try_from(host.to_owned()) + .map_err(|e| ImapError::Tls(format!("invalid server name {host}: {e}")))?; + let conn = ClientConnection::new(self.config.clone(), server_name) + .map_err(|e| ImapError::Tls(format!("rustls client: {e}")))?; + Ok(Box::new(TlsImapStream { + inner: StreamOwned::new(conn, tcp), + })) + } +} + +fn tcp_connect(host: &str, port: u16) -> Result { + let tcp = TcpStream::connect((host, port))?; + tcp.set_read_timeout(Some(DEFAULT_TIMEOUT))?; + tcp.set_write_timeout(Some(DEFAULT_TIMEOUT))?; + tcp.set_nodelay(true)?; + Ok(tcp) +} + +pub struct TcpImapStream { + tcp: TcpStream, +} + +impl Read for TcpImapStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.tcp.read(buf) + } +} + +impl Write for TcpImapStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.tcp.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.tcp.flush() + } +} + +impl ImapStream for TcpImapStream { + fn upgrade_tls( + self: Box, + connector: &Connector, + host: &str, + ) -> Result, ImapError> { + connector.wrap_tls(self.tcp, host) + } + + fn is_tls(&self) -> bool { + false + } +} + +pub struct TlsImapStream { + inner: StreamOwned, +} + +impl Read for TlsImapStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl Write for TlsImapStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl ImapStream for TlsImapStream { + fn upgrade_tls( + self: Box, + _connector: &Connector, + _host: &str, + ) -> Result, ImapError> { + Ok(self) + } + + fn is_tls(&self) -> bool { + true + } +} + +pub struct DeflateImapStream { + inner: Box, + decompress: flate2::Decompress, + compress: flate2::Compress, + rx_raw: Vec, + rx_raw_pos: usize, + rx_decoded: Vec, + rx_decoded_pos: usize, +} + +impl DeflateImapStream { + pub fn wrap(inner: Box, primer: &[u8]) -> Box { + Box::new(DeflateImapStream { + inner, + decompress: flate2::Decompress::new(false), + compress: flate2::Compress::new(flate2::Compression::default(), false), + rx_raw: primer.to_vec(), + rx_raw_pos: 0, + rx_decoded: Vec::new(), + rx_decoded_pos: 0, + }) + } +} + +impl Read for DeflateImapStream { + fn read(&mut self, out: &mut [u8]) -> io::Result { + loop { + if self.rx_decoded_pos < self.rx_decoded.len() { + let avail = &self.rx_decoded[self.rx_decoded_pos..]; + let n = avail.len().min(out.len()); + out[..n].copy_from_slice(&avail[..n]); + self.rx_decoded_pos += n; + return Ok(n); + } + if self.rx_raw_pos >= self.rx_raw.len() { + self.rx_raw.clear(); + self.rx_raw_pos = 0; + self.rx_raw.resize(8192, 0); + let n = self.inner.read(&mut self.rx_raw)?; + if n == 0 { + return Ok(0); + } + self.rx_raw.truncate(n); + } + self.rx_decoded.clear(); + self.rx_decoded_pos = 0; + self.rx_decoded.resize(out.len().max(8192) * 8, 0); + let in_before = self.decompress.total_in(); + let out_before = self.decompress.total_out(); + let status = self.decompress.decompress( + &self.rx_raw[self.rx_raw_pos..], + &mut self.rx_decoded, + flate2::FlushDecompress::None, + ); + match status { + Ok(_) => {} + Err(e) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, e)); + } + } + let consumed = (self.decompress.total_in() - in_before) as usize; + let produced = (self.decompress.total_out() - out_before) as usize; + self.rx_raw_pos += consumed; + self.rx_decoded.truncate(produced); + if produced == 0 && consumed == 0 { + self.rx_raw.clear(); + self.rx_raw_pos = 0; + continue; + } + } + } +} + +impl Write for DeflateImapStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut out = vec![0u8; buf.len() + 256]; + let in_before = self.compress.total_in(); + let out_before = self.compress.total_out(); + self.compress + .compress(buf, &mut out, flate2::FlushCompress::Sync) + .map_err(io::Error::other)?; + let consumed = (self.compress.total_in() - in_before) as usize; + let produced = (self.compress.total_out() - out_before) as usize; + self.inner.write_all(&out[..produced])?; + Ok(consumed) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl ImapStream for DeflateImapStream { + fn upgrade_tls( + self: Box, + _connector: &Connector, + _host: &str, + ) -> Result, ImapError> { + Err(ImapError::Protocol( + "cannot STARTTLS after COMPRESS DEFLATE".into(), + )) + } + + fn is_tls(&self) -> bool { + self.inner.is_tls() + } +} + +#[derive(Debug)] +struct NoVerify; + +impl rustls::client::danger::ServerCertVerifier for NoVerify { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + use rustls::SignatureScheme::*; + vec![ + RSA_PKCS1_SHA256, + RSA_PKCS1_SHA384, + RSA_PKCS1_SHA512, + ECDSA_NISTP256_SHA256, + ECDSA_NISTP384_SHA384, + ECDSA_NISTP521_SHA512, + RSA_PSS_SHA256, + RSA_PSS_SHA384, + RSA_PSS_SHA512, + ED25519, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn connector_constructs_with_invalid_certs_disabled() { + let _c = Connector::new(false).unwrap(); + } + + #[test] + fn connector_constructs_with_invalid_certs_allowed() { + let _c = Connector::new(true).unwrap(); + } + + struct PeerStream { + outbound: Vec, + inbound: Vec, + inbound_pos: usize, + } + + impl PeerStream { + fn new() -> Self { + Self { + outbound: Vec::new(), + inbound: Vec::new(), + inbound_pos: 0, + } + } + } + + impl Read for PeerStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.inbound_pos >= self.inbound.len() { + return Ok(0); + } + let avail = &self.inbound[self.inbound_pos..]; + let n = avail.len().min(buf.len()); + buf[..n].copy_from_slice(&avail[..n]); + self.inbound_pos += n; + Ok(n) + } + } + + impl Write for PeerStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.outbound.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl ImapStream for PeerStream { + fn upgrade_tls( + self: Box, + _c: &Connector, + _h: &str, + ) -> Result, ImapError> { + Ok(self) + } + fn is_tls(&self) -> bool { + false + } + } + + #[test] + fn deflate_writes_round_trip_through_peer_decompressor() { + let peer = Box::new(PeerStream::new()); + let mut stream = DeflateImapStream::wrap(peer, b""); + stream.write_all(b"hello world").unwrap(); + + stream.write_all(b" again").unwrap(); + stream.flush().unwrap(); + } + + #[test] + fn deflate_reads_decompressed_payload_from_inner() { + + let mut peer = PeerStream::new(); + let mut compressor = flate2::Compress::new(flate2::Compression::default(), false); + let plaintext = b"* OK [CAPABILITY IMAP4rev2 COMPRESS=DEFLATE] hi\r\n"; + let mut out = vec![0u8; plaintext.len() + 256]; + compressor + .compress(plaintext, &mut out, flate2::FlushCompress::Sync) + .unwrap(); + let n = compressor.total_out() as usize; + peer.inbound.extend_from_slice(&out[..n]); + + let mut stream = DeflateImapStream::wrap(Box::new(peer), b""); + let mut got = vec![0u8; 256]; + let mut total = 0usize; + while total < plaintext.len() { + let n = stream.read(&mut got[total..]).unwrap(); + if n == 0 { + break; + } + total += n; + } + assert_eq!(&got[..total], plaintext); + } + + #[test] + fn deflate_consumes_primer_before_reading_inner() { + let mut compressor = flate2::Compress::new(flate2::Compression::default(), false); + let plaintext = b"primed payload\r\n"; + let mut out = vec![0u8; plaintext.len() + 256]; + compressor + .compress(plaintext, &mut out, flate2::FlushCompress::Sync) + .unwrap(); + let n = compressor.total_out() as usize; + let primer = out[..n].to_vec(); + + let peer = Box::new(PeerStream::new()); + let mut stream = DeflateImapStream::wrap(peer, &primer); + let mut buf = vec![0u8; 256]; + let mut total = 0usize; + while total < plaintext.len() { + let n = stream.read(&mut buf[total..]).unwrap(); + if n == 0 { + break; + } + total += n; + } + assert_eq!(&buf[..total], plaintext); + } +} diff --git a/src/inspect/list.rs b/src/inspect/list.rs new file mode 100644 index 0000000..2690b06 --- /dev/null +++ b/src/inspect/list.rs @@ -0,0 +1,631 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::io::Write; + +use rusqlite::Connection; +use serde_json::Value; + +use super::{blob_summary, format_count}; +use crate::error::Error; +use crate::types::ObjectType; + +pub fn write_list( + conn: &Connection, + ty: ObjectType, + limit: Option, + offset: usize, + out: &mut impl Write, +) -> Result<(), Error> { + let table = crate::sync::table_name(ty); + let total: i64 = conn.query_row(&format!("SELECT COUNT(*) FROM {table}"), [], |r| r.get(0))?; + let total = total as u64; + write_header(out, ty, total, limit, offset)?; + if total == 0 { + writeln!(out, "(no {} in archive)", ty.token())?; + return Ok(()); + } + let names = Names::load(conn, ty)?; + let mut written = 0u64; + match ty { + ObjectType::Identity => write_identities(conn, limit, offset, out, &mut written)?, + ObjectType::Email => write_emails(conn, &names, limit, offset, out, &mut written)?, + ObjectType::SieveScript => write_sieves(conn, limit, offset, out, &mut written)?, + ObjectType::AddressBook => write_address_books(conn, limit, offset, out, &mut written)?, + ObjectType::ContactCard => { + write_contact_cards(conn, &names, limit, offset, out, &mut written)? + } + ObjectType::Calendar => write_calendars(conn, limit, offset, out, &mut written)?, + ObjectType::CalendarEvent => { + write_calendar_events(conn, &names, limit, offset, out, &mut written)? + } + ObjectType::ParticipantIdentity => { + write_participant_identities(conn, limit, offset, out, &mut written)? + } + ObjectType::Mailbox | ObjectType::FileNode => unreachable!("tree types do not list"), + } + if let Some(lim) = limit { + let next_offset = (offset as u64) + written; + if next_offset < total { + writeln!( + out, + "... ({} more; --offset {} --limit {} to continue)", + format_count(total - next_offset), + next_offset, + lim + )?; + } + } + Ok(()) +} + +fn write_header( + out: &mut impl Write, + ty: ObjectType, + total: u64, + limit: Option, + offset: usize, +) -> Result<(), Error> { + match limit { + None if offset == 0 => writeln!(out, "{} ({})", ty.jmap_name(), format_count(total))?, + None => writeln!( + out, + "{} ({}, skipping first {})", + ty.jmap_name(), + format_count(total), + format_count(offset as u64) + )?, + Some(lim) => writeln!( + out, + "{} ({} total, showing up to {} from offset {})", + ty.jmap_name(), + format_count(total), + format_count(lim as u64), + format_count(offset as u64) + )?, + } + writeln!(out)?; + Ok(()) +} + +pub(crate) struct Names { + mailboxes: HashMap, + address_books: HashMap, + calendars: HashMap, +} + +impl Names { + pub(crate) fn load(conn: &Connection, ty: ObjectType) -> Result { + let mailboxes = if matches!(ty, ObjectType::Email) { + load_name_map(conn, "SELECT id, name FROM mailboxes")? + } else { + HashMap::new() + }; + let address_books = if matches!(ty, ObjectType::ContactCard) { + load_name_map(conn, "SELECT id, name FROM address_books")? + } else { + HashMap::new() + }; + let calendars = if matches!(ty, ObjectType::CalendarEvent) { + load_name_map(conn, "SELECT id, name FROM calendars")? + } else { + HashMap::new() + }; + Ok(Names { + mailboxes, + address_books, + calendars, + }) + } + + fn render(&self, kind: ObjectType, ids: &[i64]) -> String { + let map = match kind { + ObjectType::Mailbox => &self.mailboxes, + ObjectType::AddressBook => &self.address_books, + ObjectType::Calendar => &self.calendars, + _ => return format!("{ids:?}"), + }; + let parts: Vec = ids + .iter() + .map(|id| match map.get(id) { + Some(name) => format!("{name:?}"), + None => format!("#{id} (missing)"), + }) + .collect(); + format!("[{}]", parts.join(", ")) + } +} + +fn load_name_map(conn: &Connection, sql: &str) -> Result, Error> { + let mut stmt = conn.prepare(sql)?; + let rows = stmt.query_map([], |r| Ok((r.get::<_, i64>(0)?, r.get::<_, String>(1)?)))?; + let mut out = HashMap::new(); + for row in rows { + let (id, name) = row?; + out.insert(id, name); + } + Ok(out) +} + +fn limit_clause(limit: Option, offset: usize) -> String { + match limit { + Some(n) => format!(" LIMIT {n} OFFSET {offset}"), + None if offset > 0 => format!(" LIMIT -1 OFFSET {offset}"), + None => String::new(), + } +} + +fn parse_local_ids(text: &str) -> Vec { + serde_json::from_str::(text) + .ok() + .and_then(|v| { + v.as_array() + .map(|arr| arr.iter().filter_map(|x| x.as_i64()).collect::>()) + }) + .unwrap_or_default() +} + +fn json_pretty_or_raw(text: &str) -> String { + match serde_json::from_str::(text) { + Ok(v) => serde_json::to_string_pretty(&v).unwrap_or_else(|_| text.to_owned()), + Err(_) => text.to_owned(), + } +} + +fn keywords_summary(text: &str) -> String { + let ids = serde_json::from_str::>(text).unwrap_or_default(); + if ids.is_empty() { + "(none)".to_owned() + } else { + format!("[{}]", ids.join(", ")) + } +} + +fn opt_or_null(s: Option) -> String { + s.unwrap_or_else(|| "(null)".to_owned()) +} + +fn bool_str(b: bool) -> String { + (if b { "true" } else { "false" }).to_owned() +} + +fn write_record(out: &mut impl Write, title: &str, fields: &[(&str, String)]) -> Result<(), Error> { + writeln!(out, "{title}")?; + let max_label = fields.iter().map(|(l, _)| l.len()).max().unwrap_or(0); + let pad = " ".repeat(max_label + 4); + for (label, value) in fields { + for (i, line) in value.split('\n').enumerate() { + if i == 0 { + writeln!(out, " {label:, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, name, email, reply_to, bcc, text_signature, html_signature + FROM identities ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let fields = vec![ + ("name", row.get::<_, String>(1)?), + ("email", row.get::<_, String>(2)?), + ("reply_to", opt_or_null(row.get(3)?)), + ("bcc", opt_or_null(row.get(4)?)), + ("text_signature", row.get::<_, String>(5)?), + ("html_signature", row.get::<_, String>(6)?), + ]; + write_record(out, &format!("identity #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_emails( + conn: &Connection, + names: &Names, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, blob_id, received_at, mailbox_ids, keywords, message_match + FROM emails ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let blob_id: i64 = row.get(1)?; + let mailbox_ids: String = row.get(3)?; + let keywords: String = row.get(4)?; + let message_match: String = row.get(5)?; + let locals = parse_local_ids(&mailbox_ids); + let fields = vec![ + ("blob", blob_summary(conn, blob_id)?), + ("received_at", row.get::<_, String>(2)?), + ("mailboxes", names.render(ObjectType::Mailbox, &locals)), + ("keywords", keywords_summary(&keywords)), + ("message_match", json_pretty_or_raw(&message_match)), + ]; + write_record(out, &format!("email #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_sieves( + conn: &Connection, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, name, is_active, blob_id FROM sieve_scripts ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let name: Option = row.get(1)?; + let active: i64 = row.get(2)?; + let blob_id: i64 = row.get(3)?; + let fields = vec![ + ("name", opt_or_null(name)), + ("is_active", bool_str(active != 0)), + ("blob", blob_summary(conn, blob_id)?), + ]; + write_record(out, &format!("sievescript #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_address_books( + conn: &Connection, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, name, description, sort_order, is_default, is_subscribed + FROM address_books ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let fields = vec![ + ("name", row.get::<_, String>(1)?), + ("description", opt_or_null(row.get(2)?)), + ("sort_order", row.get::<_, i64>(3)?.to_string()), + ("is_default", bool_str(row.get::<_, i64>(4)? != 0)), + ("is_subscribed", bool_str(row.get::<_, i64>(5)? != 0)), + ]; + write_record(out, &format!("addressbook #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_contact_cards( + conn: &Connection, + names: &Names, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, uid, address_book_ids, data FROM contact_cards ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let uid: String = row.get(1)?; + let ab_ids: String = row.get(2)?; + let data: String = row.get(3)?; + let locals = parse_local_ids(&ab_ids); + let fields = vec![ + ("uid", uid), + ( + "address_books", + names.render(ObjectType::AddressBook, &locals), + ), + ("data", redact_blobs_in_json(&data)), + ]; + write_record(out, &format!("contactcard #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_calendars( + conn: &Connection, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, name, description, color, sort_order, is_subscribed, is_visible, is_default, + include_in_availability, default_alerts_with_time, default_alerts_without_time, + time_zone + FROM calendars ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let fields = vec![ + ("name", row.get::<_, String>(1)?), + ("description", opt_or_null(row.get(2)?)), + ("color", opt_or_null(row.get(3)?)), + ("sort_order", row.get::<_, i64>(4)?.to_string()), + ("is_subscribed", bool_str(row.get::<_, i64>(5)? != 0)), + ("is_visible", bool_str(row.get::<_, i64>(6)? != 0)), + ("is_default", bool_str(row.get::<_, i64>(7)? != 0)), + ("include_in_availability", row.get::<_, String>(8)?), + ("default_alerts_with_time", opt_or_null(row.get(9)?)), + ("default_alerts_without_time", opt_or_null(row.get(10)?)), + ("time_zone", opt_or_null(row.get(11)?)), + ]; + write_record(out, &format!("calendar #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_calendar_events( + conn: &Connection, + names: &Names, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, calendar_ids, is_draft, use_default_alerts, data + FROM calendar_events ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let cal_ids: String = row.get(1)?; + let data: String = row.get(4)?; + let locals = parse_local_ids(&cal_ids); + let fields = vec![ + ("calendars", names.render(ObjectType::Calendar, &locals)), + ("is_draft", bool_str(row.get::<_, i64>(2)? != 0)), + ("use_default_alerts", bool_str(row.get::<_, i64>(3)? != 0)), + ("data", redact_blobs_in_json(&data)), + ]; + write_record(out, &format!("calendarevent #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn write_participant_identities( + conn: &Connection, + limit: Option, + offset: usize, + out: &mut impl Write, + written: &mut u64, +) -> Result<(), Error> { + let sql = format!( + "SELECT id, name, calendar_address, is_default + FROM participant_identities ORDER BY id{}", + limit_clause(limit, offset) + ); + let mut stmt = conn.prepare(&sql)?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let id: i64 = row.get(0)?; + let fields = vec![ + ("name", row.get::<_, String>(1)?), + ("calendar_address", row.get::<_, String>(2)?), + ("is_default", bool_str(row.get::<_, i64>(3)? != 0)), + ]; + write_record(out, &format!("participantidentity #{id}"), &fields)?; + *written += 1; + } + Ok(()) +} + +fn redact_blobs_in_json(text: &str) -> String { + let mut value = match serde_json::from_str::(text) { + Ok(v) => v, + Err(_) => return text.to_owned(), + }; + redact_blobs(&mut value); + serde_json::to_string_pretty(&value).unwrap_or_else(|_| text.to_owned()) +} + +fn redact_blobs(value: &mut Value) { + match value { + Value::Object(map) => { + if let Some(bid) = map.get("@blob").and_then(|v| v.as_i64()) { + map.clear(); + map.insert( + "@blob".to_owned(), + Value::String(format!("local #{bid} (omitted)")), + ); + return; + } + for v in map.values_mut() { + redact_blobs(v); + } + } + Value::Array(arr) => { + for v in arr.iter_mut() { + redact_blobs(v); + } + } + _ => {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::{blobs, init}; + use rusqlite::params; + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + fn add_mailbox(c: &Connection, name: &str) -> i64 { + c.execute("INSERT INTO mailboxes (name) VALUES (?1)", params![name]) + .unwrap(); + c.last_insert_rowid() + } + + #[test] + fn empty_table_prints_none_message() { + let c = mem(); + let mut buf = Vec::new(); + write_list(&c, ObjectType::Identity, None, 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("Identity (0)")); + assert!(s.contains("(no identity in archive)")); + } + + #[test] + fn identity_record_uses_aligned_fields_and_redacts_nothing() { + let c = mem(); + c.execute( + "INSERT INTO identities (name, email, text_signature) VALUES ('Alice', 'a@x.test', 'best,A')", + [], + ) + .unwrap(); + let mut buf = Vec::new(); + write_list(&c, ObjectType::Identity, None, 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("identity #1")); + assert!(s.contains(" name Alice"), "got:\n{s}"); + assert!(s.contains(" email a@x.test")); + assert!(s.contains(" bcc (null)")); + } + + #[test] + fn email_resolves_mailbox_names_and_summarises_blob() { + let c = mem(); + let inbox = add_mailbox(&c, "Inbox"); + let other = add_mailbox(&c, "Personal"); + let blob = blobs::intern_blob(&c, b"From: a@x.test\r\nSubject: hi\r\n\r\nhello").unwrap(); + c.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords) + VALUES (?1, '2024-01-01T00:00:00Z', ?2, ?3)", + params![ + blob, + format!("[{inbox},{other}]"), + "[\"$seen\",\"Important\"]" + ], + ) + .unwrap(); + let mut buf = Vec::new(); + write_list(&c, ObjectType::Email, None, 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("email #1")); + assert!(s.contains("blob ")); + assert!(s.contains("blake3=")); + assert!(s.contains("mailboxes ")); + assert!(s.contains("\"Inbox\""), "got:\n{s}"); + assert!(s.contains("\"Personal\"")); + assert!(s.contains("keywords ")); + assert!(s.contains("[$seen, Important]")); + } + + #[test] + fn contact_card_redacts_blob_sentinel_in_data() { + let c = mem(); + c.execute("INSERT INTO address_books (name) VALUES ('Personal')", []) + .unwrap(); + c.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![ + "urn:uuid:1", + "[1]", + r#"{"name":{"full":"Alice"},"photos":{"p":{"@blob":17}}}"# + ], + ) + .unwrap(); + let mut buf = Vec::new(); + write_list(&c, ObjectType::ContactCard, None, 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("contactcard #1")); + assert!(s.contains("\"Personal\"")); + assert!(s.contains("local #17 (omitted)")); + assert!(!s.contains("\"@blob\": 17")); + } + + #[test] + fn pagination_writes_continuation_hint_when_more_remain() { + let c = mem(); + for i in 0..5 { + c.execute( + "INSERT INTO identities (name, email) VALUES (?1, ?2)", + params![format!("u{i}"), format!("u{i}@x.test")], + ) + .unwrap(); + } + let mut buf = Vec::new(); + write_list(&c, ObjectType::Identity, Some(2), 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("Identity (5 total, showing up to 2 from offset 0)")); + assert!(s.contains("identity #1")); + assert!(s.contains("identity #2")); + assert!(!s.contains("identity #3")); + assert!(s.contains("... (3 more; --offset 2 --limit 2 to continue)")); + } + + #[test] + fn pagination_at_end_does_not_print_continuation() { + let c = mem(); + for i in 0..3 { + c.execute( + "INSERT INTO identities (name, email) VALUES (?1, ?2)", + params![format!("u{i}"), format!("u{i}@x.test")], + ) + .unwrap(); + } + let mut buf = Vec::new(); + write_list(&c, ObjectType::Identity, Some(5), 0, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(!s.contains("more;"), "got:\n{s}"); + } +} diff --git a/src/inspect/mod.rs b/src/inspect/mod.rs new file mode 100644 index 0000000..f09e273 --- /dev/null +++ b/src/inspect/mod.rs @@ -0,0 +1,285 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +use rusqlite::{Connection, OptionalExtension, params}; + +use crate::db::init; +use crate::error::Error; +use crate::types::ObjectType; + +pub mod list; +pub mod tree; + +pub struct InspectConfig { + pub archive: PathBuf, + pub target: Option, + pub limit: Option, + pub offset: usize, +} + +pub fn run(config: InspectConfig) -> Result<(), Error> { + let conn = init::open(&config.archive)?; + let stdout = io::stdout(); + let mut out = stdout.lock(); + dispatch(&conn, &config, &mut out)?; + Ok(()) +} + +fn dispatch(conn: &Connection, cfg: &InspectConfig, out: &mut impl Write) -> Result<(), Error> { + match cfg.target { + None => write_summary(conn, &cfg.archive, out), + Some(ObjectType::Mailbox) => { + warn_if_paged(out, cfg, "mailbox")?; + tree::write_mailboxes(conn, out) + } + Some(ObjectType::FileNode) => { + warn_if_paged(out, cfg, "filenode")?; + tree::write_file_nodes(conn, out) + } + Some(ty) => list::write_list(conn, ty, cfg.limit, cfg.offset, out), + } +} + +fn warn_if_paged(out: &mut impl Write, cfg: &InspectConfig, token: &str) -> Result<(), Error> { + if cfg.limit.is_some() || cfg.offset > 0 { + writeln!( + out, + "note: --limit/--offset are ignored for {token} (tree view always shows the full hierarchy)" + )?; + } + Ok(()) +} + +fn write_summary(conn: &Connection, archive: &Path, out: &mut impl Write) -> Result<(), Error> { + writeln!(out, "Archive: {}", archive.display())?; + match read_source(conn)? { + Some(src) => { + let name = src.account_name.unwrap_or_else(|| "(unnamed)".to_owned()); + writeln!( + out, + "Source: {} {} (account {} / {})", + src.kind, src.session_url, src.account_id, name + )?; + } + None => writeln!(out, "Source: (no source recorded)")?, + } + writeln!(out)?; + let counts = read_type_counts(conn)?; + let label_width = ObjectType::ALL + .iter() + .map(|t| t.token().len()) + .max() + .unwrap_or(0); + let count_width = counts + .values() + .map(|n| format_count(*n).len()) + .max() + .unwrap_or(1) + .max("blobs".len()); + for ty in ObjectType::ALL { + let n = counts.get(&ty).copied().unwrap_or(0); + writeln!( + out, + " {:cw$}", + ty.token(), + format_count(n), + lw = label_width, + cw = count_width + )?; + } + let (blob_count, blob_bytes) = read_blob_totals(conn)?; + writeln!(out)?; + writeln!( + out, + " {:cw$} ({})", + "blobs", + format_count(blob_count), + format_bytes(blob_bytes), + lw = label_width, + cw = count_width + )?; + Ok(()) +} + +struct SourceRow { + kind: String, + session_url: String, + account_id: String, + account_name: Option, +} + +fn read_source(conn: &Connection) -> Result, Error> { + let row = conn + .query_row( + "SELECT kind, session_url, account_id, account_name FROM sources LIMIT 1", + [], + |r| { + Ok(SourceRow { + kind: r.get(0)?, + session_url: r.get(1)?, + account_id: r.get(2)?, + account_name: r.get(3)?, + }) + }, + ) + .optional()?; + Ok(row) +} + +fn read_type_counts(conn: &Connection) -> Result, Error> { + let mut out = HashMap::new(); + for ty in ObjectType::ALL { + let sql = format!("SELECT COUNT(*) FROM {}", crate::sync::table_name(ty)); + let n: i64 = conn.query_row(&sql, [], |r| r.get(0))?; + out.insert(ty, n as u64); + } + Ok(out) +} + +fn read_blob_totals(conn: &Connection) -> Result<(u64, u64), Error> { + let (count, bytes): (i64, i64) = conn.query_row( + "SELECT COUNT(*), COALESCE(SUM(length(data)), 0) FROM blobs", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + )?; + Ok((count as u64, bytes.max(0) as u64)) +} + +pub(crate) fn format_count(n: u64) -> String { + let s = n.to_string(); + let bytes = s.as_bytes(); + let len = bytes.len(); + let mut out = String::with_capacity(len + len / 3); + for (i, ch) in bytes.iter().enumerate() { + if i > 0 && (len - i).is_multiple_of(3) { + out.push(','); + } + out.push(*ch as char); + } + out +} + +pub(crate) fn format_bytes(n: u64) -> String { + const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"]; + if n < 1024 { + return format!("{n} B"); + } + let mut value = n as f64; + let mut i = 0; + while value >= 1024.0 && i + 1 < UNITS.len() { + value /= 1024.0; + i += 1; + } + format!("{value:.1} {}", UNITS[i]) +} + +pub(crate) fn blob_summary(conn: &Connection, blob_id: i64) -> Result { + let row = conn + .query_row( + "SELECT length(data), substr(lower(hex(hash)), 1, 12) FROM blobs WHERE id = ?1", + params![blob_id], + |r| { + let len: i64 = r.get(0)?; + let hex: String = r.get(1)?; + Ok((len, hex)) + }, + ) + .optional()?; + match row { + Some((len, hex)) => Ok(format!("{} blake3={hex}", format_bytes(len.max(0) as u64))), + None => Ok(format!("(missing blob #{blob_id})")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + #[test] + fn format_count_inserts_grouping_commas() { + assert_eq!(format_count(0), "0"); + assert_eq!(format_count(42), "42"); + assert_eq!(format_count(999), "999"); + assert_eq!(format_count(1_000), "1,000"); + assert_eq!(format_count(12_345), "12,345"); + assert_eq!(format_count(1_234_567_890), "1,234,567,890"); + } + + #[test] + fn format_bytes_picks_appropriate_unit() { + assert_eq!(format_bytes(0), "0 B"); + assert_eq!(format_bytes(512), "512 B"); + assert_eq!(format_bytes(1024), "1.0 KB"); + assert_eq!(format_bytes(1_536), "1.5 KB"); + assert_eq!(format_bytes(10 * 1024 * 1024), "10.0 MB"); + } + + #[test] + fn blob_summary_reports_size_and_short_hash() { + let c = mem(); + let id = crate::db::blobs::intern_blob(&c, b"hello world").unwrap(); + let s = blob_summary(&c, id).unwrap(); + assert!(s.starts_with("11 B blake3="), "got {s}"); + assert!(s.len() > "11 B blake3=".len()); + } + + #[test] + fn blob_summary_handles_missing_id() { + let c = mem(); + let s = blob_summary(&c, 9_999).unwrap(); + assert_eq!(s, "(missing blob #9999)"); + } + + #[test] + fn summary_lists_every_type_and_blob_totals() { + let c = mem(); + crate::db::sources::upsert_source( + &c, + &crate::db::sources::SourceKey { + kind: "jmap".to_owned(), + session_url: "https://example.test/jmap".to_owned(), + account_id: "u-1".to_owned(), + }, + Some("alice@example.test"), + "alice@example.test", + ) + .unwrap(); + c.execute("INSERT INTO mailboxes (name) VALUES (?1)", params!["Inbox"]) + .unwrap(); + crate::db::blobs::intern_blob(&c, b"some bytes").unwrap(); + let mut buf = Vec::new(); + write_summary(&c, Path::new("test.sqlite"), &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("Archive: test.sqlite")); + assert!(s.contains("Source: jmap https://example.test/jmap")); + assert!(s.contains("alice@example.test")); + for ty in ObjectType::ALL { + assert!(s.contains(ty.token()), "missing {} in:\n{s}", ty.token()); + } + assert!(s.contains("blobs")); + assert!(s.contains("10 B")); + } + + #[test] + fn summary_without_source_reports_none() { + let c = mem(); + let mut buf = Vec::new(); + write_summary(&c, Path::new("x.sqlite"), &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("(no source recorded)")); + } +} diff --git a/src/inspect/tree.rs b/src/inspect/tree.rs new file mode 100644 index 0000000..5eed42f --- /dev/null +++ b/src/inspect/tree.rs @@ -0,0 +1,327 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::io::Write; + +use rusqlite::Connection; + +use super::{blob_summary, format_count}; +use crate::error::Error; + +struct MailboxNode { + id: i64, + parent_id: Option, + name: String, + role: Option, + sort_order: i64, + msg_count: u64, +} + +pub fn write_mailboxes(conn: &Connection, out: &mut impl Write) -> Result<(), Error> { + let nodes = load_mailboxes(conn)?; + writeln!(out, "Mailboxes ({})", format_count(nodes.len() as u64))?; + if nodes.is_empty() { + writeln!(out, "(none)")?; + return Ok(()); + } + let by_id: HashMap = nodes.iter().map(|n| (n.id, n)).collect(); + let mut by_parent: HashMap, Vec> = HashMap::new(); + for n in &nodes { + by_parent.entry(n.parent_id).or_default().push(n.id); + } + for ids in by_parent.values_mut() { + ids.sort_by(|a, b| { + let na = by_id[a]; + let nb = by_id[b]; + na.sort_order + .cmp(&nb.sort_order) + .then_with(|| na.name.cmp(&nb.name)) + }); + } + let roots: Vec = by_parent.get(&None).cloned().unwrap_or_default(); + let last = roots.len().saturating_sub(1); + for (i, id) in roots.iter().enumerate() { + write_mailbox(out, &by_id, &by_parent, *id, "", i == last)?; + } + Ok(()) +} + +fn write_mailbox( + out: &mut impl Write, + by_id: &HashMap, + by_parent: &HashMap, Vec>, + id: i64, + prefix: &str, + is_last: bool, +) -> Result<(), Error> { + let node = by_id[&id]; + let connector = if is_last { "└── " } else { "├── " }; + let mut tags: Vec = Vec::new(); + if let Some(role) = &node.role { + tags.push(format!("role={role}")); + } + tags.push(format!("{} msgs", format_count(node.msg_count))); + writeln!( + out, + "{prefix}{connector}{} [{}]", + node.name, + tags.join(", ") + )?; + let child_prefix = format!("{prefix}{}", if is_last { " " } else { "│ " }); + if let Some(children) = by_parent.get(&Some(id)) { + let last = children.len() - 1; + for (i, child) in children.iter().enumerate() { + write_mailbox(out, by_id, by_parent, *child, &child_prefix, i == last)?; + } + } + Ok(()) +} + +fn load_mailboxes(conn: &Connection) -> Result, Error> { + let mut stmt = conn.prepare( + "SELECT m.id, m.parent_id, m.name, m.role, m.sort_order, + (SELECT COUNT(*) FROM emails e + WHERE EXISTS (SELECT 1 FROM json_each(e.mailbox_ids) j + WHERE CAST(j.value AS INTEGER) = m.id)) + FROM mailboxes m", + )?; + let rows = stmt.query_map([], |r| { + Ok(MailboxNode { + id: r.get(0)?, + parent_id: r.get(1)?, + name: r.get(2)?, + role: r.get(3)?, + sort_order: r.get(4)?, + msg_count: r.get::<_, i64>(5)? as u64, + }) + })?; + let mut out = Vec::new(); + for row in rows { + out.push(row?); + } + Ok(out) +} + +struct FileNodeRow { + id: i64, + parent_id: Option, + node_type: String, + blob_id: Option, + name: String, + media_type: Option, + target: Option, + child_count: u64, +} + +pub fn write_file_nodes(conn: &Connection, out: &mut impl Write) -> Result<(), Error> { + let nodes = load_file_nodes(conn)?; + writeln!(out, "File nodes ({})", format_count(nodes.len() as u64))?; + if nodes.is_empty() { + writeln!(out, "(none)")?; + return Ok(()); + } + let by_id: HashMap = nodes.iter().map(|n| (n.id, n)).collect(); + let mut by_parent: HashMap, Vec> = HashMap::new(); + for n in &nodes { + by_parent.entry(n.parent_id).or_default().push(n.id); + } + for ids in by_parent.values_mut() { + ids.sort_by(|a, b| { + let na = by_id[a]; + let nb = by_id[b]; + (na.node_type != "directory") + .cmp(&(nb.node_type != "directory")) + .then_with(|| na.name.cmp(&nb.name)) + }); + } + let roots: Vec = by_parent.get(&None).cloned().unwrap_or_default(); + let last = roots.len().saturating_sub(1); + for (i, id) in roots.iter().enumerate() { + write_file_node(conn, out, &by_id, &by_parent, *id, "", i == last)?; + } + Ok(()) +} + +fn write_file_node( + conn: &Connection, + out: &mut impl Write, + by_id: &HashMap, + by_parent: &HashMap, Vec>, + id: i64, + prefix: &str, + is_last: bool, +) -> Result<(), Error> { + let node = by_id[&id]; + let connector = if is_last { "└── " } else { "├── " }; + let display = match node.node_type.as_str() { + "directory" => format!("{}/", node.name), + _ => node.name.clone(), + }; + let detail = match node.node_type.as_str() { + "directory" => format!("({} children)", format_count(node.child_count)), + "symlink" => { + let target = node.target.as_deref().unwrap_or("[]"); + format!("symlink -> {target}") + } + _ => { + let media = node + .media_type + .as_deref() + .unwrap_or("application/octet-stream"); + match node.blob_id { + Some(bid) => format!("{media}, {}", blob_summary(conn, bid)?), + None => format!("{media}, (no blob)"), + } + } + }; + writeln!(out, "{prefix}{connector}{display} {detail}")?; + let child_prefix = format!("{prefix}{}", if is_last { " " } else { "│ " }); + if let Some(children) = by_parent.get(&Some(id)) { + let last = children.len() - 1; + for (i, child) in children.iter().enumerate() { + write_file_node( + conn, + out, + by_id, + by_parent, + *child, + &child_prefix, + i == last, + )?; + } + } + Ok(()) +} + +fn load_file_nodes(conn: &Connection) -> Result, Error> { + let mut stmt = conn.prepare( + "SELECT f.id, f.parent_id, f.node_type, f.blob_id, f.name, f.media_type, f.target, + (SELECT COUNT(*) FROM file_nodes c WHERE c.parent_id = f.id) + FROM file_nodes f", + )?; + let rows = stmt.query_map([], |r| { + Ok(FileNodeRow { + id: r.get(0)?, + parent_id: r.get(1)?, + node_type: r.get(2)?, + blob_id: r.get(3)?, + name: r.get(4)?, + media_type: r.get(5)?, + target: r.get(6)?, + child_count: r.get::<_, i64>(7)? as u64, + }) + })?; + let mut out = Vec::new(); + for row in rows { + out.push(row?); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::{blobs, init}; + use rusqlite::params; + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + fn insert_mailbox( + c: &Connection, + name: &str, + parent: Option, + role: Option<&str>, + sort: i64, + ) -> i64 { + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, ?4, 1)", + params![name, parent, role, sort], + ) + .unwrap(); + c.last_insert_rowid() + } + + fn insert_email_in(c: &Connection, mailbox_id: i64) { + let blob = blobs::intern_blob(c, format!("seed-{mailbox_id}").as_bytes()).unwrap(); + c.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords) + VALUES (?1, '2024-01-01T00:00:00Z', ?2, '[]')", + params![blob, format!("[{mailbox_id}]")], + ) + .unwrap(); + } + + #[test] + fn mailbox_tree_draws_box_characters_and_msg_counts() { + let c = mem(); + let inbox = insert_mailbox(&c, "Inbox", None, Some("inbox"), 0); + let _archived = insert_mailbox(&c, "Archived", Some(inbox), None, 0); + let _y2024 = insert_mailbox(&c, "2024", Some(inbox), None, 1); + let _sent = insert_mailbox(&c, "Sent", None, Some("sent"), 1); + insert_email_in(&c, inbox); + insert_email_in(&c, inbox); + let mut buf = Vec::new(); + write_mailboxes(&c, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("Mailboxes (4)"), "header missing: {s}"); + assert!(s.contains("├── Inbox [role=inbox, 2 msgs]"), "got:\n{s}"); + assert!(s.contains("│ ├── Archived"), "got:\n{s}"); + assert!(s.contains("│ └── 2024"), "got:\n{s}"); + assert!(s.contains("└── Sent [role=sent, 0 msgs]"), "got:\n{s}"); + } + + #[test] + fn mailbox_tree_empty_says_none() { + let c = mem(); + let mut buf = Vec::new(); + write_mailboxes(&c, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("Mailboxes (0)")); + assert!(s.contains("(none)")); + } + + #[test] + fn file_node_tree_distinguishes_dirs_files_symlinks() { + let c = mem(); + let docs: i64 = { + c.execute( + "INSERT INTO file_nodes (parent_id, node_type, name, created) + VALUES (NULL, 'directory', 'Documents', '2024-01-01T00:00:00Z')", + [], + ) + .unwrap(); + c.last_insert_rowid() + }; + let blob = blobs::intern_blob(&c, b"hello world").unwrap(); + c.execute( + "INSERT INTO file_nodes (parent_id, node_type, blob_id, name, media_type, created) + VALUES (?1, 'file', ?2, 'note.txt', 'text/plain', '2024-01-01T00:00:00Z')", + params![docs, blob], + ) + .unwrap(); + c.execute( + "INSERT INTO file_nodes (parent_id, node_type, target, name, created) + VALUES (?1, 'symlink', '[\"..\",\"etc\"]', 'shortcut', '2024-01-01T00:00:00Z')", + params![docs], + ) + .unwrap(); + let mut buf = Vec::new(); + write_file_nodes(&c, &mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert!(s.contains("File nodes (3)")); + assert!(s.contains("Documents/")); + assert!(s.contains("(2 children)")); + assert!(s.contains("note.txt")); + assert!(s.contains("text/plain, 11 B blake3=")); + assert!(s.contains("symlink -> [\"..\",\"etc\"]")); + } +} diff --git a/src/jmap/account.rs b/src/jmap/account.rs new file mode 100644 index 0000000..12e2cd8 --- /dev/null +++ b/src/jmap/account.rs @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Value, json}; + +use crate::error::Error; +use crate::jmap::http::HttpClient; +use crate::jmap::request::{Request, check_method_error}; +use crate::jmap::session::Session; + +#[derive(Debug, Clone)] +pub enum AccountSelector { + Id(String), + Name(String), +} + +const OWNER_URN: &str = "urn:ietf:params:jmap:principals:owner"; + +pub fn resolve( + selector: &AccountSelector, + session: &Session, + client: &HttpClient, +) -> Result { + match selector { + AccountSelector::Id(id) => Ok(id.clone()), + AccountSelector::Name(name) => { + for (id, account) in &session.accounts { + if account.name == *name { + return Ok(id.clone()); + } + } + resolve_via_principal(name, session, client) + } + } +} + +fn resolve_via_principal( + name: &str, + session: &Session, + client: &HttpClient, +) -> Result { + let mut req = Request::new(); + req.call( + "Principal/query", + json!({ "filter": { "name": name } }), + "q", + ); + req.call( + "Principal/get", + json!({ + "#ids": { "resultOf": "q", "name": "Principal/query", "path": "/ids" }, + "properties": ["id", "name", "accounts"] + }), + "g", + ); + let resp = req + .send(client, &session.api_url) + .map_err(|e| Error::Account(format!("principal resolution request failed: {e}")))?; + + let query = resp + .by_call_id("q") + .map_err(|e| Error::Account(e.to_string()))?; + check_method_error(query).map_err(|e| Error::Account(e.to_string()))?; + let get = resp + .by_call_id("g") + .map_err(|e| Error::Account(e.to_string()))?; + check_method_error(get).map_err(|e| Error::Account(e.to_string()))?; + + let list = get + .args + .get("list") + .and_then(Value::as_array) + .ok_or_else(|| Error::Account("Principal/get returned no list".to_owned()))?; + + let mut matches: Vec<&Value> = list + .iter() + .filter(|p| { + p.get("name") + .and_then(Value::as_str) + .map(|n| n == name) + .unwrap_or(false) + }) + .collect(); + + match matches.len() { + 0 => Err(Error::Account(format!( + "account name '{name}' not found via Principal/query" + ))), + 1 => extract_account_id(matches.remove(0), name), + _ => { + let ids: Vec = matches + .iter() + .filter_map(|p| p.get("id").and_then(Value::as_str).map(str::to_owned)) + .collect(); + Err(Error::Account(format!( + "account name '{name}' is ambiguous; candidate principals: {}", + ids.join(", ") + ))) + } + } +} + +fn extract_account_id(principal: &Value, name: &str) -> Result { + if let Some(accounts) = principal.get("accounts").and_then(Value::as_object) { + for data in accounts.values() { + if let Some(owner) = data.get(OWNER_URN) + && let Some(id) = owner.get("accountIdForPrincipal").and_then(Value::as_str) + { + return Ok(id.to_owned()); + } + } + for data in accounts.values() { + if let Some(cap) = data.as_object() { + for v in cap.values() { + if let Some(id) = v.get("accountId").and_then(Value::as_str) { + return Ok(id.to_owned()); + } + } + } + } + } + principal + .get("id") + .and_then(Value::as_str) + .map(str::to_owned) + .ok_or_else(|| { + Error::Account(format!( + "principal for '{name}' has no resolvable data account id" + )) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn session_with(name: &str, id: &str) -> Session { + let raw = serde_json::json!({ + "apiUrl": "https://example.org/api", + "uploadUrl": "https://example.org/upload", + "downloadUrl": "https://example.org/download", + "capabilities": {}, + "accounts": { id: { "name": name } } + }); + serde_json::from_value(raw).unwrap() + } + + fn client() -> HttpClient { + HttpClient::new( + crate::jmap::http::Auth::Bearer { token: "t".into() }, + crate::jmap::http::RetryPolicy::new(0), + false, + ) + } + + #[test] + fn id_selector_is_verbatim() { + let s = session_with("alice", "a"); + assert_eq!( + resolve(&AccountSelector::Id("zzz".into()), &s, &client()).unwrap(), + "zzz" + ); + } + + #[test] + fn name_selector_exact_case_sensitive_match() { + let s = session_with("alice@example.org", "w"); + assert_eq!( + resolve( + &AccountSelector::Name("alice@example.org".into()), + &s, + &client() + ) + .unwrap(), + "w" + ); + } + + #[test] + fn substring_near_match_is_rejected_exactly() { + let list = json!([ + { "id": "p1", "name": "alice2", "accounts": {} }, + { "id": "p2", "name": "alice", "accounts": { + "w": { "urn:ietf:params:jmap:principals:owner": + { "accountIdForPrincipal": "w" } } } } + ]); + let matches: Vec<&Value> = list + .as_array() + .unwrap() + .iter() + .filter(|p| p.get("name").and_then(Value::as_str) == Some("alice")) + .collect(); + assert_eq!(matches.len(), 1); + assert_eq!(extract_account_id(matches[0], "alice").unwrap(), "w"); + } + + #[test] + fn extract_falls_back_to_principal_id() { + let p = json!({ "id": "acc-7", "name": "bob", "accounts": {} }); + assert_eq!(extract_account_id(&p, "bob").unwrap(), "acc-7"); + } + + #[test] + fn extract_uses_per_capability_account_id() { + let p = json!({ + "id": "px", + "name": "bob", + "accounts": { "z": { "urn:ietf:params:jmap:mail": { "accountId": "data-9" } } } + }); + assert_eq!(extract_account_id(&p, "bob").unwrap(), "data-9"); + } +} diff --git a/src/jmap/blob.rs b/src/jmap/blob.rs new file mode 100644 index 0000000..19714cd --- /dev/null +++ b/src/jmap/blob.rs @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Map, Value}; + +pub const SENTINEL_KEY: &str = "@blob"; + +pub fn import_blob_ids(value: &mut Value, mut resolve: F) -> Result<(), BlobWalkError> +where + F: FnMut(&str) -> Result, +{ + walk_import(value, &mut resolve) +} + +fn walk_import(value: &mut Value, resolve: &mut F) -> Result<(), BlobWalkError> +where + F: FnMut(&str) -> Result, +{ + match value { + Value::Object(map) => { + + if let Some(Value::String(blob_id)) = map.remove("blobId") { + let local = resolve(&blob_id)?; + map.insert(SENTINEL_KEY.to_owned(), Value::from(local)); + } + for child in map.values_mut() { + walk_import(child, resolve)?; + } + } + Value::Array(items) => { + for item in items { + walk_import(item, resolve)?; + } + } + _ => {} + } + Ok(()) +} + +pub fn export_blob_ids(value: &mut Value, mut resolve: F) -> Result<(), BlobWalkError> +where + F: FnMut(i64) -> Result, +{ + walk_export(value, &mut resolve) +} + +fn walk_export(value: &mut Value, resolve: &mut F) -> Result<(), BlobWalkError> +where + F: FnMut(i64) -> Result, +{ + match value { + Value::Object(map) => { + if let Some(sentinel) = map.remove(SENTINEL_KEY) { + let local_id = sentinel.as_i64().ok_or(BlobWalkError::MalformedSentinel)?; + let target_jmap_id = resolve(local_id)?; + map.insert("blobId".to_owned(), Value::String(target_jmap_id)); + } + for child in map.values_mut() { + walk_export(child, resolve)?; + } + } + Value::Array(items) => { + for item in items { + walk_export(item, resolve)?; + } + } + _ => {} + } + Ok(()) +} + +pub fn prepend_property(target: &mut Map, key: &str, value: Value) { + let existing: Vec<(String, Value)> = + target.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + target.clear(); + target.insert(key.to_owned(), value); + for (k, v) in existing { + if k != key { + target.insert(k, v); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum BlobWalkError { + #[error("malformed @blob sentinel: expected integer local id")] + MalformedSentinel, + #[error("blob resolver failed: {0}")] + Resolver(String), +} diff --git a/src/jmap/blobxfer.rs b/src/jmap/blobxfer.rs new file mode 100644 index 0000000..353c532 --- /dev/null +++ b/src/jmap/blobxfer.rs @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::Value; + +use crate::jmap::error::JmapError; +use crate::jmap::http::HttpClient; +use crate::jmap::session::Session; +use crate::jmap::wire::JmapId; + +pub fn upload_bytes( + client: &HttpClient, + session: &Session, + account_id: &str, + content_type: &str, + bytes: &[u8], +) -> Result { + let url = session.upload_url_for(account_id); + let response = client.upload(&url, content_type, bytes)?; + response + .get("blobId") + .and_then(Value::as_str) + .map(|s| JmapId(s.to_owned())) + .ok_or_else(|| JmapError::malformed("upload response has no blobId")) +} + +pub fn download_bytes( + client: &HttpClient, + session: &Session, + account_id: &str, + blob_id: &str, + type_hint: &str, + name: &str, +) -> Result, JmapError> { + let url = session.download_url_for(account_id, blob_id, type_hint, name); + client.download(&url) +} diff --git a/src/jmap/connect.rs b/src/jmap/connect.rs new file mode 100644 index 0000000..ab10fb9 --- /dev/null +++ b/src/jmap/connect.rs @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; +use crate::jmap::account; +use crate::jmap::session::{Limits, Session}; +use crate::sync::{ConnectConfig, Context}; +use crate::types::ObjectType; + +pub struct Connected { + pub session: Session, + pub limits: Limits, + pub account_id: String, + supported: Vec, +} + +impl Connected { + pub fn supports(&self, ty: ObjectType) -> bool { + self.supported.contains(&ty) + } + + pub fn supported_types(&self) -> &[ObjectType] { + &self.supported + } +} + +pub fn prepare(ctx: &Context, connect: &ConnectConfig) -> Result { + ctx.client.set_logger(ctx.common.logger); + + let session = Session::discover(&ctx.client, &connect.url)?; + let limits = session.core_limits()?; + ctx.client.set_limits(&limits); + let account_id = account::resolve(&connect.account, &session, &ctx.client)?; + + if session.account(&account_id).is_none() + && !matches!(connect.account, account::AccountSelector::Name(_)) + { + ctx.common.logger.warn(&format!( + "account id {account_id} is not enumerated in this session; capabilities cannot be gated and every type will be attempted" + )); + } + + let mut supported = Vec::new(); + for ty in ObjectType::ALL { + let urn = ty.capability_urn(); + let known = session.account(&account_id).is_some(); + if !known || session.supports(&account_id, urn) { + supported.push(ty); + } else { + ctx.common.logger.warn(&format!( + "target account does not advertise {urn}; skipping type {}", + ty.jmap_name() + )); + } + } + + Ok(Connected { + session, + limits, + account_id, + supported, + }) +} diff --git a/src/jmap/error.rs b/src/jmap/error.rs new file mode 100644 index 0000000..f014853 --- /dev/null +++ b/src/jmap/error.rs @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; +use crate::jmap::blob::BlobWalkError; + +#[derive(Debug, thiserror::Error)] +pub enum JmapError { + #[error("transport failure: {0}")] + Transport(String), + + #[error("connection failure: {0}")] + Connect(String), + + #[error("authentication rejected: {0}")] + Auth(String), + + #[error("http status {status}: {body}")] + HttpStatus { status: u16, body: String }, + + #[error("retries exhausted: {0}")] + RetriesExhausted(String), + + #[error("request too large")] + RequestTooLarge, + + #[error("single object exceeds the server size limit and cannot be split: {0}")] + SingleObjectTooLarge(String), + + #[error("query anchor not found")] + AnchorNotFound, + + #[error("jmap method error in call {call_id}: {error_type}{}", .description.as_deref().map(|d| format!(" ({d})")).unwrap_or_default())] + Method { + call_id: String, + error_type: String, + description: Option, + }, + + #[error("malformed jmap response: {0}")] + Malformed(String), + + #[error("json error: {0}")] + Json(#[from] serde_json::Error), + + #[error("sqlite error: {0}")] + Sqlite(#[from] rusqlite::Error), + + #[error("blob walk error: {0}")] + Blob(#[from] BlobWalkError), +} + +impl JmapError { + pub fn malformed(context: impl Into) -> JmapError { + JmapError::Malformed(context.into()) + } +} + +impl From for Error { + fn from(value: JmapError) -> Self { + match value { + JmapError::Connect(m) | JmapError::Transport(m) => Error::Connection(m), + JmapError::Auth(m) => Error::Connection(format!("authentication rejected: {m}")), + other => Error::Connection(other.to_string()), + } + } +} diff --git a/src/jmap/http.rs b/src/jmap/http.rs new file mode 100644 index 0000000..1f9555d --- /dev/null +++ b/src/jmap/http.rs @@ -0,0 +1,766 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Arc; +use std::sync::OnceLock; +use std::sync::atomic::{AtomicU8, AtomicU64, Ordering}; +use std::time::{Duration, SystemTime}; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use serde_json::Value; +use ureq::Agent; +use ureq::config::{Config, RedirectAuthHeaders}; +use ureq::tls::{RootCerts, TlsConfig}; + +use crate::jmap::error::JmapError; +use crate::jmap::inflight::{Permit, Semaphore}; +use crate::jmap::retry::{self, Disposition, RateLimitState}; +use crate::jmap::session::Limits; +use crate::logging::{LEVEL_BODIES, LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; + +const MAX_BODY: u64 = 512 * 1024 * 1024; + +const LONG_RETRY_THRESHOLD: Duration = Duration::from_secs(10); + +#[derive(Debug, Clone)] +pub enum Auth { + Basic { user: String, password: String }, + Bearer { token: String }, +} + +impl Auth { + pub fn header_value(&self) -> String { + match self { + Auth::Basic { user, password } => { + format!("Basic {}", STANDARD.encode(format!("{user}:{password}"))) + } + Auth::Bearer { token } => format!("Bearer {token}"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct RetryPolicy { + pub max_retries: u32, + pub base: Duration, + pub cap: Duration, +} + +impl RetryPolicy { + pub fn new(max_retries: u32) -> Self { + RetryPolicy { + max_retries, + base: Duration::from_millis(500), + cap: Duration::from_secs(30), + } + } +} + +struct Inner { + agent: Agent, + auth: Auth, + retry: RetryPolicy, + allow_invalid_certs: bool, + rate_limit: RateLimitState, + log_level: AtomicU8, + requests_gate: OnceLock, + uploads_gate: OnceLock, + max_upload_bytes: AtomicU64, + retries_total: AtomicU64, + retry_after_sleeps: AtomicU64, +} + +#[derive(Debug, Clone, Copy)] +enum Kind { + Api, + Upload, +} + +#[derive(Clone)] +pub struct HttpClient { + inner: Arc, +} + +enum Attempt { + Ok { + status: u16, + body: Vec, + retry_after: Option, + rate_limit_headers: Vec<(String, String)>, + }, + Transport(JmapError), +} + +impl HttpClient { + pub fn new(auth: Auth, retry: RetryPolicy, allow_invalid_certs: bool) -> Self { + let config: Config = Config::builder() + .http_status_as_error(false) + .redirect_auth_headers(RedirectAuthHeaders::SameHost) + .tls_config( + TlsConfig::builder() + .root_certs(RootCerts::PlatformVerifier) + .disable_verification(allow_invalid_certs) + .build(), + ) + .build(); + HttpClient { + inner: Arc::new(Inner { + agent: config.new_agent(), + auth, + retry, + allow_invalid_certs, + rate_limit: RateLimitState::new(), + log_level: AtomicU8::new(LEVEL_DEFAULT), + requests_gate: OnceLock::new(), + uploads_gate: OnceLock::new(), + max_upload_bytes: AtomicU64::new(0), + retries_total: AtomicU64::new(0), + retry_after_sleeps: AtomicU64::new(0), + }), + } + } + + pub fn set_limits(&self, limits: &Limits) { + let _ = self + .inner + .requests_gate + .set(Semaphore::new(limits.max_concurrent_requests)); + let _ = self + .inner + .uploads_gate + .set(Semaphore::new(limits.max_concurrent_upload)); + self.inner + .max_upload_bytes + .store(limits.max_size_upload, Ordering::Relaxed); + } + + pub fn retries_observed(&self) -> u64 { + self.inner.retries_total.load(Ordering::Relaxed) + } + + pub fn retry_after_sleeps(&self) -> u64 { + self.inner.retry_after_sleeps.load(Ordering::Relaxed) + } + + fn acquire_requests(&self) -> Option> { + self.inner.requests_gate.get().map(Semaphore::acquire) + } + + fn acquire_uploads(&self) -> Option> { + self.inner.uploads_gate.get().map(Semaphore::acquire) + } + + pub fn auth(&self) -> &Auth { + &self.inner.auth + } + + pub fn retry(&self) -> &RetryPolicy { + &self.inner.retry + } + + pub fn allow_invalid_certs(&self) -> bool { + self.inner.allow_invalid_certs + } + + pub fn rate_limit(&self) -> &RateLimitState { + &self.inner.rate_limit + } + + pub fn throttle_level(&self) -> u32 { + self.inner.rate_limit.level() + } + + pub fn set_logger(&self, logger: Logger) { + self.inner + .log_level + .store(logger.level(), Ordering::Relaxed); + } + + fn logger(&self) -> Logger { + Logger::new(self.inner.log_level.load(Ordering::Relaxed)) + } + + pub fn get(&self, url: &str) -> Result { + let body = self.execute(Kind::Api, "GET", url, None, None)?; + String::from_utf8(body).map_err(|e| JmapError::Malformed(format!("non-utf8 body: {e}"))) + } + + pub fn post_json(&self, url: &str, body: &Value) -> Result { + let payload = serde_json::to_vec(body)?; + let raw = self.execute( + Kind::Api, + "POST", + url, + Some(&payload), + Some("application/json"), + )?; + serde_json::from_slice(&raw) + .map_err(|e| JmapError::Malformed(format!("response is not valid json: {e}"))) + } + + pub fn upload( + &self, + upload_url: &str, + content_type: &str, + bytes: &[u8], + ) -> Result { + let cap = self.inner.max_upload_bytes.load(Ordering::Relaxed); + if cap > 0 && (bytes.len() as u64) > cap { + return Err(JmapError::SingleObjectTooLarge(format!( + "blob upload of {} bytes exceeds maxSizeUpload ({cap})", + bytes.len() + ))); + } + let raw = self.execute( + Kind::Upload, + "POST", + upload_url, + Some(bytes), + Some(content_type), + )?; + serde_json::from_slice(&raw) + .map_err(|e| JmapError::Malformed(format!("upload response is not valid json: {e}"))) + } + + pub fn download(&self, url: &str) -> Result, JmapError> { + self.execute(Kind::Api, "GET", url, None, None) + } + + fn execute( + &self, + kind: Kind, + method: &str, + url: &str, + body: Option<&[u8]>, + content_type: Option<&str>, + ) -> Result, JmapError> { + let policy = self.inner.retry; + let logger = self.logger(); + let mut attempt: u32 = 0; + loop { + self.inner.rate_limit.cooldown().wait(); + let attempt_outcome = { + let _r = self.acquire_requests(); + let _u = if matches!(kind, Kind::Upload) { + self.acquire_uploads() + } else { + None + }; + self.one_attempt(url, body, content_type) + }; + match attempt_outcome { + Attempt::Ok { status, body, .. } if (200..300).contains(&status) => { + self.inner.rate_limit.on_success(); + return Ok(body); + } + Attempt::Ok { + status, + body, + retry_after, + rate_limit_headers, + } => { + let disposition = self.status_disposition(status, &body); + match disposition { + StatusOutcome::Auth => { + return Err(JmapError::Auth(format!( + "server returned {status}: {}", + truncate(&body) + ))); + } + StatusOutcome::RequestTooLarge => { + return Err(JmapError::RequestTooLarge); + } + StatusOutcome::Fatal => { + return Err(JmapError::HttpStatus { + status, + body: truncate(&body), + }); + } + StatusOutcome::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + let detail = problem_detail(&body); + let headers_blurb = format_rate_headers(&rate_limit_headers); + let reason = match (&detail, headers_blurb.as_deref()) { + (Some(d), Some(h)) => format!("http {status}: {d} [{h}]"), + (Some(d), None) => format!("http {status}: {d}"), + (None, Some(h)) => format!("http {status} [{h}]"), + (None, None) => format!("http {status}"), + }; + if attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!( + "{method} {url} kept returning {reason}" + ))); + } + if retry_after.is_some() { + self.inner + .retry_after_sleeps + .fetch_add(1, Ordering::Relaxed); + } + let delay = self.inner.rate_limit.on_throttle(&policy, retry_after); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "server rate-limited ({reason}); waiting {} before retry {}/{} (shared level {})", + format_retry_wait(delay), + attempt, + policy.max_retries, + self.inner.rate_limit.level(), + )); + } + self.log_retry( + &logger, + RetryLog { + method, + url, + attempt, + delay, + reason: &reason, + body: &body, + }, + ); + std::thread::sleep(delay); + } + } + } + Attempt::Transport(err) => match transport_disposition(&err) { + Disposition::Fatal => return Err(err), + Disposition::Retryable => { + attempt += 1; + self.inner.retries_total.fetch_add(1, Ordering::Relaxed); + if attempt > policy.max_retries { + return Err(JmapError::RetriesExhausted(format!( + "{method} {url}: {err}" + ))); + } + let delay = retry::backoff_delay(&policy, attempt); + if delay >= LONG_RETRY_THRESHOLD { + logger.warn(&format!( + "transient transport failure ({err}); waiting {} before retry {}/{}", + format_retry_wait(delay), + attempt, + policy.max_retries, + )); + } + self.log_retry( + &logger, + RetryLog { + method, + url, + attempt, + delay, + reason: &err.to_string(), + body: &[], + }, + ); + std::thread::sleep(delay); + } + }, + } + } + } + + fn one_attempt(&self, url: &str, body: Option<&[u8]>, content_type: Option<&str>) -> Attempt { + let auth = self.inner.auth.header_value(); + let result = if let Some(payload) = body { + let mut req = self + .inner + .agent + .post(url) + .header("Authorization", auth) + .header("Accept", "application/json"); + if let Some(ct) = content_type { + req = req.header("Content-Type", ct); + } + req.send(payload) + } else { + self.inner + .agent + .get(url) + .header("Authorization", auth) + .header("Accept", "application/json") + .call() + }; + match result { + Ok(mut resp) => { + let status = resp.status().as_u16(); + let retry_after = resp + .headers() + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(retry_after_header); + let rate_limit_headers: Vec<(String, String)> = resp + .headers() + .iter() + .filter_map(|(name, value)| { + let lower = name.as_str().to_ascii_lowercase(); + if matches!( + lower.as_str(), + "retry-after" | "ratelimit" | "ratelimit-policy" | "content-type" + ) { + value.to_str().ok().map(|v| (lower, v.to_owned())) + } else { + None + } + }) + .collect(); + match resp.body_mut().with_config().limit(MAX_BODY).read_to_vec() { + Ok(bytes) => Attempt::Ok { + status, + body: bytes, + retry_after, + rate_limit_headers, + }, + Err(e) => Attempt::Transport(JmapError::Transport(format!( + "reading response body: {e}" + ))), + } + } + Err(e) => Attempt::Transport(map_ureq_error(e)), + } + } + + fn status_disposition(&self, status: u16, body: &[u8]) -> StatusOutcome { + if status == 401 || status == 403 { + return StatusOutcome::Auth; + } + if status == 413 { + return StatusOutcome::RequestTooLarge; + } + if let Ok(problem) = serde_json::from_slice::(body) + && let Some(kind) = problem.get("type").and_then(Value::as_str) + { + if kind.ends_with(":requestTooLarge") { + return StatusOutcome::RequestTooLarge; + } + if kind.ends_with(":limit") { + let which = problem.get("limit").and_then(Value::as_str).unwrap_or(""); + let subtype = problem.get("subType").and_then(Value::as_str).unwrap_or(""); + match which { + "maxSizeRequest" | "maxSizeUpload" => { + return StatusOutcome::RequestTooLarge; + } + "maxConcurrentRequests" | "maxConcurrentUpload" => { + return StatusOutcome::Retryable; + } + _ if subtype == "rateLimit" => { + return StatusOutcome::Retryable; + } + _ => {} + } + } + } + match retry::classify_http_status(status) { + Disposition::Retryable => StatusOutcome::Retryable, + Disposition::Fatal => StatusOutcome::Fatal, + } + } + + fn log_retry(&self, logger: &Logger, r: RetryLog<'_>) { + if logger.enabled(LEVEL_BODIES) { + let snippet = if r.body.is_empty() { + String::new() + } else { + format!(" body={}", truncate(r.body)) + }; + eprintln!( + "retry {}/{} {} {} after {:?} ({}){snippet}", + r.attempt, self.inner.retry.max_retries, r.method, r.url, r.delay, r.reason, + ); + } else if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "retry {}/{} ({})", + r.attempt, self.inner.retry.max_retries, r.reason + ); + } + } +} + +enum StatusOutcome { + Retryable, + Fatal, + Auth, + RequestTooLarge, +} + +struct RetryLog<'a> { + method: &'a str, + url: &'a str, + attempt: u32, + delay: Duration, + reason: &'a str, + body: &'a [u8], +} + +fn transport_disposition(err: &JmapError) -> Disposition { + match err { + JmapError::Connect(_) => Disposition::Fatal, + _ => Disposition::Retryable, + } +} + +fn map_ureq_error(err: ureq::Error) -> JmapError { + match err { + ureq::Error::Io(e) => JmapError::Transport(format!("io: {e}")), + ureq::Error::Timeout(t) => JmapError::Transport(format!("timeout: {t}")), + ureq::Error::HostNotFound => JmapError::Transport("host not found".to_owned()), + ureq::Error::ConnectionFailed => JmapError::Transport("connection failed".to_owned()), + ureq::Error::BodyStalled => JmapError::Transport("body stalled".to_owned()), + ureq::Error::Tls(m) => JmapError::Transport(format!("tls: {m}")), + ureq::Error::TooManyRedirects => JmapError::Connect("too many redirects".to_owned()), + ureq::Error::RedirectFailed => JmapError::Connect("redirect failed".to_owned()), + ureq::Error::TlsRequired => { + JmapError::Connect("server requires TLS but transport is unsecured".to_owned()) + } + other => JmapError::Connect(other.to_string()), + } +} + +fn truncate(body: &[u8]) -> String { + let text = String::from_utf8_lossy(body); + if text.len() <= 512 { + return text.into_owned(); + } + let end = text.floor_char_boundary(512); + format!("{}...", &text[..end]) +} + +pub fn retry_after_header(value: &str) -> Option { + retry::parse_retry_after(value, SystemTime::now()) +} + +fn problem_detail(body: &[u8]) -> Option { + let value: Value = serde_json::from_slice(body).ok()?; + let title = value.get("title").and_then(Value::as_str); + let detail = value.get("detail").and_then(Value::as_str); + match (title, detail) { + (Some(t), Some(d)) => Some(format!("{t}: {d}")), + (Some(t), None) => Some(t.to_owned()), + (None, Some(d)) => Some(d.to_owned()), + (None, None) => None, + } +} + +fn format_rate_headers(headers: &[(String, String)]) -> Option { + if headers.is_empty() { + return None; + } + let mut parts: Vec = headers + .iter() + .filter(|(name, _)| name != "content-type") + .map(|(name, value)| format!("{name}={value}")) + .collect(); + if parts.is_empty() { + return None; + } + parts.sort(); + Some(parts.join("; ")) +} + +fn format_retry_wait(d: Duration) -> String { + let total = d.as_secs(); + if total < 60 { + format!("{total}s") + } else if total < 3600 { + format!("{}m{:02}s", total / 60, total % 60) + } else { + format!( + "{}h{:02}m{:02}s", + total / 3600, + (total % 3600) / 60, + total % 60 + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_header_matches_rfc7617_example() { + let auth = Auth::Basic { + user: "Aladdin".to_owned(), + password: "open sesame".to_owned(), + }; + assert_eq!(auth.header_value(), "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); + } + + #[test] + fn bearer_header() { + let auth = Auth::Bearer { + token: "abc.def".to_owned(), + }; + assert_eq!(auth.header_value(), "Bearer abc.def"); + } + + #[test] + fn truncate_does_not_panic_on_multibyte_boundary() { + let mut body = vec![b'a'; 510]; + body.extend_from_slice("\u{1F4A9}".as_bytes()); + body.extend_from_slice(&[b'b'; 600]); + let s = truncate(&body); + assert!(s.ends_with("...")); + assert!(s.is_char_boundary(s.len())); + let inner = s.trim_end_matches('.'); + assert!(inner.is_char_boundary(inner.len())); + } + + #[test] + fn truncate_short_body_returns_verbatim() { + let s = truncate("héllo".as_bytes()); + assert_eq!(s, "héllo"); + } + + #[test] + fn limit_problem_unknown_kind_on_429_is_retried() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + let body = br#"{"type":"urn:ietf:params:jmap:error:limit","limit":"someServerLimit"}"#; + assert!(matches!( + client.status_disposition(429, body), + StatusOutcome::Retryable + )); + } + + #[test] + fn limit_problem_max_size_request_is_resplit() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + let body = br#"{"type":"urn:ietf:params:jmap:error:limit","limit":"maxSizeRequest"}"#; + assert!(matches!( + client.status_disposition(400, body), + StatusOutcome::RequestTooLarge + )); + } + + #[test] + fn limit_problem_max_concurrent_is_retried() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + let body = + br#"{"type":"urn:ietf:params:jmap:error:limit","limit":"maxConcurrentRequests"}"#; + assert!(matches!( + client.status_disposition(429, body), + StatusOutcome::Retryable + )); + } + + fn limits_with(max_upload: u64, req: u64, up: u64) -> Limits { + Limits { + max_objects_in_get: 500, + max_objects_in_set: 500, + max_calls_in_request: 16, + max_concurrent_requests: req, + max_concurrent_upload: up, + max_size_request: 10_000_000, + max_size_upload: max_upload, + } + } + + #[test] + fn upload_rejects_oversize_before_sending() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + client.set_limits(&limits_with(10, 4, 4)); + let err = client + .upload( + "http://127.0.0.1:1/upload", + "application/octet-stream", + &[0u8; 11], + ) + .unwrap_err(); + assert!( + matches!(err, JmapError::SingleObjectTooLarge(_)), + "got {err:?}" + ); + assert_eq!(client.retries_observed(), 0); + } + + #[test] + fn upload_within_max_size_is_not_short_circuited() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + client.set_limits(&limits_with(1024, 4, 4)); + let err = client + .upload( + "http://127.0.0.1:1/upload", + "application/octet-stream", + b"abc", + ) + .unwrap_err(); + assert!( + !matches!(err, JmapError::SingleObjectTooLarge(_)), + "expected the call to proceed to transport (got SingleObjectTooLarge): {err:?}" + ); + } + + #[test] + fn problem_detail_extracts_title_and_detail() { + let body = br#"{"type":"about:blank","status":429,"title":"Quota exceeded","detail":"You have exceeded the blob upload quota of 1000 files or 50000000 bytes."}"#; + let got = problem_detail(body).expect("title+detail"); + assert!(got.contains("Quota exceeded")); + assert!(got.contains("50000000 bytes")); + } + + #[test] + fn problem_detail_title_only() { + let body = br#"{"title":"Too Many Requests"}"#; + assert_eq!(problem_detail(body).as_deref(), Some("Too Many Requests")); + } + + #[test] + fn problem_detail_returns_none_for_empty_body() { + assert_eq!(problem_detail(b"{}"), None); + assert_eq!(problem_detail(b"not json"), None); + } + + #[test] + fn format_retry_wait_short_medium_long() { + assert_eq!(format_retry_wait(Duration::from_secs(7)), "7s"); + assert_eq!(format_retry_wait(Duration::from_secs(67)), "1m07s"); + assert_eq!(format_retry_wait(Duration::from_secs(1427)), "23m47s"); + assert_eq!(format_retry_wait(Duration::from_secs(3661)), "1h01m01s"); + } + + #[test] + fn counters_start_at_zero() { + let client = HttpClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(0), + false, + ); + assert_eq!(client.retries_observed(), 0); + assert_eq!(client.retry_after_sleeps(), 0); + } +} diff --git a/src/jmap/inflight.rs b/src/jmap/inflight.rs new file mode 100644 index 0000000..67fb001 --- /dev/null +++ b/src/jmap/inflight.rs @@ -0,0 +1,122 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::{Condvar, Mutex}; + +pub struct Semaphore { + state: Mutex, + cond: Condvar, +} + +impl Semaphore { + pub fn new(capacity: u64) -> Semaphore { + Semaphore { + state: Mutex::new(capacity.max(1)), + cond: Condvar::new(), + } + } + + pub fn acquire(&self) -> Permit<'_> { + let mut guard = lock(&self.state); + loop { + if *guard > 0 { + *guard -= 1; + return Permit { sem: self }; + } + guard = wait(&self.cond, guard); + } + } + + fn release(&self) { + let mut guard = lock(&self.state); + *guard = guard.saturating_add(1); + self.cond.notify_one(); + } +} + +pub struct Permit<'a> { + sem: &'a Semaphore, +} + +impl Drop for Permit<'_> { + fn drop(&mut self) { + self.sem.release(); + } +} + +fn lock(m: &Mutex) -> std::sync::MutexGuard<'_, u64> { + match m.lock() { + Ok(g) => g, + Err(p) => p.into_inner(), + } +} + +fn wait<'a>(cv: &Condvar, g: std::sync::MutexGuard<'a, u64>) -> std::sync::MutexGuard<'a, u64> { + match cv.wait(g) { + Ok(g) => g, + Err(p) => p.into_inner(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::thread; + use std::time::{Duration, Instant}; + + #[test] + fn permit_decrements_and_drop_releases() { + let s = Semaphore::new(1); + { + let _p = s.acquire(); + let s2 = &s; + let started = Instant::now(); + thread::scope(|sc| { + let h = sc.spawn(move || { + let _q = s2.acquire(); + started.elapsed() + }); + thread::sleep(Duration::from_millis(80)); + drop(_p); + let waited = h.join().unwrap(); + assert!( + waited >= Duration::from_millis(60), + "second acquirer should have waited until first released, waited {waited:?}" + ); + }); + } + } + + #[test] + fn capacity_bounds_concurrent_acquirers() { + let s = Arc::new(Semaphore::new(2)); + let in_flight = Arc::new(AtomicU64::new(0)); + let max = Arc::new(AtomicU64::new(0)); + let mut handles = Vec::new(); + for _ in 0..8 { + let s = s.clone(); + let inf = in_flight.clone(); + let mx = max.clone(); + handles.push(thread::spawn(move || { + let _p = s.acquire(); + let cur = inf.fetch_add(1, Ordering::SeqCst) + 1; + mx.fetch_max(cur, Ordering::SeqCst); + thread::sleep(Duration::from_millis(30)); + inf.fetch_sub(1, Ordering::SeqCst); + })); + } + for h in handles { + h.join().unwrap(); + } + assert_eq!( + max.load(Ordering::SeqCst), + 2, + "max in-flight must equal capacity" + ); + } +} diff --git a/src/jmap/mod.rs b/src/jmap/mod.rs new file mode 100644 index 0000000..c99cd74 --- /dev/null +++ b/src/jmap/mod.rs @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod account; +pub mod blob; +pub mod blobxfer; +pub mod connect; +pub mod error; +pub mod http; +pub mod inflight; +pub mod request; +pub mod retry; +pub mod session; +pub mod wire; diff --git a/src/jmap/request.rs b/src/jmap/request.rs new file mode 100644 index 0000000..4a63b99 --- /dev/null +++ b/src/jmap/request.rs @@ -0,0 +1,685 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexSet; +use serde::de::DeserializeOwned; +use serde_json::{Map, Value, json}; + +use crate::jmap::error::JmapError; +use crate::jmap::http::HttpClient; +use crate::jmap::session::Limits; +use crate::jmap::wire::JmapId; + +pub const URN_CORE: &str = "urn:ietf:params:jmap:core"; + +pub fn using_urn(method: &str) -> &'static str { + let prefix = method.split('/').next().unwrap_or(method); + match prefix { + "Mailbox" | "Email" | "Identity" => "urn:ietf:params:jmap:mail", + "SieveScript" => "urn:ietf:params:jmap:sieve", + "AddressBook" | "ContactCard" => "urn:ietf:params:jmap:contacts", + "Calendar" | "CalendarEvent" | "ParticipantIdentity" => "urn:ietf:params:jmap:calendars", + "FileNode" => "urn:ietf:params:jmap:filenode", + "Principal" => "urn:ietf:params:jmap:principals", + "x:Account" | "x:Domain" => "urn:stalwart:jmap", + _ => URN_CORE, + } +} + +#[derive(Debug, Clone)] +pub struct MethodCall { + pub name: String, + pub args: Value, + pub call_id: String, +} + +#[derive(Debug, Clone, Default)] +pub struct Request { + calls: Vec, +} + +impl Request { + pub fn new() -> Request { + Request { calls: Vec::new() } + } + + pub fn call( + &mut self, + name: impl Into, + args: Value, + call_id: impl Into, + ) -> &mut Request { + self.calls.push(MethodCall { + name: name.into(), + args, + call_id: call_id.into(), + }); + self + } + + pub fn len(&self) -> usize { + self.calls.len() + } + + pub fn is_empty(&self) -> bool { + self.calls.is_empty() + } + + pub fn using(&self) -> Vec { + let mut set: IndexSet = IndexSet::new(); + set.insert(URN_CORE.to_owned()); + for c in &self.calls { + set.insert(using_urn(&c.name).to_owned()); + } + set.into_iter().collect() + } + + fn envelope(&self) -> Value { + let method_calls: Vec = self + .calls + .iter() + .map(|c| json!([c.name, c.args, c.call_id])) + .collect(); + json!({ "using": self.using(), "methodCalls": method_calls }) + } + + pub fn fits(&self, limits: &Limits) -> Result<(), JmapError> { + if self.calls.len() as u64 > limits.max_calls_in_request { + return Err(JmapError::RequestTooLarge); + } + let size = serde_json::to_vec(&self.envelope())?.len() as u64; + if size > limits.max_size_request { + return Err(JmapError::RequestTooLarge); + } + Ok(()) + } + + pub fn send(&self, client: &HttpClient, api_url: &str) -> Result { + let value = client.post_json(api_url, &self.envelope())?; + Response::parse(value) + } +} + +#[derive(Debug)] +pub struct Response { + pub method_responses: Vec, +} + +impl Response { + fn parse(value: Value) -> Result { + let arr = value + .get("methodResponses") + .and_then(Value::as_array) + .ok_or_else(|| JmapError::malformed("response has no methodResponses array"))?; + let mut out = Vec::with_capacity(arr.len()); + for entry in arr { + let triple = entry.as_array().filter(|t| t.len() == 3).ok_or_else(|| { + JmapError::malformed("methodResponse is not a [name,args,id] triple") + })?; + let name = triple[0] + .as_str() + .ok_or_else(|| JmapError::malformed("methodResponse name is not a string"))? + .to_owned(); + let call_id = triple[2] + .as_str() + .ok_or_else(|| JmapError::malformed("methodResponse callId is not a string"))? + .to_owned(); + out.push(MethodCall { + name, + args: triple[1].clone(), + call_id, + }); + } + Ok(Response { + method_responses: out, + }) + } + + pub fn first(&self) -> Result<&MethodCall, JmapError> { + self.method_responses + .first() + .ok_or_else(|| JmapError::malformed("empty methodResponses")) + } + + pub fn by_call_id(&self, call_id: &str) -> Result<&MethodCall, JmapError> { + self.method_responses + .iter() + .find(|m| m.call_id == call_id) + .ok_or_else(|| JmapError::malformed(format!("no response for callId {call_id}"))) + } +} + +pub fn check_method_error(mr: &MethodCall) -> Result<(), JmapError> { + if mr.name != "error" { + return Ok(()); + } + let error_type = mr + .args + .get("type") + .and_then(Value::as_str) + .unwrap_or("unknown") + .to_owned(); + if error_type == "anchorNotFound" { + return Err(JmapError::AnchorNotFound); + } + let description = mr + .args + .get("description") + .and_then(Value::as_str) + .map(str::to_owned); + Err(JmapError::Method { + call_id: mr.call_id.clone(), + error_type, + description, + }) +} + +fn ids_array(ids: &[JmapId]) -> Value { + Value::Array(ids.iter().map(|i| Value::String(i.0.clone())).collect()) +} + +pub fn query_all_ids( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, + limits: &Limits, +) -> Result, JmapError> { + let mut restarts = 0u32; + loop { + match query_pages(client, api_url, account_id, type_name, limits) { + Ok(ids) => return Ok(ids), + Err(JmapError::AnchorNotFound) if restarts < 2 => { + restarts += 1; + } + Err(e) => return Err(e), + } + } +} + +fn query_pages( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, + limits: &Limits, +) -> Result, JmapError> { + let limit = limits.max_objects_in_get.max(1); + let mut collected: Vec = Vec::new(); + let mut page_size: Option = None; + let mut anchor: Option = None; + + loop { + let mut args = Map::new(); + args.insert("accountId".to_owned(), Value::String(account_id.to_owned())); + args.insert("limit".to_owned(), Value::from(limit)); + if let Some(a) = &anchor { + args.insert("anchor".to_owned(), Value::String(a.clone())); + args.insert("anchorOffset".to_owned(), Value::from(1)); + } + let mut req = Request::new(); + req.call(format!("{type_name}/query"), Value::Object(args), "q"); + let resp = req.send(client, api_url)?; + let mr = resp.first()?; + check_method_error(mr)?; + let ids = mr + .args + .get("ids") + .and_then(Value::as_array) + .ok_or_else(|| JmapError::malformed("query response has no ids array"))?; + let this_len = ids.len(); + for v in ids { + let s = v + .as_str() + .ok_or_else(|| JmapError::malformed("query id is not a string"))?; + collected.push(JmapId(s.to_owned())); + } + match page_size { + None => { + if this_len == 0 { + break; + } + page_size = Some(this_len); + } + Some(full) => { + if this_len == 0 || this_len < full { + break; + } + } + } + let last = collected + .last() + .map(|i| i.0.clone()) + .ok_or_else(|| JmapError::malformed("non-empty page yielded no anchor"))?; + anchor = Some(last); + } + Ok(collected) +} + +#[derive(Debug)] +pub struct GetResult { + pub list: Vec, + pub not_found: Vec, +} + +impl Default for GetResult { + fn default() -> Self { + GetResult { + list: Vec::new(), + not_found: Vec::new(), + } + } +} + +struct GetCtx<'a> { + client: &'a HttpClient, + api_url: &'a str, + account_id: &'a str, + type_name: &'a str, + properties: Option<&'a [&'a str]>, + limits: &'a Limits, +} + +pub fn get_objects( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, + ids: &[JmapId], + properties: Option<&[&str]>, + limits: &Limits, +) -> Result, JmapError> { + let ctx = GetCtx { + client, + api_url, + account_id, + type_name, + properties, + limits, + }; + let mut out = GetResult::default(); + let chunk = limits.max_objects_in_get.max(1) as usize; + let mut start = 0; + while start < ids.len() { + let end = (start + chunk).min(ids.len()); + get_chunk(&ctx, &ids[start..end], &mut out)?; + start = end; + } + Ok(out) +} + +fn get_chunk( + ctx: &GetCtx<'_>, + ids: &[JmapId], + out: &mut GetResult, +) -> Result<(), JmapError> { + let mut args = Map::new(); + args.insert( + "accountId".to_owned(), + Value::String(ctx.account_id.to_owned()), + ); + args.insert("ids".to_owned(), ids_array(ids)); + if let Some(props) = ctx.properties { + let mut p: Vec = props + .iter() + .map(|s| Value::String((*s).to_owned())) + .collect(); + p.push(Value::String("id".to_owned())); + args.insert("properties".to_owned(), Value::Array(p)); + } + let mut req = Request::new(); + req.call(format!("{}/get", ctx.type_name), Value::Object(args), "g"); + let send_result: Result<(), JmapError> = if req.fits(ctx.limits).is_err() { + Err(JmapError::RequestTooLarge) + } else { + match req.send(ctx.client, ctx.api_url) { + Ok(resp) => { + let mr = resp.first()?; + check_method_error(mr)?; + decode_get(mr, out) + } + Err(e) => Err(e), + } + }; + match send_result { + Ok(()) => Ok(()), + Err(JmapError::RequestTooLarge) => { + if ids.len() <= 1 { + return Err(JmapError::SingleObjectTooLarge(format!( + "{}/get of a single id exceeds maxSizeRequest", + ctx.type_name + ))); + } + let mid = ids.len() / 2; + get_chunk(ctx, &ids[..mid], out)?; + get_chunk(ctx, &ids[mid..], out) + } + Err(e) => Err(e), + } +} + +pub fn get_all( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, +) -> Result, JmapError> { + let mut args = Map::new(); + args.insert("accountId".to_owned(), Value::String(account_id.to_owned())); + args.insert("ids".to_owned(), Value::Null); + let mut req = Request::new(); + req.call(format!("{type_name}/get"), Value::Object(args), "g"); + let resp = req.send(client, api_url)?; + let mr = resp.first()?; + check_method_error(mr)?; + let mut out = GetResult::default(); + decode_get(mr, &mut out)?; + Ok(out) +} + +fn decode_get( + mr: &MethodCall, + out: &mut GetResult, +) -> Result<(), JmapError> { + let list = mr + .args + .get("list") + .and_then(Value::as_array) + .ok_or_else(|| JmapError::malformed("get response has no list array"))?; + for item in list { + out.list.push(serde_json::from_value(item.clone())?); + } + if let Some(nf) = mr.args.get("notFound").and_then(Value::as_array) { + for v in nf { + if let Some(s) = v.as_str() { + out.not_found.push(JmapId(s.to_owned())); + } + } + } + Ok(()) +} + +#[derive(Debug, Default)] +pub struct SetOutcome { + pub created: Vec<(String, Value)>, + pub updated: Vec, + pub destroyed: Vec, + pub not_created: Vec<(String, Value)>, + pub not_updated: Vec<(String, Value)>, + pub not_destroyed: Vec<(String, Value)>, +} + +impl SetOutcome { + fn absorb(&mut self, mut other: SetOutcome) { + self.created.append(&mut other.created); + self.updated.append(&mut other.updated); + self.destroyed.append(&mut other.destroyed); + self.not_created.append(&mut other.not_created); + self.not_updated.append(&mut other.not_updated); + self.not_destroyed.append(&mut other.not_destroyed); + } +} + +#[derive(Debug, Default)] +pub struct SetRequest<'a> { + pub create: Option, + pub update: Option, + pub destroy: Option, + pub extra_args: &'a [(&'a str, Value)], +} + +#[derive(Debug, Clone, Copy)] +enum Section { + Create, + Update, + Destroy, +} + +fn collect_items(value: Option<&Value>, section: Section) -> Vec<(Section, String, Value)> { + match (section, value) { + (Section::Destroy, Some(Value::Array(ids))) => ids + .iter() + .filter_map(|v| v.as_str().map(|s| (section, s.to_owned(), Value::Null))) + .collect(), + (_, Some(Value::Object(m))) => m + .iter() + .map(|(k, v)| (section, k.clone(), v.clone())) + .collect(), + _ => Vec::new(), + } +} + +pub fn set_call( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, + body: SetRequest<'_>, + limits: &Limits, +) -> Result { + let mut items = collect_items(body.create.as_ref(), Section::Create); + items.extend(collect_items(body.update.as_ref(), Section::Update)); + items.extend(collect_items(body.destroy.as_ref(), Section::Destroy)); + + if items.is_empty() { + return set_send( + client, + api_url, + account_id, + type_name, + &[], + body.extra_args, + limits, + ); + } + + let chunk = limits.max_objects_in_set.max(1) as usize; + let mut outcome = SetOutcome::default(); + let mut start = 0; + while start < items.len() { + let end = (start + chunk).min(items.len()); + let part = set_send( + client, + api_url, + account_id, + type_name, + &items[start..end], + body.extra_args, + limits, + )?; + outcome.absorb(part); + start = end; + } + Ok(outcome) +} + +fn set_send( + client: &HttpClient, + api_url: &str, + account_id: &str, + type_name: &str, + items: &[(Section, String, Value)], + extra_args: &[(&str, Value)], + limits: &Limits, +) -> Result { + let mut args = Map::new(); + args.insert("accountId".to_owned(), Value::String(account_id.to_owned())); + let mut create = Map::new(); + let mut update = Map::new(); + let mut destroy: Vec = Vec::new(); + for (section, key, val) in items { + match section { + Section::Create => { + create.insert(key.clone(), val.clone()); + } + Section::Update => { + update.insert(key.clone(), val.clone()); + } + Section::Destroy => destroy.push(Value::String(key.clone())), + } + } + if !create.is_empty() { + args.insert("create".to_owned(), Value::Object(create)); + } + if !update.is_empty() { + args.insert("update".to_owned(), Value::Object(update)); + } + if !destroy.is_empty() { + args.insert("destroy".to_owned(), Value::Array(destroy)); + } + for (k, v) in extra_args { + args.insert((*k).to_owned(), v.clone()); + } + let mut req = Request::new(); + req.call(format!("{type_name}/set"), Value::Object(args), "s"); + + let oversize = req.fits(limits).is_err(); + let send_result = if oversize { + Err(JmapError::RequestTooLarge) + } else { + req.send(client, api_url).and_then(|resp| { + let mr = resp.first()?; + check_method_error(mr)?; + Ok(decode_set(mr)) + }) + }; + + match send_result { + Ok(outcome) => Ok(outcome), + Err(JmapError::RequestTooLarge) => { + if items.len() <= 1 { + return Err(JmapError::SingleObjectTooLarge(format!( + "{type_name}/set of a single object exceeds maxSizeRequest" + ))); + } + let mid = items.len() / 2; + let mut left = set_send( + client, + api_url, + account_id, + type_name, + &items[..mid], + extra_args, + limits, + )?; + let right = set_send( + client, + api_url, + account_id, + type_name, + &items[mid..], + extra_args, + limits, + )?; + left.absorb(right); + Ok(left) + } + Err(e) => Err(e), + } +} + +fn pairs(args: &Value, key: &str) -> Vec<(String, Value)> { + args.get(key) + .and_then(Value::as_object) + .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect()) + .unwrap_or_default() +} + +fn keys(args: &Value, key: &str) -> Vec { + args.get(key) + .and_then(Value::as_array) + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(str::to_owned)) + .collect() + }) + .unwrap_or_default() +} + +fn decode_set(mr: &MethodCall) -> SetOutcome { + SetOutcome { + created: pairs(&mr.args, "created"), + updated: mr + .args + .get("updated") + .and_then(Value::as_object) + .map(|m| m.keys().cloned().collect()) + .unwrap_or_default(), + destroyed: keys(&mr.args, "destroyed"), + not_created: pairs(&mr.args, "notCreated"), + not_updated: pairs(&mr.args, "notUpdated"), + not_destroyed: pairs(&mr.args, "notDestroyed"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn using_union_includes_core_and_dedups() { + let mut r = Request::new(); + r.call("Mailbox/get", json!({}), "a"); + r.call("Email/query", json!({}), "b"); + let u = r.using(); + assert!(u.contains(&URN_CORE.to_owned())); + assert!(u.contains(&"urn:ietf:params:jmap:mail".to_owned())); + assert_eq!(u.iter().filter(|x| x.as_str() == URN_CORE).count(), 1); + } + + #[test] + fn using_maps_principal_and_stalwart() { + assert_eq!( + using_urn("Principal/query"), + "urn:ietf:params:jmap:principals" + ); + assert_eq!(using_urn("x:Account/set"), "urn:stalwart:jmap"); + assert_eq!( + using_urn("CalendarEvent/set"), + "urn:ietf:params:jmap:calendars" + ); + } + + #[test] + fn fits_rejects_too_many_calls() { + let limits = Limits { + max_objects_in_get: 10, + max_objects_in_set: 10, + max_calls_in_request: 2, + max_concurrent_requests: 4, + max_concurrent_upload: 4, + max_size_request: 10_000_000, + max_size_upload: 10_000_000, + }; + let mut r = Request::new(); + r.call("Mailbox/get", json!({}), "a"); + r.call("Mailbox/get", json!({}), "b"); + r.call("Mailbox/get", json!({}), "c"); + assert!(matches!(r.fits(&limits), Err(JmapError::RequestTooLarge))); + } + + #[test] + fn parse_response_and_method_error() { + let v = json!({ + "methodResponses": [ + ["Mailbox/get", {"list": [], "notFound": []}, "g"], + ["error", {"type": "invalidArguments"}, "x"] + ] + }); + let r = Response::parse(v).unwrap(); + assert!(check_method_error(r.by_call_id("g").unwrap()).is_ok()); + let err = check_method_error(r.by_call_id("x").unwrap()).unwrap_err(); + assert!(matches!(err, JmapError::Method { .. })); + } + + #[test] + fn anchor_not_found_is_classified() { + let v = json!({ "methodResponses": [["error", {"type": "anchorNotFound"}, "q"]] }); + let r = Response::parse(v).unwrap(); + let err = check_method_error(r.first().unwrap()).unwrap_err(); + assert!(matches!(err, JmapError::AnchorNotFound)); + } +} diff --git a/src/jmap/retry.rs b/src/jmap/retry.rs new file mode 100644 index 0000000..0ab5487 --- /dev/null +++ b/src/jmap/retry.rs @@ -0,0 +1,312 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::cell::Cell; +use std::sync::Mutex; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use crate::jmap::http::RetryPolicy; + +pub const THROTTLE_BASE: Duration = Duration::from_secs(1); +const THROTTLE_LEVEL_CAP: u32 = 16; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Disposition { + Retryable, + Fatal, +} + +pub fn classify_http_status(status: u16) -> Disposition { + match status { + 429 | 502 | 503 | 504 => Disposition::Retryable, + _ => Disposition::Fatal, + } +} + +pub fn jmap_method_disposition(error_type: &str) -> Disposition { + match error_type { + "serverUnavailable" | "serverPartialFail" => Disposition::Retryable, + _ => Disposition::Fatal, + } +} + +pub fn parse_retry_after(value: &str, now: SystemTime) -> Option { + let trimmed = value.trim(); + if let Ok(secs) = trimmed.parse::() { + return Some(Duration::from_secs(secs)); + } + let imf_fixdate = time::macros::format_description!( + "[weekday repr:short], [day] [month repr:short] [year] \ + [hour]:[minute]:[second] GMT" + ); + let parsed = time::PrimitiveDateTime::parse(trimmed, imf_fixdate).ok()?; + let target: SystemTime = parsed.assume_utc().into(); + target.duration_since(now).ok() +} + +thread_local! { + static RNG_STATE: Cell = Cell::new(seed()); +} + +fn seed() -> u64 { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_nanos() as u64) + .unwrap_or(0x9E37_79B9_7F4A_7C15); + let tid = { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + std::hash::Hash::hash(&std::thread::current().id(), &mut hasher); + std::hash::Hasher::finish(&hasher) + }; + (nanos ^ tid).max(1) +} + +fn next_u64() -> u64 { + RNG_STATE.with(|cell| { + let mut x = cell.get(); + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + cell.set(x); + x + }) +} + +fn jitter_uniform(span: Duration) -> Duration { + let nanos = span.as_nanos(); + if nanos == 0 { + return Duration::ZERO; + } + let pick = (next_u64() as u128) % (nanos + 1); + Duration::from_nanos(pick.min(u64::MAX as u128) as u64) +} + +pub fn backoff_delay(policy: &RetryPolicy, attempt: u32) -> Duration { + let shift = attempt.saturating_sub(1).min(32); + let scaled = policy + .base + .saturating_mul(1u32.checked_shl(shift).unwrap_or(u32::MAX)); + let capped = scaled.min(policy.cap); + jitter_uniform(capped) +} + +#[derive(Debug, Default)] +pub struct Cooldown { + until: Mutex>, +} + +impl Cooldown { + pub fn new() -> Cooldown { + Cooldown { + until: Mutex::new(None), + } + } + + pub fn arm(&self, delay: Duration) { + let target = Instant::now() + delay; + if let Ok(mut guard) = self.until.lock() { + let extend = guard.map(|cur| target > cur).unwrap_or(true); + if extend { + *guard = Some(target); + } + } + } + + pub fn remaining(&self) -> Option { + let guard = self.until.lock().ok()?; + let until = (*guard)?; + let now = Instant::now(); + if until > now { Some(until - now) } else { None } + } + + pub fn wait(&self) { + while let Some(remaining) = self.remaining() { + std::thread::sleep(remaining); + } + } +} + +#[derive(Debug, Default)] +pub struct RateLimitState { + cooldown: Cooldown, + level: AtomicU32, +} + +impl RateLimitState { + pub fn new() -> RateLimitState { + RateLimitState { + cooldown: Cooldown::new(), + level: AtomicU32::new(0), + } + } + + pub fn cooldown(&self) -> &Cooldown { + &self.cooldown + } + + pub fn level(&self) -> u32 { + self.level.load(Ordering::Relaxed) + } + + pub fn on_success(&self) { + self.level.store(0, Ordering::Relaxed); + } + + pub fn on_throttle(&self, policy: &RetryPolicy, retry_after: Option) -> Duration { + let n = self + .level + .fetch_add(1, Ordering::Relaxed) + .saturating_add(1) + .min(THROTTLE_LEVEL_CAP); + let delay = retry_after.unwrap_or_else(|| throttle_backoff(policy, n)); + self.cooldown.arm(delay); + delay + } +} + +pub fn throttle_backoff(policy: &RetryPolicy, level: u32) -> Duration { + let shift = level.saturating_sub(1).min(20); + let scaled = THROTTLE_BASE + .saturating_mul(1u32.checked_shl(shift).unwrap_or(u32::MAX)) + .min(policy.cap); + let half = scaled / 2; + half + jitter_uniform(scaled - half) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn policy() -> RetryPolicy { + RetryPolicy::new(5) + } + + #[test] + fn backoff_is_bounded_by_cap_with_full_jitter() { + let p = policy(); + for attempt in 1..=12 { + let d = backoff_delay(&p, attempt); + assert!(d <= p.cap, "attempt {attempt} delay {d:?} exceeds cap"); + } + } + + #[test] + fn backoff_grows_until_cap() { + let p = policy(); + let big = backoff_delay(&p, 30); + assert!(big <= p.cap); + } + + #[test] + fn retry_after_seconds() { + assert_eq!( + parse_retry_after("120", SystemTime::now()), + Some(Duration::from_secs(120)) + ); + } + + #[test] + fn retry_after_http_date_in_future() { + let now = SystemTime::UNIX_EPOCH + Duration::from_secs(784_111_777); + let d = parse_retry_after("Sun, 06 Nov 1994 08:49:47 GMT", now); + assert_eq!(d, Some(Duration::from_secs(10))); + } + + #[test] + fn retry_after_garbage_is_none() { + assert_eq!(parse_retry_after("soon", SystemTime::now()), None); + } + + #[test] + fn status_classification() { + assert_eq!(classify_http_status(429), Disposition::Retryable); + assert_eq!(classify_http_status(503), Disposition::Retryable); + assert_eq!(classify_http_status(400), Disposition::Fatal); + assert_eq!(classify_http_status(401), Disposition::Fatal); + } + + #[test] + fn method_classification() { + assert_eq!( + jmap_method_disposition("serverUnavailable"), + Disposition::Retryable + ); + assert_eq!( + jmap_method_disposition("invalidArguments"), + Disposition::Fatal + ); + } + + #[test] + fn cooldown_arms_and_clears() { + let c = Cooldown::new(); + assert!(c.remaining().is_none()); + c.arm(Duration::from_millis(40)); + assert!(c.remaining().is_some()); + std::thread::sleep(Duration::from_millis(60)); + assert!(c.remaining().is_none()); + } + + #[test] + fn throttle_backoff_doubles_then_clamps_to_cap() { + let p = RetryPolicy::new(8); + let mut prev = throttle_backoff(&p, 1); + for level in 2..=4 { + let d = throttle_backoff(&p, level); + assert!( + d >= prev / 2, + "level {level} ({d:?}) collapsed below half of prev ({prev:?})" + ); + prev = d; + } + assert!(throttle_backoff(&p, 20) <= p.cap); + } + + #[test] + fn rate_limit_state_escalates_across_concurrent_callers() { + let p = RetryPolicy::new(8); + let s = RateLimitState::new(); + let first = s.on_throttle(&p, None); + let second = s.on_throttle(&p, None); + let third = s.on_throttle(&p, None); + assert!( + third >= first, + "level 3 delay ({third:?}) should not be smaller than level 1 ({first:?})" + ); + assert!( + second >= first, + "level 2 delay ({second:?}) should not be smaller than level 1 ({first:?})" + ); + assert!(s.level() >= 3); + } + + #[test] + fn rate_limit_state_resets_on_success() { + let p = RetryPolicy::new(8); + let s = RateLimitState::new(); + let _ = s.on_throttle(&p, None); + let _ = s.on_throttle(&p, None); + let _ = s.on_throttle(&p, None); + assert!(s.level() >= 3); + s.on_success(); + assert_eq!(s.level(), 0); + let next = s.on_throttle(&p, None); + assert!( + next <= THROTTLE_BASE, + "first throttle after reset should be small ({next:?})" + ); + } + + #[test] + fn rate_limit_state_honours_retry_after_over_schedule() { + let p = RetryPolicy::new(8); + let s = RateLimitState::new(); + let d = s.on_throttle(&p, Some(Duration::from_millis(750))); + assert_eq!(d, Duration::from_millis(750)); + assert_eq!(s.level(), 1); + } +} diff --git a/src/jmap/session.rs b/src/jmap/session.rs new file mode 100644 index 0000000..063ab5a --- /dev/null +++ b/src/jmap/session.rs @@ -0,0 +1,250 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use serde::Deserialize; +use serde_json::Value; + +use crate::error::Error; +use crate::jmap::error::JmapError; +use crate::jmap::http::HttpClient; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Session { + pub api_url: String, + pub upload_url: String, + pub download_url: String, + pub capabilities: IndexMap, + pub accounts: IndexMap, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Account { + pub name: String, + #[serde(default)] + pub account_capabilities: IndexMap, +} + +#[derive(Debug, Clone, Copy, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Limits { + pub max_objects_in_get: u64, + pub max_objects_in_set: u64, + pub max_calls_in_request: u64, + pub max_concurrent_requests: u64, + pub max_concurrent_upload: u64, + pub max_size_request: u64, + pub max_size_upload: u64, +} + +fn parse_session(body: &str) -> Option { + let value: Value = serde_json::from_str(body).ok()?; + let has_shape = value.get("apiUrl").is_some() + && value.get("accounts").is_some() + && value.get("capabilities").is_some(); + if !has_shape { + return None; + } + serde_json::from_value(value).ok() +} + +fn well_known_url(base: &str) -> String { + let trimmed = base.trim_end_matches('/'); + format!("{trimmed}/.well-known/jmap") +} + +impl Session { + pub fn discover(client: &HttpClient, url: &str) -> Result { + match client.get(url) { + Ok(direct) => { + if let Some(session) = parse_session(&direct) { + return session.ensure_authenticated(); + } + } + Err(JmapError::Auth(m)) => return Err(JmapError::Auth(m)), + Err(_) => {} + } + let well_known = well_known_url(url); + match client.get(&well_known) { + Ok(body) => { + if let Some(session) = parse_session(&body) { + return session.ensure_authenticated(); + } + Err(JmapError::Connect(format!( + "no JMAP Session object at {url} or {well_known}" + ))) + } + Err(JmapError::Auth(m)) => Err(JmapError::Auth(m)), + Err(e) => Err(JmapError::Connect(format!( + "session discovery failed: no Session at {url}; {well_known}: {e}" + ))), + } + } + + fn ensure_authenticated(self) -> Result { + if self.accounts.is_empty() { + return Err(JmapError::Auth( + "session enumerates no accounts (anonymous session: authentication failed)" + .to_owned(), + )); + } + Ok(self) + } + + pub fn core_limits(&self) -> Result { + let core = self + .capabilities + .get("urn:ietf:params:jmap:core") + .ok_or_else(|| { + Error::Connection("session has no urn:ietf:params:jmap:core capability".to_owned()) + })?; + serde_json::from_value(core.clone()) + .map_err(|e| Error::Connection(format!("session core capability is malformed: {e}"))) + } + + pub fn account(&self, account_id: &str) -> Option<&Account> { + self.accounts.get(account_id) + } + + pub fn account_capabilities(&self, account_id: &str) -> Option<&IndexMap> { + self.accounts + .get(account_id) + .map(|a| &a.account_capabilities) + } + + pub fn supports(&self, account_id: &str, capability_urn: &str) -> bool { + self.account_capabilities(account_id) + .map(|caps| caps.contains_key(capability_urn)) + .unwrap_or(false) + } + + pub fn upload_url_for(&self, account_id: &str) -> String { + self.upload_url + .replace("{accountId}", &encode_segment(account_id)) + } + + pub fn download_url_for( + &self, + account_id: &str, + blob_id: &str, + type_hint: &str, + name: &str, + ) -> String { + self.download_url + .replace("{accountId}", &encode_segment(account_id)) + .replace("{blobId}", &encode_segment(blob_id)) + .replace("{type}", &encode_segment(type_hint)) + .replace("{name}", &encode_segment(name)) + } +} + +fn encode_segment(input: &str) -> String { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let mut out = String::with_capacity(input.len()); + for byte in input.as_bytes() { + let b = *byte; + let unreserved = b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~'); + if unreserved { + out.push(b as char); + } else { + out.push('%'); + out.push(HEX[(b >> 4) as usize] as char); + out.push(HEX[(b & 0x0F) as usize] as char); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn raw_session() -> &'static str { + r#"{ + "apiUrl": "https://example.org/jmap/api", + "uploadUrl": "https://example.org/jmap/upload/{accountId}/", + "downloadUrl": "https://example.org/jmap/download/{accountId}/{blobId}/{type}/{name}", + "capabilities": { + "urn:ietf:params:jmap:core": { + "maxObjectsInGet": 500, + "maxObjectsInSet": 500, + "maxCallsInRequest": 16, + "maxConcurrentRequests": 4, + "maxConcurrentUpload": 4, + "maxSizeRequest": 10000000, + "maxSizeUpload": 50000000 + } + }, + "accounts": { + "w": { + "name": "vspec-user@example.org", + "accountCapabilities": { "urn:ietf:params:jmap:mail": {} } + } + } + }"# + } + + #[test] + fn parses_a_session_and_reads_limits() { + let session = parse_session(raw_session()).unwrap(); + let limits = session.core_limits().unwrap(); + assert_eq!(limits.max_objects_in_get, 500); + assert_eq!(limits.max_size_upload, 50000000); + assert_eq!(session.accounts["w"].name, "vspec-user@example.org"); + } + + #[test] + fn capability_gate_reads_account_capabilities() { + let session = parse_session(raw_session()).unwrap(); + assert!(session.supports("w", "urn:ietf:params:jmap:mail")); + assert!(!session.supports("w", "urn:ietf:params:jmap:filenode")); + assert!(!session.supports("missing", "urn:ietf:params:jmap:mail")); + } + + #[test] + fn templates_upload_and_download_urls_with_encoding() { + let session = parse_session(raw_session()).unwrap(); + assert_eq!( + session.upload_url_for("a c"), + "https://example.org/jmap/upload/a%20c/" + ); + assert_eq!( + session.download_url_for("w", "G1/2", "application/sieve", "my script"), + "https://example.org/jmap/download/w/G1%2F2/application%2Fsieve/my%20script" + ); + } + + #[test] + fn rejects_body_that_is_not_a_session() { + assert!(parse_session("{\"hello\":true}").is_none()); + assert!(parse_session("not json").is_none()); + } + + #[test] + fn anonymous_session_is_auth_failure() { + let raw = + r#"{"apiUrl":"x","uploadUrl":"u","downloadUrl":"d","capabilities":{},"accounts":{}}"#; + let session = parse_session(raw).unwrap(); + assert!(matches!( + session.ensure_authenticated(), + Err(JmapError::Auth(_)) + )); + } + + #[test] + fn well_known_appends_correctly() { + assert_eq!( + well_known_url("https://h.example/"), + "https://h.example/.well-known/jmap" + ); + assert_eq!( + well_known_url("https://h.example"), + "https://h.example/.well-known/jmap" + ); + } +} diff --git a/src/jmap/wire/address_book.rs b/src/jmap/wire/address_book.rs new file mode 100644 index 0000000..2e4e9cb --- /dev/null +++ b/src/jmap/wire/address_book.rs @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AddressBook { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub name: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(default)] + pub sort_order: u32, + + #[serde(default)] + pub is_default: bool, + + #[serde(default = "default_true")] + pub is_subscribed: bool, +} + +fn default_true() -> bool { + true +} diff --git a/src/jmap/wire/calendar.rs b/src/jmap/wire/calendar.rs new file mode 100644 index 0000000..326ff44 --- /dev/null +++ b/src/jmap/wire/calendar.rs @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Calendar { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub name: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + + #[serde(default)] + pub sort_order: u32, + + #[serde(default = "default_true")] + pub is_subscribed: bool, + + #[serde(default = "default_true")] + pub is_visible: bool, + + #[serde(default)] + pub is_default: bool, + + #[serde(default = "default_include_in_availability")] + pub include_in_availability: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_alerts_with_time: Option>, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_alerts_without_time: Option>, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub time_zone: Option, +} + +fn default_true() -> bool { + true +} + +fn default_include_in_availability() -> String { + "all".to_owned() +} diff --git a/src/jmap/wire/calendar_event.rs b/src/jmap/wire/calendar_event.rs new file mode 100644 index 0000000..44aa062 --- /dev/null +++ b/src/jmap/wire/calendar_event.rs @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CalendarEvent { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub calendar_ids: IndexMap, + + #[serde(default)] + pub is_draft: bool, + + #[serde(default)] + pub use_default_alerts: bool, + + #[serde(flatten)] + pub rest: IndexMap, +} diff --git a/src/jmap/wire/common.rs b/src/jmap/wire/common.rs new file mode 100644 index 0000000..c8ab03a --- /dev/null +++ b/src/jmap/wire/common.rs @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct JmapId(pub String); + +impl From for JmapId { + fn from(value: String) -> Self { + JmapId(value) + } +} + +impl From<&str> for JmapId { + fn from(value: &str) -> Self { + JmapId(value.to_owned()) + } +} + +impl std::fmt::Display for JmapId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +pub type UtcDate = time::OffsetDateTime; + +pub type Date = time::OffsetDateTime; + +pub fn bool_or_true<'de, D>(d: D) -> Result +where + D: serde::Deserializer<'de>, +{ + Ok(Option::::deserialize(d)?.unwrap_or(true)) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EmailAddress { + pub name: Option, + pub email: String, +} diff --git a/src/jmap/wire/contact_card.rs b/src/jmap/wire/contact_card.rs new file mode 100644 index 0000000..4eb72cc --- /dev/null +++ b/src/jmap/wire/contact_card.rs @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContactCard { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub address_book_ids: IndexMap, + + #[serde(flatten)] + pub rest: IndexMap, +} diff --git a/src/jmap/wire/email.rs b/src/jmap/wire/email.rs new file mode 100644 index 0000000..bf3df50 --- /dev/null +++ b/src/jmap/wire/email.rs @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; + +use super::{JmapId, UtcDate}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Email { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub blob_id: JmapId, + + #[serde(with = "time::serde::rfc3339")] + pub received_at: UtcDate, + + pub mailbox_ids: IndexMap, + + pub keywords: IndexMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EmailImport { + + pub blob_id: JmapId, + + pub mailbox_ids: IndexMap, + + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub keywords: IndexMap, + + #[serde( + default, + with = "time::serde::rfc3339::option", + skip_serializing_if = "Option::is_none" + )] + pub received_at: Option, +} diff --git a/src/jmap/wire/file_node.rs b/src/jmap/wire/file_node.rs new file mode 100644 index 0000000..d5cd229 --- /dev/null +++ b/src/jmap/wire/file_node.rs @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::common::bool_or_true; +use super::{JmapId, UtcDate}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FileNode { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + + pub node_type: NodeType, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub blob_id: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub target: Option>, + + pub name: String, + + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub media_type: Option, + + #[serde(with = "time::serde::rfc3339")] + pub created: UtcDate, + + #[serde( + default, + with = "time::serde::rfc3339::option", + skip_serializing_if = "Option::is_none" + )] + pub modified: Option, + + #[serde(default = "default_true", deserialize_with = "bool_or_true")] + pub is_subscribed: bool, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum NodeType { + File, + Directory, + Symlink, +} + +fn default_true() -> bool { + true +} diff --git a/src/jmap/wire/identity.rs b/src/jmap/wire/identity.rs new file mode 100644 index 0000000..b88144e --- /dev/null +++ b/src/jmap/wire/identity.rs @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::{EmailAddress, JmapId}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Identity { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(default)] + pub name: String, + + pub email: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reply_to: Option>, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bcc: Option>, + + #[serde(default)] + pub text_signature: String, + + #[serde(default)] + pub html_signature: String, +} diff --git a/src/jmap/wire/mailbox.rs b/src/jmap/wire/mailbox.rs new file mode 100644 index 0000000..0b4a46f --- /dev/null +++ b/src/jmap/wire/mailbox.rs @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Mailbox { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + pub name: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, + + #[serde(default)] + pub sort_order: u32, + + #[serde(default = "default_true")] + pub is_subscribed: bool, +} + +fn default_true() -> bool { + true +} diff --git a/src/jmap/wire/mod.rs b/src/jmap/wire/mod.rs new file mode 100644 index 0000000..2250540 --- /dev/null +++ b/src/jmap/wire/mod.rs @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod common; + +pub mod address_book; +pub mod calendar; +pub mod calendar_event; +pub mod contact_card; +pub mod email; +pub mod file_node; +pub mod identity; +pub mod mailbox; +pub mod participant_identity; +pub mod sieve_script; + +pub use common::{Date, EmailAddress, JmapId, UtcDate}; diff --git a/src/jmap/wire/participant_identity.rs b/src/jmap/wire/participant_identity.rs new file mode 100644 index 0000000..ed69d04 --- /dev/null +++ b/src/jmap/wire/participant_identity.rs @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ParticipantIdentity { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(default)] + pub name: String, + + pub calendar_address: String, + + #[serde(default)] + pub is_default: bool, +} diff --git a/src/jmap/wire/sieve_script.rs b/src/jmap/wire/sieve_script.rs new file mode 100644 index 0000000..95c5d9c --- /dev/null +++ b/src/jmap/wire/sieve_script.rs @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde::{Deserialize, Serialize}; + +use super::JmapId; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SieveScript { + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(default)] + pub name: Option, + + pub blob_id: JmapId, + + #[serde(default)] + pub is_active: bool, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..10b7642 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod cli; +pub mod dav; +pub mod db; +pub mod error; +pub mod exchange; +pub mod exchange_ews; +pub mod exchange_graph; +pub mod imap; +pub mod inspect; +pub mod jmap; +pub mod logging; +pub mod managesieve; +pub mod secret; +pub mod sync; +pub mod types; + +use std::sync::Once; + +static CRYPTO_INIT: Once = Once::new(); + +pub fn install_default_crypto_provider() { + CRYPTO_INIT.call_once(|| { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + }); +} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..5903f5c --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub const LEVEL_QUIET: u8 = 0; +pub const LEVEL_DEFAULT: u8 = 1; +pub const LEVEL_PROGRESS: u8 = 2; +pub const LEVEL_METHOD: u8 = 3; +pub const LEVEL_BODIES: u8 = 4; + +#[derive(Debug, Clone, Copy)] +pub struct Logger { + level: u8, +} + +impl Logger { + pub fn new(level: u8) -> Self { + Logger { + level: level.min(LEVEL_BODIES), + } + } + + pub fn from_flags(quiet: bool, verbose: u8) -> Self { + let level = if quiet { + LEVEL_QUIET + } else { + LEVEL_DEFAULT.saturating_add(verbose) + }; + Logger::new(level) + } + + pub fn level(&self) -> u8 { + self.level + } + + pub fn enabled(&self, level: u8) -> bool { + self.level >= level + } + + pub fn warn(&self, message: &str) { + eprintln!("warning: {message}"); + } + + pub fn error(&self, message: &str) { + eprintln!("error: {message}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quiet_is_level_zero() { + assert_eq!(Logger::from_flags(true, 3).level(), LEVEL_QUIET); + } + + #[test] + fn default_is_level_one() { + assert_eq!(Logger::from_flags(false, 0).level(), LEVEL_DEFAULT); + } + + #[test] + fn verbose_caps_at_four() { + assert_eq!(Logger::from_flags(false, 9).level(), LEVEL_BODIES); + } + + #[test] + fn enabled_is_a_superset_scale() { + let l = Logger::from_flags(false, 1); + assert!(l.enabled(LEVEL_DEFAULT)); + assert!(l.enabled(LEVEL_PROGRESS)); + assert!(!l.enabled(LEVEL_METHOD)); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..26eea8e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use clap::Parser; + +use vandelay::cli::{Action, Cli}; +use vandelay::error::Error; +use vandelay::inspect; +use vandelay::sync::{self, Summary}; + +fn main() { + vandelay::install_default_crypto_provider(); + let code = run(); + std::process::exit(code); +} + +fn run() -> i32 { + let cli = match Cli::try_parse() { + Ok(cli) => cli, + Err(err) => { + let _ = err.print(); + return if err.use_stderr() { 1 } else { 0 }; + } + }; + + let action = match cli.resolve() { + Ok(action) => action, + Err(err) => return fail(&err), + }; + + let result = match action { + Action::Import(common, config) => { + let logger = common.logger; + sync::import_jmap::run(common, config).map(|s| (s, logger)) + } + Action::ImportImap(common, config) => { + let logger = common.logger; + sync::import_imap::run(common, config).map(|s| (s, logger)) + } + Action::ImportDav(common, config) => { + let logger = common.logger; + sync::import_dav::run(common, config).map(|s| (s, logger)) + } + Action::ImportManageSieve(common, config) => { + let logger = common.logger; + sync::import_managesieve::run(common, config).map(|s| (s, logger)) + } + Action::ImportMaildir(common, config) => { + let logger = common.logger; + sync::import_maildir::run(common, config).map(|s| (s, logger)) + } + Action::ImportTakeout(common, config) => { + let logger = common.logger; + sync::import_takeout::run(common, config).map(|s| (s, logger)) + } + Action::ImportExchangeEws(common, config) => { + let logger = common.logger; + sync::import_exchange_ews::run(common, config).map(|s| (s, logger)) + } + Action::ImportExchangeGraph(common, config) => { + let logger = common.logger; + sync::import_exchange_graph::run(common, config).map(|s| (s, logger)) + } + Action::Export(common, config) => { + let logger = common.logger; + sync::export::run(common, config).map(|s| (s, logger)) + } + Action::Inspect(config) => { + return match inspect::run(config) { + Ok(()) => 0, + Err(err) => fail(&err), + }; + } + }; + + match result { + Ok((summary, logger)) => { + report(&summary); + if summary.any_failed() { + logger.error("some objects failed; the archive is consistent and resumable"); + 5 + } else { + 0 + } + } + Err(err) => fail(&err), + } +} + +fn fail(err: &Error) -> i32 { + eprintln!("error: {err}"); + err.exit_code() +} + +fn report(summary: &Summary) { + for (type_name, counts) in &summary.per_type { + println!( + "{type_name}: created={} fetched={} deleted={} skipped={} failed={}", + counts.created, counts.fetched, counts.deleted, counts.skipped, counts.failed + ); + } +} diff --git a/src/managesieve/client.rs b/src/managesieve/client.rs new file mode 100644 index 0000000..619487c --- /dev/null +++ b/src/managesieve/client.rs @@ -0,0 +1,504 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufRead, BufReader, Read, Write}; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; + +use crate::imap::transport::{Connector, ImapStream}; + +use super::command; +use super::error::SieveError; +use super::response::{ + Capabilities, ResponseBlock, Status, StatusLine, Token, parse_capabilities, read_response, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConnectMode { + ImplicitTls, + Plain, + StartTls, +} + +pub struct SieveClient { + reader: BufReader>, + pub host: String, + pub capabilities: Capabilities, + closed: bool, + fresh_post_auth_caps: bool, +} + +impl SieveClient { + pub fn connect( + connector: &Connector, + host: &str, + port: u16, + mode: ConnectMode, + allow_cleartext: bool, + ) -> Result { + let stream: Box = match mode { + ConnectMode::ImplicitTls => connector + .connect_tls(host, port) + .map_err(|e| SieveError::Tls(e.to_string()))?, + ConnectMode::Plain | ConnectMode::StartTls => connector + .connect_plain(host, port) + .map_err(|e| SieveError::Io(std::io::Error::other(e.to_string())))?, + }; + let mut client = SieveClient { + reader: BufReader::new(stream), + host: host.to_owned(), + capabilities: Capabilities::default(), + closed: false, + fresh_post_auth_caps: false, + }; + client.read_initial_capabilities()?; + if matches!(mode, ConnectMode::StartTls) { + if client.capabilities.starttls { + client.start_tls(connector)?; + client.read_initial_capabilities()?; + } else if !allow_cleartext { + return Err(SieveError::Unsupported( + "server does not advertise STARTTLS; pass --allow-cleartext to permit \ + credentials over an unencrypted socket" + .into(), + )); + } + } + Ok(client) + } + + pub fn capabilities(&self) -> &Capabilities { + &self.capabilities + } + + pub fn is_closed(&self) -> bool { + self.closed + } + + fn read_initial_capabilities(&mut self) -> Result<(), SieveError> { + let block = read_response(&mut self.reader)?; + match block.status.status { + Status::Ok => { + self.capabilities = parse_capabilities(&block.data); + Ok(()) + } + Status::Bye => { + self.closed = true; + Err(SieveError::Bye(block.status.text)) + } + Status::No => Err(SieveError::No(block.status.into_no_error())), + } + } + + pub fn refresh_capabilities(&mut self) -> Result<(), SieveError> { + let block = self.run(&command::capability())?; + let caps = parse_capabilities(&block.data); + self.capabilities = caps; + self.fresh_post_auth_caps = true; + Ok(()) + } + + pub fn had_fresh_post_auth_caps(&self) -> bool { + self.fresh_post_auth_caps + } + + fn consume_post_auth_caps(&mut self, data: &[Vec]) { + if data.is_empty() { + return; + } + let parsed = parse_capabilities(data); + let mentions_any = + !parsed.sasl.is_empty() || parsed.implementation.is_some() || parsed.version.is_some(); + if mentions_any { + self.capabilities = parsed; + self.fresh_post_auth_caps = true; + } + } + + fn start_tls(&mut self, connector: &Connector) -> Result<(), SieveError> { + let block = self.run(&command::starttls())?; + let _ = block; + let stream = self.reader.get_ref(); + if stream.is_tls() { + return Err(SieveError::Protocol( + "STARTTLS issued but stream already TLS".into(), + )); + } + let buffered = std::mem::replace(&mut self.reader, BufReader::new(noop_stream())); + if !buffered.buffer().is_empty() { + return Err(SieveError::Protocol( + "buffered data found after STARTTLS OK; possible response injection".into(), + )); + } + let inner = buffered.into_inner(); + let upgraded = inner + .upgrade_tls(connector, &self.host) + .map_err(|e| SieveError::Tls(e.to_string()))?; + self.reader = BufReader::new(upgraded); + Ok(()) + } + + pub fn run(&mut self, command: &str) -> Result { + self.write_all(command.as_bytes())?; + let block = read_response(&mut self.reader)?; + finish_block(block, &mut self.closed) + } + + pub fn run_raw(&mut self, bytes: &[u8]) -> Result { + self.write_all(bytes)?; + let block = read_response(&mut self.reader)?; + finish_block(block, &mut self.closed) + } + + pub fn listscripts(&mut self) -> Result { + self.run(&command::listscripts()) + } + + pub fn getscript(&mut self, name: &str) -> Result { + self.run(&command::getscript(name)) + } + + pub fn noop(&mut self) -> Result<(), SieveError> { + let _ = self.run(&command::noop())?; + Ok(()) + } + + pub fn logout(&mut self) -> Result<(), SieveError> { + if self.closed { + return Ok(()); + } + let _ = self.write_all(command::logout().as_bytes()); + self.closed = true; + Ok(()) + } + + pub fn authenticate_plain(&mut self, authcid: &str, password: &str) -> Result<(), SieveError> { + let mut payload: Vec = Vec::with_capacity(authcid.len() + password.len() + 3); + payload.push(0); + payload.extend_from_slice(authcid.as_bytes()); + payload.push(0); + payload.extend_from_slice(password.as_bytes()); + let encoded = BASE64.encode(&payload); + self.send_authenticate("PLAIN", &encoded) + } + + pub fn authenticate_login(&mut self, authcid: &str, password: &str) -> Result<(), SieveError> { + self.fresh_post_auth_caps = false; + self.write_all(command::authenticate("LOGIN").as_bytes())?; + let user_payload = BASE64.encode(authcid.as_bytes()); + self.expect_continuation()?; + self.write_all(command::continuation_payload(&user_payload).as_bytes())?; + let pass_payload = BASE64.encode(password.as_bytes()); + self.expect_continuation()?; + self.write_all(command::continuation_payload(&pass_payload).as_bytes())?; + let block = read_response(&mut self.reader)?; + match finish_block(block, &mut self.closed) { + Ok(block) => { + self.consume_post_auth_caps(&block.data); + Ok(()) + } + Err(SieveError::No(no)) if no.is_referral() => { + Err(SieveError::Referral(no.to_string())) + } + Err(other) => Err(other), + } + } + + pub fn authenticate_oauthbearer(&mut self, user: &str, token: &str) -> Result<(), SieveError> { + let mut payload = String::with_capacity(user.len() + token.len() + 32); + payload.push_str("n,a="); + payload.push_str(user); + payload.push(','); + payload.push('\x01'); + payload.push_str("auth=Bearer "); + payload.push_str(token); + payload.push('\x01'); + payload.push('\x01'); + let encoded = BASE64.encode(payload.as_bytes()); + self.send_authenticate("OAUTHBEARER", &encoded) + } + + fn send_authenticate(&mut self, mechanism: &str, b64: &str) -> Result<(), SieveError> { + self.fresh_post_auth_caps = false; + self.write_all(command::authenticate_with_initial(mechanism, b64).as_bytes())?; + let block = read_response(&mut self.reader)?; + match finish_block(block, &mut self.closed) { + Ok(block) => { + self.consume_post_auth_caps(&block.data); + Ok(()) + } + Err(SieveError::No(no)) if no.is_referral() => { + Err(SieveError::Referral(no.to_string())) + } + Err(other) => Err(other), + } + } + + fn expect_continuation(&mut self) -> Result<(), SieveError> { + let mut line = Vec::new(); + loop { + line.clear(); + let n = self.reader.read_until(b'\n', &mut line)?; + if n == 0 { + return Err(SieveError::Io(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "expected continuation", + ))); + } + while matches!(line.last(), Some(b'\n') | Some(b'\r')) { + line.pop(); + } + if line.is_empty() { + continue; + } + let s = String::from_utf8_lossy(&line); + let trimmed = s.trim_start(); + if trimmed.starts_with('"') || trimmed.starts_with('{') { + return Ok(()); + } + if trimmed.starts_with("NO") || trimmed.starts_with("BYE") { + let line_tokens = tokens_from_text(&line)?; + let status = super::response::try_parse_status(&line_tokens) + .ok_or_else(|| SieveError::Parse(format!("bad status: {s:?}")))?; + let block = ResponseBlock { + data: Vec::new(), + status, + }; + return finish_block(block, &mut self.closed).map(|_| ()); + } + return Err(SieveError::Parse(format!( + "unexpected line during AUTHENTICATE LOGIN: {s:?}" + ))); + } + } + + fn write_all(&mut self, bytes: &[u8]) -> Result<(), SieveError> { + let stream = self.reader.get_mut(); + stream.write_all(bytes)?; + stream.flush()?; + Ok(()) + } +} + +impl Drop for SieveClient { + fn drop(&mut self) { + if !self.closed { + let _ = self.logout(); + } + } +} + +impl SieveClient { + pub fn new_from_stream(stream: Box, host: String) -> Self { + SieveClient { + reader: BufReader::new(stream), + host, + capabilities: Capabilities::default(), + closed: false, + fresh_post_auth_caps: false, + } + } + + pub fn read_response_block(&mut self) -> Result { + let block = read_response(&mut self.reader)?; + finish_block(block, &mut self.closed) + } +} + +fn tokens_from_text(line: &[u8]) -> Result, SieveError> { + let mut buf: Vec = line.to_vec(); + buf.extend_from_slice(b"\r\n"); + let mut cursor = std::io::Cursor::new(buf); + let mut reader = BufReader::new(&mut cursor); + let block = read_response_with_eof_ok(&mut reader)?; + if let Some(line) = block.data.into_iter().next() { + return Ok(line); + } + Ok(Vec::new()) +} + +fn read_response_with_eof_ok( + reader: &mut R, +) -> Result { + match read_response(reader) { + Ok(b) => Ok(b), + Err(SieveError::Io(_)) => Ok(ResponseBlock::default()), + Err(e) => Err(e), + } +} + +fn finish_block(block: ResponseBlock, closed: &mut bool) -> Result { + match block.status.status { + Status::Ok => Ok(block), + Status::No => { + let no = StatusLine::into_no_error(block.status); + Err(SieveError::No(no)) + } + Status::Bye => { + *closed = true; + Err(SieveError::Bye(block.status.text)) + } + } +} + +fn noop_stream() -> Box { + Box::new(NoopStream) +} + +struct NoopStream; + +impl Read for NoopStream { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +impl Write for NoopStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl ImapStream for NoopStream { + fn upgrade_tls( + self: Box, + _connector: &Connector, + _host: &str, + ) -> Result, crate::imap::ImapError> { + Ok(self) + } + fn is_tls(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::imap::ImapError; + use std::io::Cursor; + + struct MockStream { + reader: Cursor>, + written: Vec, + } + + impl MockStream { + fn new(server_says: &[u8]) -> Self { + MockStream { + reader: Cursor::new(server_says.to_vec()), + written: Vec::new(), + } + } + } + + impl Read for MockStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } + } + + impl Write for MockStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.written.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + impl ImapStream for MockStream { + fn upgrade_tls( + self: Box, + _c: &Connector, + _h: &str, + ) -> Result, ImapError> { + Ok(self) + } + fn is_tls(&self) -> bool { + false + } + } + + fn client_with(server: &[u8]) -> SieveClient { + SieveClient::new_from_stream(Box::new(MockStream::new(server)), "test.example".to_owned()) + } + + #[test] + fn run_listscripts_returns_data_and_ok() { + let server = b"\"a\"\r\n\"b\" ACTIVE\r\nOK\r\n"; + let mut c = client_with(server); + let r = c.listscripts().unwrap(); + let scripts = super::super::response::parse_listscripts(&r.data).unwrap(); + assert_eq!(scripts.len(), 2); + assert!(scripts[1].active); + } + + #[test] + fn run_returns_no_as_sieve_error_no() { + let server = b"NO (NONEXISTENT) \"not here\"\r\n"; + let mut c = client_with(server); + let err = c.getscript("missing").unwrap_err(); + match err { + SieveError::No(no) => { + assert_eq!(no.code.as_deref(), Some("NONEXISTENT")); + assert!(no.is_nonexistent()); + } + other => panic!("expected No, got {other:?}"), + } + } + + #[test] + fn bye_closes_client() { + let server = b"BYE \"going away\"\r\n"; + let mut c = client_with(server); + let err = c.noop().unwrap_err(); + assert!(matches!(err, SieveError::Bye(_))); + assert!(c.is_closed()); + } + + #[test] + fn authenticate_plain_sends_initial_response() { + let server = b"OK\r\n"; + let mut c = client_with(server); + c.authenticate_plain("alice", "p@ss").unwrap(); + } + + #[test] + fn authenticate_oauthbearer_sends_initial_response() { + let server = b"OK\r\n"; + let mut c = client_with(server); + c.authenticate_oauthbearer("alice@x", "tok").unwrap(); + } + + #[test] + fn authenticate_login_walks_two_continuations() { + let server = b"\"VXNlcm5hbWU6\"\r\n\"UGFzc3dvcmQ6\"\r\nOK\r\n"; + let mut c = client_with(server); + c.authenticate_login("alice", "p@ss").unwrap(); + } + + #[test] + fn authenticate_no_response_propagates_as_no() { + let server = b"NO \"bad creds\"\r\n"; + let mut c = client_with(server); + let err = c.authenticate_plain("alice", "wrong").unwrap_err(); + assert!(matches!(err, SieveError::No(_)), "got {err:?}"); + } + + #[test] + fn authenticate_referral_no_translates_to_referral() { + let server = b"NO (REFERRAL \"sieve://other\") \"go\"\r\n"; + let mut c = client_with(server); + let err = c.authenticate_plain("alice", "x").unwrap_err(); + assert!(matches!(err, SieveError::Referral(_)), "got {err:?}"); + } +} diff --git a/src/managesieve/command.rs b/src/managesieve/command.rs new file mode 100644 index 0000000..94ac480 --- /dev/null +++ b/src/managesieve/command.rs @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use super::name::encode_argument; + +pub fn capability() -> String { + "CAPABILITY\r\n".to_owned() +} + +pub fn starttls() -> String { + "STARTTLS\r\n".to_owned() +} + +pub fn noop() -> String { + "NOOP\r\n".to_owned() +} + +pub fn logout() -> String { + "LOGOUT\r\n".to_owned() +} + +pub fn listscripts() -> String { + "LISTSCRIPTS\r\n".to_owned() +} + +pub fn getscript(name: &str) -> String { + let mut out = String::with_capacity(name.len() + 16); + out.push_str("GETSCRIPT "); + encode_argument(name, &mut out); + out.push_str("\r\n"); + out +} + +pub fn authenticate(mechanism: &str) -> String { + let mut out = String::with_capacity(mechanism.len() + 20); + out.push_str("AUTHENTICATE "); + encode_argument(mechanism, &mut out); + out.push_str("\r\n"); + out +} + +pub fn authenticate_with_initial(mechanism: &str, initial_b64: &str) -> String { + let mut out = String::with_capacity(mechanism.len() + initial_b64.len() + 32); + out.push_str("AUTHENTICATE "); + encode_argument(mechanism, &mut out); + out.push(' '); + encode_argument(initial_b64, &mut out); + out.push_str("\r\n"); + out +} + +pub fn continuation_payload(initial_b64: &str) -> String { + let mut out = String::with_capacity(initial_b64.len() + 8); + encode_argument(initial_b64, &mut out); + out.push_str("\r\n"); + out +} + +pub use super::name::contains_literal; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn capability_is_uppercase_with_crlf() { + assert_eq!(capability(), "CAPABILITY\r\n"); + } + + #[test] + fn getscript_quoted_for_ascii_names() { + assert_eq!(getscript("vacation"), "GETSCRIPT \"vacation\"\r\n"); + } + + #[test] + fn getscript_escapes_quotes_and_backslashes() { + let out = getscript("a\"b\\c"); + assert!(out.contains("\\\"")); + assert!(out.contains("\\\\")); + } + + #[test] + fn getscript_uses_literal_for_non_ascii() { + let name = "fancy\u{00e7}y"; + let out = getscript(name); + let nbytes = name.len(); + let expected = { + let mut v: Vec = Vec::new(); + v.extend_from_slice(format!("GETSCRIPT {{{nbytes}+}}\r\n").as_bytes()); + v.extend_from_slice(name.as_bytes()); + v.extend_from_slice(b"\r\n"); + v + }; + assert_eq!( + out.as_bytes(), + expected.as_slice(), + "GETSCRIPT wire bytes for non-ascii name must be byte-exact" + ); + } + + #[test] + fn getscript_uses_literal_for_names_containing_crlf() { + let name = "with\nlinebreak"; + let out = getscript(name); + assert!( + out.contains("{") && out.contains("+}\r\n"), + "expected literal for newline-bearing name: {out:?}" + ); + } + + #[test] + fn authenticate_with_initial_emits_two_arguments() { + let out = authenticate_with_initial("PLAIN", "AGZvbwBiYXI="); + assert_eq!(out, "AUTHENTICATE \"PLAIN\" \"AGZvbwBiYXI=\"\r\n"); + } + + #[test] + fn authenticate_without_initial_emits_one_argument() { + let out = authenticate("LOGIN"); + assert_eq!(out, "AUTHENTICATE \"LOGIN\"\r\n"); + } + + #[test] + fn continuation_payload_quotes_simple_base64() { + assert_eq!(continuation_payload("Zm9v"), "\"Zm9v\"\r\n"); + } + + #[test] + fn contains_literal_detects_open_brace() { + assert!(contains_literal(b"GETSCRIPT {3+}\r\nabc\r\n")); + assert!(!contains_literal(b"GETSCRIPT \"abc\"\r\n")); + } +} diff --git a/src/managesieve/error.rs b/src/managesieve/error.rs new file mode 100644 index 0000000..52570e8 --- /dev/null +++ b/src/managesieve/error.rs @@ -0,0 +1,166 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::fmt; +use std::io; + +#[derive(Debug, thiserror::Error)] +pub enum SieveError { + #[error("transport error: {0}")] + Io(#[from] io::Error), + + #[error("tls error: {0}")] + Tls(String), + + #[error("protocol error: {0}")] + Protocol(String), + + #[error("response parse error: {0}")] + Parse(String), + + #[error("server said NO: {0}")] + No(NoError), + + #[error("server closed connection: {0}")] + Bye(String), + + #[error("unsupported by server: {0}")] + Unsupported(String), + + #[error("referral not supported: {0}")] + Referral(String), +} + +#[derive(Debug, Clone)] +pub struct NoError { + pub text: String, + pub code: Option, +} + +impl NoError { + pub fn new(text: impl Into, code: Option) -> Self { + Self { + text: text.into(), + code, + } + } + + pub fn is_transient(&self) -> bool { + if let Some(c) = &self.code { + let cu = c.to_ascii_uppercase(); + if cu == "TRYLATER" { + return true; + } + if matches!( + cu.as_str(), + "QUOTA" + | "QUOTA/MAXSCRIPTS" + | "AUTH-TOO-WEAK" + | "ENCRYPT-NEEDED" + | "TRANSITION-NEEDED" + | "NONEXISTENT" + | "ALREADYEXISTS" + | "ACTIVE" + | "SASL" + | "TAG" + | "WARNINGS" + ) { + return false; + } + } + let lower = self.text.to_ascii_lowercase(); + const TRANSIENT_MARKERS: &[&str] = &[ + "temp", + "try again", + "try later", + "unavailable", + "throttle", + "rate limit", + "too many", + ]; + TRANSIENT_MARKERS.iter().any(|m| lower.contains(m)) + } + + pub fn is_referral(&self) -> bool { + matches!( + self.code.as_deref().map(str::to_ascii_uppercase).as_deref(), + Some("REFERRAL") + ) + } + + pub fn is_nonexistent(&self) -> bool { + matches!( + self.code.as_deref().map(str::to_ascii_uppercase).as_deref(), + Some("NONEXISTENT") + ) + } +} + +impl fmt::Display for NoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.code { + Some(code) => write!(f, "({code}) {}", self.text), + None => f.write_str(&self.text), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn transient_markers_detected_case_insensitively() { + for s in [ + "Temporary failure", + "Try again later", + "Server unavailable", + "user is rate limited", + "Too many connections", + ] { + assert!( + NoError::new(s, None).is_transient(), + "should be transient: {s}" + ); + } + } + + #[test] + fn trylater_code_is_transient() { + assert!(NoError::new("x", Some("TRYLATER".into())).is_transient()); + } + + #[test] + fn quota_code_is_permanent_per_spec() { + assert!(!NoError::new("x", Some("QUOTA".into())).is_transient()); + assert!(!NoError::new("x", Some("QUOTA/MAXSCRIPTS".into())).is_transient()); + } + + #[test] + fn permanent_codes_override_text_match() { + + assert!(!NoError::new("try again later", Some("QUOTA".into())).is_transient()); + assert!(!NoError::new("temporarily", Some("AUTH-TOO-WEAK".into())).is_transient()); + assert!(!NoError::new("rate limit", Some("TRANSITION-NEEDED".into())).is_transient()); + } + + #[test] + fn referral_code_detected() { + assert!(NoError::new("x", Some("REFERRAL".into())).is_referral()); + assert!(!NoError::new("x", None).is_referral()); + } + + #[test] + fn nonexistent_code_detected() { + assert!(NoError::new("x", Some("NONEXISTENT".into())).is_nonexistent()); + assert!(NoError::new("x", Some("nonexistent".into())).is_nonexistent()); + } + + #[test] + fn non_transient_no_not_flagged() { + assert!(!NoError::new("Permission denied", None).is_transient()); + } +} diff --git a/src/managesieve/mod.rs b/src/managesieve/mod.rs new file mode 100644 index 0000000..dfbebad --- /dev/null +++ b/src/managesieve/mod.rs @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod client; +pub mod command; +pub mod error; +pub mod name; +pub mod response; +pub mod retry; + +pub use client::{ConnectMode, SieveClient}; +pub use error::{NoError, SieveError}; +pub use response::{ + Capabilities, ListedScript, ResponseBlock, Status, StatusLine, Token, parse_capabilities, + parse_getscript, parse_listscripts, read_response, try_parse_status, +}; diff --git a/src/managesieve/name.rs b/src/managesieve/name.rs new file mode 100644 index 0000000..c774459 --- /dev/null +++ b/src/managesieve/name.rs @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub fn encode_argument(s: &str, out: &mut String) { + let bytes = s.as_bytes(); + if can_be_quoted(bytes) { + out.push('"'); + for ch in s.chars() { + if ch == '"' || ch == '\\' { + out.push('\\'); + } + out.push(ch); + } + out.push('"'); + } else { + out.push('{'); + out.push_str(&bytes.len().to_string()); + out.push_str("+}\r\n"); + out.push_str(s); + } +} + +pub fn can_be_quoted(bytes: &[u8]) -> bool { + if bytes.len() > 1024 { + return false; + } + bytes + .iter() + .all(|&b| b != b'\r' && b != b'\n' && b != 0 && (b == b'\t' || (0x20..=0x7e).contains(&b))) +} + +pub fn contains_literal(bytes: &[u8]) -> bool { + bytes.contains(&b'{') +} + +#[cfg(test)] +mod tests { + use super::*; + + fn encoded(s: &str) -> String { + let mut out = String::new(); + encode_argument(s, &mut out); + out + } + + #[test] + fn ascii_round_trips_as_quoted_string() { + assert_eq!(encoded("vacation"), "\"vacation\""); + assert_eq!(encoded(""), "\"\""); + } + + #[test] + fn quotes_and_backslashes_are_escaped() { + assert_eq!(encoded("a\"b"), "\"a\\\"b\""); + assert_eq!(encoded("a\\b"), "\"a\\\\b\""); + } + + #[test] + fn non_ascii_promotes_to_literal_with_byte_exact_body() { + let name = "fancy\u{00e7}y"; + let out = encoded(name); + let in_bytes = name.as_bytes(); + let header = format!("{{{}+}}\r\n", in_bytes.len()); + assert!(out.starts_with(&header), "got {out:?}"); + let body = &out.as_bytes()[header.len()..]; + assert_eq!(body, in_bytes, "literal body must be byte-exact UTF-8"); + assert_eq!(body.len(), in_bytes.len()); + } + + #[test] + fn literal_length_matches_payload_byte_count_for_multibyte_runs() { + for s in [ + "\u{00e7}", + "na\u{00ef}ve", + "\u{4e2d}\u{6587}", + "mix-\u{00e9}\u{00e0}\u{00f6}", + ] { + let out = encoded(s); + let wire = out.as_bytes(); + let header = format!("{{{}+}}\r\n", s.len()); + assert!( + wire.starts_with(header.as_bytes()), + "expected header {header:?} for {s:?}, got {out:?}" + ); + let body = &wire[header.len()..]; + assert_eq!(body, s.as_bytes(), "literal body must equal input bytes"); + } + } + + #[test] + fn crlf_in_name_promotes_to_literal() { + let out = encoded("with\nline"); + assert!(out.starts_with('{')); + assert!(out.contains("+}\r\n")); + let header = format!("{{{}+}}\r\n", "with\nline".len()); + assert!(out.starts_with(&header)); + } + + #[test] + fn tab_is_allowed_in_quoted_string() { + assert!(can_be_quoted(b"abc\tdef")); + } + + #[test] + fn nul_byte_forces_literal() { + assert!(!can_be_quoted(b"a\0b")); + } + + #[test] + fn names_over_quoted_threshold_use_literal() { + let long: Vec = (0..2048).map(|_| b'x').collect(); + assert!(!can_be_quoted(&long)); + } + + #[test] + fn contains_literal_detects_open_brace() { + assert!(contains_literal(b"GETSCRIPT {3+}\r\nabc\r\n")); + assert!(!contains_literal(b"GETSCRIPT \"abc\"\r\n")); + } +} diff --git a/src/managesieve/response.rs b/src/managesieve/response.rs new file mode 100644 index 0000000..f67032d --- /dev/null +++ b/src/managesieve/response.rs @@ -0,0 +1,623 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{self, BufRead}; + +use super::error::{NoError, SieveError}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Token { + Atom(String), + QStr(String), + Literal(Vec), + LParen, + RParen, +} + +impl Token { + pub fn as_string_bytes(&self) -> Option> { + match self { + Token::QStr(s) => Some(s.as_bytes().to_vec()), + Token::Literal(b) => Some(b.clone()), + _ => None, + } + } + + pub fn as_string(&self) -> Option { + match self { + Token::QStr(s) => Some(s.clone()), + Token::Literal(b) => Some(String::from_utf8_lossy(b).into_owned()), + Token::Atom(s) => Some(s.clone()), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Status { + Ok, + No, + Bye, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StatusLine { + pub status: Status, + pub code: Option, + pub code_args: Option, + pub text: String, +} + +impl Default for StatusLine { + fn default() -> Self { + StatusLine { + status: Status::Ok, + code: None, + code_args: None, + text: String::new(), + } + } +} + +impl StatusLine { + pub fn into_no_error(self) -> NoError { + NoError::new(self.text, self.code) + } +} + +#[derive(Debug, Default)] +pub struct ResponseBlock { + pub data: Vec>, + pub status: StatusLine, +} + +pub fn read_response(reader: &mut R) -> Result { + let mut data: Vec> = Vec::new(); + loop { + let line = read_logical_line(reader)?; + if line.is_empty() { + continue; + } + if let Some(status) = try_parse_status(&line) { + return Ok(ResponseBlock { data, status }); + } + data.push(line); + } +} + +pub fn try_parse_status(tokens: &[Token]) -> Option { + let first = tokens.first()?; + let status = match first { + Token::Atom(s) => match s.as_str() { + "OK" | "ok" | "Ok" => Status::Ok, + "NO" | "no" | "No" => Status::No, + "BYE" | "bye" | "Bye" => Status::Bye, + _ => return None, + }, + _ => return None, + }; + let mut idx = 1usize; + let mut code: Option = None; + let mut code_args: Option = None; + if matches!(tokens.get(idx), Some(Token::LParen)) { + idx += 1; + let mut depth: i32 = 1; + let mut inside: Vec = Vec::new(); + while idx < tokens.len() && depth > 0 { + match &tokens[idx] { + Token::LParen => { + depth += 1; + inside.push(tokens[idx].clone()); + } + Token::RParen => { + depth -= 1; + if depth > 0 { + inside.push(tokens[idx].clone()); + } + } + t => inside.push(t.clone()), + } + idx += 1; + } + if !inside.is_empty() { + if let Some(name) = inside[0].as_string() { + code = Some(name); + } + if inside.len() > 1 { + let mut joined = String::new(); + for (i, t) in inside.iter().enumerate().skip(1) { + if let Some(s) = t.as_string() { + if i > 1 { + joined.push(' '); + } + joined.push_str(&s); + } + } + if !joined.is_empty() { + code_args = Some(joined); + } + } + } + } + let text = match tokens.get(idx) { + Some(Token::QStr(s)) => s.clone(), + Some(Token::Literal(b)) => String::from_utf8_lossy(b).into_owned(), + Some(Token::Atom(s)) => s.clone(), + _ => String::new(), + }; + Some(StatusLine { + status, + code, + code_args, + text, + }) +} + +fn read_logical_line(reader: &mut R) -> Result, SieveError> { + let mut tokens: Vec = Vec::new(); + let mut physical = Vec::with_capacity(256); + read_physical_line(reader, &mut physical)?; + let mut pos = 0usize; + loop { + while pos < physical.len() && matches!(physical[pos], b' ' | b'\t') { + pos += 1; + } + if pos >= physical.len() { + return Ok(tokens); + } + match physical[pos] { + b'"' => { + let (s, new_pos) = parse_quoted_string(&physical, pos + 1)?; + tokens.push(Token::QStr(s)); + pos = new_pos; + } + b'{' => { + let (n, plus, new_pos) = parse_literal_marker(&physical, pos + 1)?; + let _ = plus; + let trailing = &physical[new_pos..]; + if trailing.iter().any(|b| !matches!(b, b' ' | b'\t')) { + return Err(SieveError::Parse(format!( + "literal marker {{{n}}} not at end of line: trailing {:?}", + String::from_utf8_lossy(trailing) + ))); + } + let mut data = vec![0u8; n]; + reader.read_exact(&mut data)?; + tokens.push(Token::Literal(data)); + physical.clear(); + read_physical_line(reader, &mut physical)?; + pos = 0; + } + b'(' => { + tokens.push(Token::LParen); + pos += 1; + } + b')' => { + tokens.push(Token::RParen); + pos += 1; + } + _ => { + let start = pos; + while pos < physical.len() + && !matches!(physical[pos], b' ' | b'\t' | b'"' | b'{' | b'(' | b')') + { + pos += 1; + } + let atom = String::from_utf8_lossy(&physical[start..pos]).into_owned(); + tokens.push(Token::Atom(atom)); + } + } + } +} + +fn read_physical_line(reader: &mut R, buf: &mut Vec) -> Result<(), SieveError> { + buf.clear(); + let n = reader.read_until(b'\n', buf)?; + if n == 0 { + return Err(SieveError::Io(io::Error::new( + io::ErrorKind::UnexpectedEof, + "managesieve closed stream", + ))); + } + while matches!(buf.last(), Some(b'\n') | Some(b'\r')) { + buf.pop(); + } + Ok(()) +} + +fn parse_quoted_string(buf: &[u8], start: usize) -> Result<(String, usize), SieveError> { + let mut bytes: Vec = Vec::new(); + let mut i = start; + while i < buf.len() { + match buf[i] { + b'"' => { + let s = String::from_utf8_lossy(&bytes).into_owned(); + return Ok((s, i + 1)); + } + b'\\' if i + 1 < buf.len() => { + bytes.push(buf[i + 1]); + i += 2; + } + _ => { + bytes.push(buf[i]); + i += 1; + } + } + } + Err(SieveError::Parse("unterminated quoted-string".into())) +} + +fn parse_literal_marker(buf: &[u8], start: usize) -> Result<(usize, bool, usize), SieveError> { + let mut i = start; + let mut n: usize = 0; + let digits_start = i; + while i < buf.len() && buf[i].is_ascii_digit() { + n = n + .checked_mul(10) + .and_then(|v| v.checked_add((buf[i] - b'0') as usize)) + .ok_or_else(|| SieveError::Parse("literal size overflow".into()))?; + i += 1; + } + if i == digits_start { + return Err(SieveError::Parse("literal marker missing digits".into())); + } + let plus = i < buf.len() && buf[i] == b'+'; + if plus { + i += 1; + } + if i >= buf.len() || buf[i] != b'}' { + return Err(SieveError::Parse( + "literal marker missing closing brace".into(), + )); + } + Ok((n, plus, i + 1)) +} + +#[derive(Debug, Default, Clone)] +pub struct ListedScript { + pub name: String, + pub active: bool, +} + +pub fn parse_listscripts(data: &[Vec]) -> Result, SieveError> { + let mut out = Vec::with_capacity(data.len()); + for line in data { + let mut iter = line.iter(); + let first = match iter.next() { + Some(t) => t, + None => continue, + }; + let name = first + .as_string() + .ok_or_else(|| SieveError::Parse(format!("LISTSCRIPTS line missing name: {line:?}")))?; + let active = iter.any(|t| matches!(t, Token::Atom(a) if a.eq_ignore_ascii_case("ACTIVE"))); + out.push(ListedScript { name, active }); + } + Ok(out) +} + +pub fn parse_getscript(data: &[Vec]) -> Result, SieveError> { + let mut buf: Vec = Vec::new(); + let mut found = false; + for line in data { + for token in line { + match token { + Token::Literal(b) => { + if found { + return Err(SieveError::Parse( + "GETSCRIPT returned more than one body token".into(), + )); + } + buf = b.clone(); + found = true; + } + Token::QStr(s) => { + if found { + return Err(SieveError::Parse( + "GETSCRIPT returned more than one body token".into(), + )); + } + buf = s.as_bytes().to_vec(); + found = true; + } + _ => {} + } + } + } + if !found { + return Ok(Vec::new()); + } + Ok(buf) +} + +pub fn parse_capabilities(data: &[Vec]) -> Capabilities { + let mut caps = Capabilities::default(); + for line in data { + if line.is_empty() { + continue; + } + let name = match line[0].as_string() { + Some(s) => s, + None => continue, + }; + let value = if line.len() > 1 { + line[1..] + .iter() + .filter_map(|t| t.as_string()) + .collect::>() + .join(" ") + } else { + String::new() + }; + match name.to_ascii_uppercase().as_str() { + "IMPLEMENTATION" => caps.implementation = Some(value), + "VERSION" => caps.version = Some(value), + "SASL" => { + caps.sasl = value + .split_ascii_whitespace() + .map(|s| s.to_ascii_uppercase()) + .collect() + } + "SIEVE" => caps.sieve = Some(value), + "STARTTLS" => caps.starttls = true, + "MAXREDIRECTS" => caps.maxredirects = value.parse().ok(), + "NOTIFY" => caps.notify = Some(value), + "LANGUAGE" => caps.language = Some(value), + "OWNER" => caps.owner = Some(value), + other => { + caps.raw.push((other.to_ascii_uppercase(), value)); + } + } + } + caps +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Capabilities { + pub implementation: Option, + pub version: Option, + pub sasl: Vec, + pub sieve: Option, + pub starttls: bool, + pub maxredirects: Option, + pub notify: Option, + pub language: Option, + pub owner: Option, + pub raw: Vec<(String, String)>, +} + +impl Capabilities { + pub fn has_sasl(&self, mech: &str) -> bool { + self.sasl.iter().any(|m| m.eq_ignore_ascii_case(mech)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + fn cursor(s: &[u8]) -> Cursor> { + Cursor::new(s.to_vec()) + } + + #[test] + fn parses_greeting_with_quoted_pairs_then_ok() { + let input = + b"\"IMPLEMENTATION\" \"Test Server\"\r\n\"VERSION\" \"1.0\"\r\n\"STARTTLS\"\r\n\"SASL\" \"PLAIN LOGIN\"\r\nOK \"hello\"\r\n"; + let mut c = cursor(input); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::Ok); + assert_eq!(r.status.text, "hello"); + let caps = parse_capabilities(&r.data); + assert_eq!(caps.implementation, Some("Test Server".to_owned())); + assert_eq!(caps.version, Some("1.0".to_owned())); + assert!(caps.starttls); + assert!(caps.has_sasl("PLAIN")); + assert!(caps.has_sasl("login")); + assert!(!caps.has_sasl("XOAUTH2")); + } + + #[test] + fn parses_listscripts_with_active_marker() { + let input = b"\"vacation\"\r\n\"out-of-office\" ACTIVE\r\n\"forward\"\r\nOK\r\n"; + let mut c = cursor(input); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::Ok); + let list = parse_listscripts(&r.data).unwrap(); + assert_eq!(list.len(), 3); + assert!(!list[0].active); + assert!(list[1].active); + assert_eq!(list[1].name, "out-of-office"); + assert!(!list[2].active); + } + + #[test] + fn parses_listscripts_with_literal_name_then_active_on_same_logical_line() { + let input = b"{12+}\r\nfun\xc3\xa7y-name SP ACTIVE\r\nOK\r\n"; + let _ = input; + let body = b"\x67\xc3\xa9\x6e\xc3\xa9\x72\x69\x71\x75\x65"; + let mut data: Vec = Vec::new(); + data.extend_from_slice(format!("{{{}}}\r\n", body.len()).as_bytes()); + data.extend_from_slice(body); + data.extend_from_slice(b" ACTIVE\r\n"); + data.extend_from_slice(b"OK\r\n"); + let mut c = cursor(&data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::Ok); + let list = parse_listscripts(&r.data).unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list[0].name.as_bytes(), body); + assert!(list[0].active); + } + + #[test] + fn parses_getscript_literal_body() { + let script = b"require [\"fileinto\"];\nif true { fileinto \"INBOX\"; }\n"; + let mut data: Vec = Vec::new(); + data.extend_from_slice(format!("{{{}}}\r\n", script.len()).as_bytes()); + data.extend_from_slice(script); + data.extend_from_slice(b"\r\nOK\r\n"); + let mut c = cursor(&data); + let r = read_response(&mut c).unwrap(); + let body = parse_getscript(&r.data).unwrap(); + assert_eq!(body, script); + } + + #[test] + fn parses_getscript_quoted_one_line_body() { + let data = b"\"stop;\"\r\nOK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + let body = parse_getscript(&r.data).unwrap(); + assert_eq!(body, b"stop;"); + } + + #[test] + fn parses_no_with_code_and_text() { + let data = b"NO (NONEXISTENT) \"no such script\"\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::No); + assert_eq!(r.status.code.as_deref(), Some("NONEXISTENT")); + assert_eq!(r.status.text, "no such script"); + } + + #[test] + fn parses_no_without_code_and_quoted_text() { + let data = b"NO \"failed\"\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::No); + assert!(r.status.code.is_none()); + assert_eq!(r.status.text, "failed"); + } + + #[test] + fn parses_bye_referral_with_code_args() { + let data = b"BYE (REFERRAL \"sieve://other.example/\") \"go away\"\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::Bye); + assert_eq!(r.status.code.as_deref(), Some("REFERRAL")); + assert_eq!(r.status.text, "go away"); + assert!(r.status.code_args.unwrap().contains("other.example")); + } + + #[test] + fn parses_bare_ok_response() { + let data = b"OK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.status.status, Status::Ok); + assert!(r.status.text.is_empty()); + assert!(r.data.is_empty()); + } + + #[test] + fn quoted_string_escapes_backslash_and_quote() { + let data = b"\"a \\\"b\\\\\" \r\nOK\r\n"; + let _ = data; + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(b"\"a \\\"b\\\\\"\r\nOK\r\n"); + let mut c = cursor(&buf); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.data.len(), 1); + match &r.data[0][0] { + Token::QStr(s) => assert_eq!(s, "a \"b\\"), + other => panic!("expected QStr, got {other:?}"), + } + } + + #[test] + fn literal_size_overflow_is_parse_error() { + let data = b"{99999999999999999999}\r\nbody\r\nOK\r\n"; + let mut c = cursor(data); + let err = read_response(&mut c).unwrap_err(); + assert!(matches!(err, SieveError::Parse(_)), "got {err:?}"); + } + + #[test] + fn unterminated_quoted_string_is_parse_error() { + let data = b"\"unterminated\r\n"; + let mut c = cursor(data); + let err = read_response(&mut c).unwrap_err(); + assert!(matches!(err, SieveError::Parse(_)), "got {err:?}"); + } + + #[test] + fn capability_atoms_recognised_without_quotes() { + let data = b"IMPLEMENTATION \"X\"\r\nSTARTTLS\r\nSASL PLAIN\r\nOK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + let caps = parse_capabilities(&r.data); + assert_eq!(caps.implementation.as_deref(), Some("X")); + assert!(caps.starttls); + assert!(caps.has_sasl("PLAIN")); + } + + #[test] + fn capability_sasl_as_multiple_unquoted_atoms_captures_every_mechanism() { + let data = b"IMPLEMENTATION \"X\"\r\nSASL PLAIN LOGIN OAUTHBEARER\r\nOK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + let caps = parse_capabilities(&r.data); + assert!(caps.has_sasl("PLAIN"), "PLAIN missing: {:?}", caps.sasl); + assert!(caps.has_sasl("LOGIN"), "LOGIN missing: {:?}", caps.sasl); + assert!( + caps.has_sasl("OAUTHBEARER"), + "OAUTHBEARER missing: {:?}", + caps.sasl + ); + } + + #[test] + fn capability_sasl_as_single_quoted_string_captures_every_mechanism() { + let data = b"\"SASL\" \"PLAIN LOGIN OAUTHBEARER\"\r\nOK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + let caps = parse_capabilities(&r.data); + assert!(caps.has_sasl("PLAIN")); + assert!(caps.has_sasl("LOGIN")); + assert!(caps.has_sasl("OAUTHBEARER")); + } + + #[test] + fn quoted_string_preserves_non_ascii_utf8_bytes() { + let name_bytes: &[u8] = b"fancy\xc3\xa7y"; + let mut data: Vec = Vec::new(); + data.push(b'"'); + data.extend_from_slice(name_bytes); + data.push(b'"'); + data.extend_from_slice(b"\r\nOK\r\n"); + let mut c = cursor(&data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.data.len(), 1); + match &r.data[0][0] { + Token::QStr(s) => assert_eq!(s.as_bytes(), name_bytes), + other => panic!("expected QStr, got {other:?}"), + } + } + + #[test] + fn listscripts_quoted_name_with_non_ascii_is_byte_exact() { + let mut data: Vec = Vec::new(); + data.extend_from_slice(b"\"fun\xc3\xa7y\"\r\nOK\r\n"); + let mut c = cursor(&data); + let r = read_response(&mut c).unwrap(); + let list = parse_listscripts(&r.data).unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list[0].name.as_bytes(), b"fun\xc3\xa7y"); + } + + #[test] + fn parses_two_logical_lines_with_intervening_blank_skipped() { + let data = b"\"a\"\r\n\r\n\"b\"\r\nOK\r\n"; + let mut c = cursor(data); + let r = read_response(&mut c).unwrap(); + assert_eq!(r.data.len(), 2); + } +} diff --git a/src/managesieve/retry.rs b/src/managesieve/retry.rs new file mode 100644 index 0000000..521e52c --- /dev/null +++ b/src/managesieve/retry.rs @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use super::error::SieveError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Disposition { + TransportDrop, + Transient, + Permanent, + PerScriptRecoverable, + FeatureNegotiation, + Referral, +} + +pub fn classify(err: &SieveError) -> Disposition { + match err { + SieveError::Io(_) => Disposition::TransportDrop, + SieveError::Tls(_) => Disposition::Permanent, + SieveError::Bye(_) => Disposition::TransportDrop, + SieveError::No(no) => { + if no.is_referral() { + Disposition::Referral + } else if no.is_transient() { + Disposition::Transient + } else { + Disposition::Permanent + } + } + SieveError::Protocol(_) | SieveError::Parse(_) | SieveError::Unsupported(_) => { + Disposition::Permanent + } + SieveError::Referral(_) => Disposition::Referral, + } +} + +pub fn is_negotiation_failure(err: &SieveError) -> bool { + match err { + SieveError::No(no) => !no.is_transient() && !no.is_referral(), + SieveError::Unsupported(_) => true, + _ => false, + } +} + +pub fn script_disposition(err: &SieveError) -> Disposition { + let base = classify(err); + match base { + Disposition::Permanent | Disposition::FeatureNegotiation => { + Disposition::PerScriptRecoverable + } + other => other, + } +} + +#[cfg(test)] +mod tests { + use super::super::error::NoError; + use super::*; + use std::io::{self, ErrorKind}; + + #[test] + fn io_errors_are_transport_drop() { + for k in [ + ErrorKind::ConnectionReset, + ErrorKind::ConnectionAborted, + ErrorKind::BrokenPipe, + ErrorKind::UnexpectedEof, + ErrorKind::TimedOut, + ] { + let e = SieveError::Io(io::Error::new(k, "x")); + assert_eq!(classify(&e), Disposition::TransportDrop, "{k:?}"); + } + } + + #[test] + fn bye_is_transport_drop() { + assert_eq!( + classify(&SieveError::Bye("going away".into())), + Disposition::TransportDrop + ); + } + + #[test] + fn transient_no_text_marked_transient() { + let e = SieveError::No(NoError::new("Try again later", None)); + assert_eq!(classify(&e), Disposition::Transient); + } + + #[test] + fn trylater_code_marked_transient() { + let e = SieveError::No(NoError::new("x", Some("TRYLATER".into()))); + assert_eq!(classify(&e), Disposition::Transient); + } + + #[test] + fn referral_code_classified_as_referral() { + let e = SieveError::No(NoError::new("x", Some("REFERRAL".into()))); + assert_eq!(classify(&e), Disposition::Referral); + } + + #[test] + fn nonexistent_no_is_permanent_but_per_script_demotes_it() { + let e = SieveError::No(NoError::new("no such script", Some("NONEXISTENT".into()))); + assert_eq!(classify(&e), Disposition::Permanent); + assert_eq!(script_disposition(&e), Disposition::PerScriptRecoverable); + } + + #[test] + fn negotiation_failure_matches_non_transient_no_and_unsupported() { + assert!(is_negotiation_failure(&SieveError::Unsupported("x".into()))); + assert!(is_negotiation_failure(&SieveError::No(NoError::new( + "no such mech", + None + )))); + assert!(!is_negotiation_failure(&SieveError::No(NoError::new( + "try again", + None + )))); + assert!(!is_negotiation_failure(&SieveError::No(NoError::new( + "x", + Some("REFERRAL".into()) + )))); + } +} diff --git a/src/secret.rs b/src/secret.rs new file mode 100644 index 0000000..153427e --- /dev/null +++ b/src/secret.rs @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufRead, IsTerminal, Write}; +use std::process::Command; + +use crate::error::Error; + +pub fn resolve( + flag_value: Option<&str>, + env_var: &str, + prompt_label: &str, +) -> Result { + if let Some(value) = flag_value { + return Ok(value.to_owned()); + } + if let Ok(value) = std::env::var(env_var) + && !value.is_empty() + { + return Ok(value); + } + if std::io::stdin().is_terminal() { + return prompt_no_echo(prompt_label); + } + Err(Error::Usage(format!( + "no {prompt_label} provided: pass the flag, set ${env_var}, or run on a TTY for an interactive prompt" + ))) +} + +fn prompt_no_echo(label: &str) -> Result { + let mut stderr = std::io::stderr(); + write!(stderr, "{label}: ")?; + stderr.flush()?; + + let echo_off = set_echo(false); + let mut line = String::new(); + let read = std::io::stdin().lock().read_line(&mut line); + if echo_off { + let _ = set_echo(true); + } + writeln!(stderr)?; + stderr.flush()?; + read?; + + let trimmed = line.trim_end_matches(['\r', '\n']); + if trimmed.is_empty() { + return Err(Error::Usage(format!("empty {label}"))); + } + Ok(trimmed.to_owned()) +} + +fn set_echo(on: bool) -> bool { + let arg = if on { "echo" } else { "-echo" }; + Command::new("stty") + .arg(arg) + .stdin(std::process::Stdio::inherit()) + .status() + .map(|s| s.success()) + .unwrap_or(false) +} diff --git a/src/sync/emailmeta.rs b/src/sync/emailmeta.rs new file mode 100644 index 0000000..104e1d4 --- /dev/null +++ b/src/sync/emailmeta.rs @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use mail_parser::{Address, HeaderName, HeaderValue, MessageParser}; + +use crate::sync::keys::{EmailIndex, email_index}; + +fn addrs(a: Option<&Address>) -> Vec { + match a { + Some(addr) => addr + .iter() + .filter_map(|x| x.address().map(|s| s.to_owned())) + .collect(), + None => Vec::new(), + } +} + +fn message_ids(value: &HeaderValue) -> Vec { + match value { + HeaderValue::Text(s) => vec![s.to_string()], + HeaderValue::TextList(l) => l.iter().map(|s| s.to_string()).collect(), + _ => Vec::new(), + } +} + +pub fn email_index_from_blob(bytes: &[u8]) -> EmailIndex { + email_meta_from_blob(bytes).0 +} + +pub fn email_meta_from_blob(bytes: &[u8]) -> (EmailIndex, Option) { + let Some(m) = MessageParser::default().parse(bytes) else { + return (email_index(&[], &[], "", "", &[]), None); + }; + let mut mids: Vec = Vec::new(); + for v in m.header_values(HeaderName::MessageId) { + mids.extend(message_ids(v)); + } + if mids.is_empty() + && let Some(single) = m.message_id() + { + mids.push(single.to_owned()); + } + let date = m.date().map(|d| d.to_rfc3339()); + let idx = email_index( + &mids, + &addrs(m.from()), + m.subject().unwrap_or(""), + date.as_deref().unwrap_or(""), + &addrs(m.to()), + ); + (idx, date) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::keys::{EmailKey, email_key}; + + #[test] + fn parses_core_fields_and_keys_by_message_id() { + let raw = b"From: Alice \r\nTo: Bob \r\nSubject: Hi there\r\nMessage-ID: \r\nDate: Mon, 12 May 2025 10:00:00 +0200\r\n\r\nbody\r\n"; + let idx = email_index_from_blob(raw); + assert_eq!(idx.mids, vec!["abc-1@host".to_owned()]); + assert_eq!( + email_key(&idx), + EmailKey::MessageId("abc-1@host".to_owned()) + ); + } + + #[test] + fn sent_at_offset_is_preserved_then_z_for_utc() { + let off = email_index_from_blob( + b"Message-ID: \r\nDate: Mon, 12 May 2025 10:00:00 +0200\r\n\r\nx", + ); + let utc = email_index_from_blob( + b"Message-ID: \r\nDate: Mon, 12 May 2025 08:00:00 +0000\r\n\r\nx", + ); + + assert_ne!(off.fb, utc.fb); + } + + #[test] + fn missing_message_id_falls_back_to_hash() { + let idx = email_index_from_blob(b"From: a@x\r\nSubject: only\r\n\r\nbody"); + assert!(idx.mids.is_empty()); + assert!(matches!(email_key(&idx), EmailKey::Fallback(_))); + } + + #[test] + fn multiple_message_id_values_are_all_captured() { + let idx = + email_index_from_blob(b"Message-ID: \r\nMessage-ID: \r\nSubject: s\r\n\r\nx"); + assert_eq!(idx.mids, vec!["a@h".to_owned(), "b@h".to_owned()]); + assert_eq!( + email_key(&idx), + EmailKey::MessageId("a@h\u{1f}b@h".to_owned()) + ); + } + + #[test] + fn same_message_different_received_metadata_same_key() { + let a = email_index_from_blob( + b"From: a@x\r\nSubject: S\r\nMessage-ID: \r\nDate: Mon, 12 May 2025 10:00:00 +0200\r\n\r\nbody", + ); + let b = email_index_from_blob( + b"From: a@x\r\nSubject: S\r\nMessage-ID: \r\nDate: Tue, 13 May 2025 11:00:00 +0000\r\n\r\nbody", + ); + assert_eq!(email_key(&a), email_key(&b)); + } +} diff --git a/src/sync/export.rs b/src/sync/export.rs new file mode 100644 index 0000000..51e3cc5 --- /dev/null +++ b/src/sync/export.rs @@ -0,0 +1,501 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::io::{IsTerminal, Write}; + +use rusqlite::Connection; +use serde_json::{Map, Value, json}; + +use crate::db; +use crate::error::Error; +use crate::jmap::blobxfer; +use crate::jmap::connect::{self, Connected}; +use crate::jmap::error::JmapError; +use crate::jmap::http::HttpClient; +use crate::jmap::request::{Request, SetRequest, get_all, get_objects, query_all_ids, set_call}; +use crate::jmap::session::{Limits, Session}; +use crate::jmap::wire::JmapId; +use crate::logging::{LEVEL_DEFAULT, Logger}; +use crate::sync::import_jmap::mapping::{BlobUpload, TargetResolver}; +use crate::sync::{CommonConfig, Context, ExportConfig, Summary, TypeCounts}; +use crate::types::ObjectType; + +const EXPORT_ORDER: [ObjectType; 10] = [ + ObjectType::Mailbox, + ObjectType::AddressBook, + ObjectType::Calendar, + ObjectType::FileNode, + ObjectType::Identity, + ObjectType::SieveScript, + ObjectType::ParticipantIdentity, + ObjectType::Email, + ObjectType::ContactCard, + ObjectType::CalendarEvent, +]; + +type IdMap = HashMap; + +#[derive(Default)] +struct Maps { + m: HashMap, +} + +impl Maps { + fn insert(&mut self, ty: ObjectType, local: i64, target: JmapId) { + self.m.entry(ty).or_default().insert(local, target); + } +} + +impl TargetResolver for Maps { + fn target(&self, ty: ObjectType, local_id: i64) -> Option { + self.m.get(&ty)?.get(&local_id).cloned() + } +} + +struct Uploader<'a> { + net: &'a Net, + conn: &'a Connection, + cache: HashMap, +} + +impl<'a> Uploader<'a> { + fn new(net: &'a Net, conn: &'a Connection) -> Uploader<'a> { + Uploader { + net, + conn, + cache: HashMap::new(), + } + } + + fn upload_with(&mut self, local_id: i64, content_type: &str) -> Result { + if let Some(id) = self.cache.get(&local_id) { + return Ok(id.clone()); + } + let id = if self.net.dry_run { + let _exists = db::blobs::blob_bytes(self.conn, local_id)? + .ok_or_else(|| JmapError::malformed(format!("blob local id {local_id} missing")))?; + JmapId(format!("dryrun-blob-{local_id}")) + } else { + let bytes = db::blobs::blob_bytes(self.conn, local_id)? + .ok_or_else(|| JmapError::malformed(format!("blob local id {local_id} missing")))?; + blobxfer::upload_bytes( + &self.net.client, + &self.net.session, + &self.net.account, + content_type, + &bytes, + )? + }; + self.cache.insert(local_id, id.clone()); + Ok(id) + } +} + +impl BlobUpload for Uploader<'_> { + fn upload(&mut self, local_id: i64) -> Result { + self.upload_with(local_id, "application/octet-stream") + } +} + +#[derive(Clone)] +struct Net { + client: HttpClient, + api: String, + account: String, + limits: Limits, + session: Session, + dry_run: bool, +} + +fn has_rows(conn: &Connection, ty: ObjectType) -> bool { + let table = crate::sync::table_name(ty); + conn.query_row(&format!("SELECT EXISTS(SELECT 1 FROM {table})"), [], |r| { + r.get::<_, i64>(0) + }) + .map(|n| n != 0) + .unwrap_or(false) +} + +pub fn run(common: CommonConfig, config: ExportConfig) -> Result { + let logger = common.logger; + let ctx = Context::open(common, &config.connect)?; + let connected = connect::prepare(&ctx, &config.connect)?; + + let net = Net { + client: ctx.client.clone(), + api: connected.session.api_url.clone(), + account: connected.account_id.clone(), + limits: connected.limits, + session: connected.session.clone(), + dry_run: ctx.dry_run(), + }; + + let work = work_list(&ctx.conn, &config, &connected, &logger); + let mut maps = Maps::default(); + let mut summary = Summary::default(); + let mut dry_rows: Vec<(&'static str, u64, u64, u64)> = Vec::new(); + let mut plans: HashMap = HashMap::new(); + let mut counts_per_type: HashMap = HashMap::new(); + + for ty in &work { + if logger.enabled(LEVEL_DEFAULT) { + eprintln!("export: {} ...", ty.jmap_name()); + } + let mut counts = TypeCounts::default(); + let res = reconcile_type( + &ctx, + &net, + *ty, + &mut maps, + &logger, + &mut counts, + &mut dry_rows, + ); + let plan = match res { + Ok(p) => p, + Err(e) => { + logger.warn(&format!("type {} aborted: {e}", ty.jmap_name())); + counts.failed += 1; + Plan::default() + } + }; + plans.insert(*ty, plan); + counts_per_type.insert(*ty, counts); + } + + if config.prune { + prune_phase( + &ctx, + &net, + &work, + &plans, + &config, + &logger, + &mut counts_per_type, + )?; + } + + for ty in &work { + if let Some(counts) = counts_per_type.remove(ty) { + summary.per_type.push((ty.jmap_name(), counts)); + } + } + + if ctx.dry_run() { + print_dry_run(&dry_rows, config.prune); + return Ok(Summary::default()); + } + summary.retries_observed = ctx.client.retries_observed(); + summary.retry_after_sleeps = ctx.client.retry_after_sleeps(); + Ok(summary) +} + +fn prune_phase( + ctx: &Context, + net: &Net, + work: &[ObjectType], + plans: &HashMap, + config: &ExportConfig, + logger: &Logger, + counts_per_type: &mut HashMap, +) -> Result<(), Error> { + let totals: Vec<(ObjectType, &Plan)> = work + .iter() + .filter_map(|ty| plans.get(ty).map(|p| (*ty, p))) + .filter(|(_, p)| !p.prune_candidates.is_empty()) + .collect(); + if totals.is_empty() { + return Ok(()); + } + eprintln!("prune plan:"); + let total: usize = totals.iter().map(|(_, p)| p.prune_candidates.len()).sum(); + for (ty, p) in &totals { + eprintln!( + " {:<22} {:>6} candidate(s); sample: {}", + ty.jmap_name(), + p.prune_candidates.len(), + sample(&p.prune_candidates), + ); + } + eprintln!(" {:<22} {:>6} total", "(all types)", total); + if ctx.dry_run() { + return Ok(()); + } + if !config.yes && std::io::stdin().is_terminal() { + eprint!("destroy all {total} objects across all types? [y/N] "); + let _ = std::io::stderr().flush(); + let mut line = String::new(); + std::io::stdin() + .read_line(&mut line) + .map_err(|e| Error::Partial(e.to_string()))?; + if !matches!(line.trim(), "y" | "Y" | "yes") { + return Err(Error::PruneAborted); + } + } + for ty in work.iter().rev() { + if let Some(plan) = plans.get(ty) + && !plan.prune_candidates.is_empty() + && let Some(counts) = counts_per_type.get_mut(ty) + { + do_destroy(net, *ty, plan, logger, counts); + } + } + Ok(()) +} + +fn work_list( + conn: &Connection, + config: &ExportConfig, + connected: &Connected, + logger: &Logger, +) -> Vec { + let selected = config.objects.as_ref(); + EXPORT_ORDER + .into_iter() + .filter(|ty| selected.map(|s| s.contains(ty)).unwrap_or(true)) + .filter(|ty| has_rows(conn, *ty)) + .filter(|ty| { + if connected.supports(*ty) { + true + } else { + logger.warn(&format!( + "target does not support {}; skipping", + ty.jmap_name() + )); + false + } + }) + .collect() +} + +#[allow(clippy::too_many_arguments)] +fn reconcile_type( + ctx: &Context, + net: &Net, + ty: ObjectType, + maps: &mut Maps, + logger: &Logger, + counts: &mut TypeCounts, + dry_rows: &mut Vec<(&'static str, u64, u64, u64)>, +) -> Result { + let plan = match ty { + ObjectType::Mailbox | ObjectType::FileNode => { + tree::reconcile(ctx, net, ty, maps, counts, logger) + } + ObjectType::AddressBook | ObjectType::Calendar => { + flat::reconcile(ctx, net, ty, maps, counts, logger) + } + ObjectType::Identity => keyed::reconcile_identity(ctx, net, maps, counts, logger), + ObjectType::ParticipantIdentity => { + keyed::reconcile_participant(ctx, net, maps, counts, logger) + } + ObjectType::SieveScript => sieve::reconcile(ctx, net, maps, counts, logger), + ObjectType::ContactCard | ObjectType::CalendarEvent => { + uidtype::reconcile(ctx, net, ty, maps, counts, logger) + } + ObjectType::Email => email::reconcile(ctx, net, maps, counts, logger), + }?; + + if ctx.dry_run() { + dry_rows.push(( + ty.jmap_name(), + counts.created, + counts.skipped, + plan.prune_candidates.len() as u64, + )); + } + + Ok(plan) +} + +#[derive(Default)] +pub struct Plan { + pub prune_candidates: Vec, + pub active_sieve_target: Option, +} + +fn do_destroy(net: &Net, ty: ObjectType, plan: &Plan, logger: &Logger, counts: &mut TypeCounts) { + if ty == ObjectType::SieveScript { + deactivate_active_sieve_script(net, logger); + } + let destroy = Value::Array( + plan.prune_candidates + .iter() + .map(|s| Value::String(s.clone())) + .collect(), + ); + let extra = destroy_contents_arg(ty); + match set_call( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + SetRequest { + destroy: Some(destroy), + extra_args: &extra, + ..Default::default() + }, + &net.limits, + ) { + Ok(outcome) => { + counts.deleted += outcome.destroyed.len() as u64; + for (id, err) in &outcome.not_destroyed { + logger.warn(&format!( + "prune: {} {id} not destroyed: {err}", + ty.jmap_name() + )); + counts.skipped += 1; + } + } + Err(e) => { + logger.warn(&format!( + "prune {}: destroy request failed: {e}", + ty.jmap_name() + )); + counts.failed += plan.prune_candidates.len() as u64; + } + } +} + +fn deactivate_active_sieve_script(net: &Net, logger: &Logger) { + let mut req = Request::new(); + req.call( + "SieveScript/set", + json!({ "accountId": net.account, "onSuccessDeactivateScript": true }), + "d", + ); + let outcome = req.send(&net.client, &net.api).and_then(|resp| { + let mr = resp.first()?; + crate::jmap::request::check_method_error(mr) + }); + if let Err(e) = outcome { + logger.warn(&format!( + "prune: SieveScript deactivation failed before destroy: {e}" + )); + } +} + +fn destroy_contents_arg(ty: ObjectType) -> Vec<(&'static str, Value)> { + match ty { + ObjectType::AddressBook => vec![("onDestroyRemoveContents", Value::Bool(false))], + ObjectType::Calendar => vec![("onDestroyRemoveEvents", Value::Bool(false))], + ObjectType::FileNode => vec![("onDestroyRemoveChildren", Value::Bool(false))], + _ => Vec::new(), + } +} + +fn sample(ids: &[String]) -> String { + let n = ids.len().min(5); + ids[..n].join(", ") +} + +fn print_dry_run(rows: &[(&'static str, u64, u64, u64)], prune: bool) { + if prune { + println!( + "{:<22} {:>10} {:>10} {:>12}", + "TYPE", "CREATE", "MATCHED", "WOULD-DESTROY" + ); + for (ty, c, m, d) in rows { + println!("{ty:<22} {c:>10} {m:>10} {d:>12}"); + } + } else { + println!("{:<22} {:>10} {:>10}", "TYPE", "CREATE", "MATCHED"); + for (ty, c, m, _) in rows { + println!("{ty:<22} {c:>10} {m:>10}"); + } + } +} + +mod tree; + +mod flat; + +mod keyed; + +mod sieve; + +mod uidtype; + +mod email; + +mod common { + use super::*; + + pub fn target_query_get( + net: &Net, + ty: ObjectType, + props: Option<&[&str]>, + ) -> Result, JmapError> { + let ids = query_all_ids( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + &net.limits, + )?; + if ids.is_empty() { + return Ok(Vec::new()); + } + let got = get_objects::( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + &ids, + props, + &net.limits, + )?; + Ok(got.list) + } + + pub fn target_get_all(net: &Net, ty: ObjectType) -> Result, JmapError> { + Ok(get_all::(&net.client, &net.api, &net.account, ty.jmap_name())?.list) + } + + pub fn jid(v: &Value) -> Option { + v.get("id").and_then(Value::as_str).map(str::to_owned) + } + + pub fn create_batch( + net: &Net, + ty: ObjectType, + creates: Vec<(String, Value)>, + ) -> Result { + if net.dry_run { + return Ok(synthesize_dry_run_outcome(ty, &creates)); + } + let mut map = Map::new(); + for (cid, obj) in creates { + map.insert(cid, obj); + } + set_call( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + SetRequest { + create: Some(Value::Object(map)), + ..Default::default() + }, + &net.limits, + ) + } + + fn synthesize_dry_run_outcome( + ty: ObjectType, + creates: &[(String, Value)], + ) -> crate::jmap::request::SetOutcome { + let mut outcome = crate::jmap::request::SetOutcome::default(); + for (cid, _) in creates { + let synthetic = serde_json::json!({ + "id": format!("dryrun-{}-{cid}", ty.jmap_name()) + }); + outcome.created.push((cid.clone(), synthetic)); + } + outcome + } +} diff --git a/src/sync/export/email.rs b/src/sync/export/email.rs new file mode 100644 index 0000000..f1d75ec --- /dev/null +++ b/src/sync/export/email.rs @@ -0,0 +1,253 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use serde_json::{Map, Value, json}; + +use super::common::{jid, target_query_get}; +use super::{Maps, Net, Plan, Uploader}; +use crate::error::Error; +use crate::jmap::error::JmapError; +use crate::jmap::request::{Request, check_method_error, get_objects}; +use crate::jmap::wire::JmapId; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{EMAIL_SELECT, TargetResolver, row_to_email}; +use crate::sync::keys::{EmailIndex, EmailKey, email_index, email_keys, index_from_json}; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +fn server_index(v: &Value) -> EmailIndex { + let arr = |k: &str| { + v.get(k) + .and_then(Value::as_array) + .map(|a| { + a.iter() + .filter_map(|x| x.get("email").and_then(Value::as_str).map(str::to_owned)) + .collect::>() + }) + .unwrap_or_default() + }; + let mids: Vec = v + .get("messageId") + .and_then(Value::as_array) + .map(|a| { + a.iter() + .filter_map(|x| x.as_str().map(str::to_owned)) + .collect() + }) + .unwrap_or_default(); + email_index( + &mids, + &arr("from"), + v.get("subject").and_then(Value::as_str).unwrap_or(""), + v.get("sentAt").and_then(Value::as_str).unwrap_or(""), + &arr("to"), + ) +} + +pub fn reconcile( + ctx: &Context, + net: &Net, + maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let ty = ObjectType::Email; + + let target_min = target_query_get(net, ty, Some(&["messageId"])).map_err(Error::from)?; + let mut indices: Vec = target_min.iter().map(server_index).collect(); + + let fallback_ids: Vec = target_min + .iter() + .zip(indices.iter()) + .filter(|(_, i)| i.mids.is_empty()) + .filter_map(|(v, _)| jid(v).map(JmapId)) + .collect(); + if !fallback_ids.is_empty() { + let got = get_objects::( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + &fallback_ids, + Some(&["messageId", "from", "subject", "sentAt", "to"]), + &net.limits, + ) + .map_err(Error::from)?; + let by_id: HashMap = got + .list + .iter() + .filter_map(|v| jid(v).map(|i| (i, v))) + .collect(); + for (v, slot) in target_min.iter().zip(indices.iter_mut()) { + if let Some(full) = jid(v).and_then(|i| by_id.get(&i)) { + *slot = server_index(full); + } + } + } + let target_keys: HashSet = email_keys(&indices).into_iter().collect(); + + let local: Vec<(i64, crate::sync::import_jmap::mapping::EmailRow)> = { + let mut stmt = ctx + .conn + .prepare(EMAIL_SELECT) + .map_err(|e| Error::Partial(e.to_string()))?; + stmt.query_map([], |row| { + let id: i64 = row.get(0)?; + Ok((id, row_to_email(row))) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + .into_iter() + .map(|(id, r)| Ok((id, r.map_err(Error::from)?))) + .collect::>()? + }; + + let local_indices: Vec = local + .iter() + .map(|(_, r)| index_from_json(&r.message_match)) + .collect(); + let local_keys = email_keys(&local_indices); + + let mut uploader = Uploader::new(net, &ctx.conn); + for (i, key) in local_keys.iter().enumerate() { + if target_keys.contains(key) { + counts.skipped += 1; + continue; + } + let (local_id, row) = &local[i]; + let mut mids = Map::new(); + let mut all_resolved = true; + for ml in &row.mailbox_locals { + match maps.target(ObjectType::Mailbox, *ml) { + Some(t) => { + mids.insert(t.0, Value::Bool(true)); + } + None => { + all_resolved = false; + break; + } + } + } + if !all_resolved { + logger.warn(&format!( + "email local {local_id} skipped: mailbox not on target" + )); + counts.failed += 1; + continue; + } + let blob = match uploader.upload_with(row.blob_local_id, "message/rfc822") { + Ok(b) => b.0, + Err(e) => { + logger.warn(&format!("email blob upload failed: {e}")); + counts.failed += 1; + continue; + } + }; + let mut kw = Map::new(); + for k in &row.keywords { + kw.insert(k.clone(), Value::Bool(true)); + } + let item = json!({ + "blobId": blob, + "mailboxIds": Value::Object(mids), + "keywords": Value::Object(kw), + "receivedAt": row.received_at, + }); + send_import_chunk(net, &[(format!("e{local_id}"), item)], counts, logger); + } + + Ok(Plan::default()) +} + +fn send_import_chunk( + net: &Net, + items: &[(String, Value)], + counts: &mut TypeCounts, + logger: &Logger, +) { + if items.is_empty() { + return; + } + if net.dry_run { + counts.created += items.len() as u64; + return; + } + let mut map = Map::new(); + for (k, v) in items { + map.insert(k.clone(), v.clone()); + } + let mut req = Request::new(); + req.call( + "Email/import", + json!({ "accountId": net.account, "emails": Value::Object(map) }), + "i", + ); + if req.fits(&net.limits).is_err() { + resplit_or_fail(net, items, counts, logger); + return; + } + match req.send(&net.client, &net.api) { + Ok(resp) => match resp.first().and_then(|mr| { + check_method_error(mr)?; + Ok(mr) + }) { + Ok(mr) => absorb_import(mr, counts, logger), + Err(JmapError::RequestTooLarge) => resplit_or_fail(net, items, counts, logger), + Err(JmapError::Method { error_type, .. }) if error_type == "requestTooLarge" => { + resplit_or_fail(net, items, counts, logger); + } + Err(e) => { + logger.warn(&format!( + "Email/import method error ({} items): {e}", + items.len() + )); + counts.failed += items.len() as u64; + } + }, + Err(JmapError::RequestTooLarge) => resplit_or_fail(net, items, counts, logger), + Err(e) => { + logger.warn(&format!( + "Email/import send failed ({} items): {e}", + items.len() + )); + counts.failed += items.len() as u64; + } + } +} + +fn resplit_or_fail(net: &Net, items: &[(String, Value)], counts: &mut TypeCounts, logger: &Logger) { + if items.len() <= 1 { + for (cid, _) in items { + logger.warn(&format!( + "Email/import {cid} exceeds maxSizeRequest alone; skipped" + )); + } + counts.failed += items.len() as u64; + return; + } + let mid = items.len() / 2; + send_import_chunk(net, &items[..mid], counts, logger); + send_import_chunk(net, &items[mid..], counts, logger); +} + +fn absorb_import(mr: &crate::jmap::request::MethodCall, counts: &mut TypeCounts, logger: &Logger) { + if let Some(created) = mr.args.get("created").and_then(Value::as_object) { + counts.created += created.len() as u64; + } + if let Some(nc) = mr.args.get("notCreated").and_then(Value::as_object) { + for (cid, err) in nc { + let etype = err.get("type").and_then(Value::as_str).unwrap_or(""); + if etype == "alreadyExists" { + counts.skipped += 1; + } else { + logger.warn(&format!("Email/import {cid} failed: {err}")); + counts.failed += 1; + } + } + } +} diff --git a/src/sync/export/flat.rs b/src/sync/export/flat.rs new file mode 100644 index 0000000..0723fc9 --- /dev/null +++ b/src/sync/export/flat.rs @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::params; +use serde_json::{Value, json}; + +use super::common::{create_batch, jid, target_get_all}; +use super::{Maps, Net, Plan}; +use crate::error::Error; +use crate::jmap::wire::JmapId; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{ + ADDRESS_BOOK_SELECT, CALENDAR_SELECT, row_to_address_book, row_to_calendar, +}; +use crate::sync::keys::fold_name; +use crate::sync::prune::{TargetObj, candidates}; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +pub fn reconcile( + ctx: &Context, + net: &Net, + ty: ObjectType, + maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let table = crate::sync::table_name(ty); + let select = if ty == ObjectType::AddressBook { + ADDRESS_BOOK_SELECT + } else { + CALENDAR_SELECT + }; + + let locals: Vec<(i64, String, bool)> = { + let mut stmt = ctx + .conn + .prepare(&format!("SELECT id, name, is_default FROM {table}")) + .map_err(|e| Error::Partial(e.to_string()))?; + stmt.query_map([], |r| { + Ok((r.get(0)?, r.get::<_, String>(1)?, r.get::<_, i64>(2)? != 0)) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + }; + + let targets = target_get_all(net, ty).map_err(Error::from)?; + let mut tmatched = std::collections::HashSet::new(); + + let mut to_create: Vec<(i64, bool)> = Vec::new(); + for (local, name, is_default) in &locals { + let hit = targets.iter().find(|t| { + let tid = jid(t); + t.get("name") + .and_then(Value::as_str) + .map(|n| fold_name(n) == fold_name(name)) + .unwrap_or(false) + && tid.as_ref().map(|i| !tmatched.contains(i)).unwrap_or(false) + }); + match hit.and_then(jid) { + Some(tid) => { + tmatched.insert(tid.clone()); + maps.insert(ty, *local, JmapId(tid)); + counts.skipped += 1; + } + None => to_create.push((*local, *is_default)), + } + } + + if !to_create.is_empty() { + let mut batch = Vec::new(); + for (local, _) in &to_create { + let wire = ctx + .conn + .query_row(&format!("{select} WHERE id = ?1"), params![local], |row| { + Ok(if ty == ObjectType::AddressBook { + row_to_address_book(row).map(|w| serde_json::to_value(&w)) + } else { + row_to_calendar(row).map(|w| serde_json::to_value(&w)) + }) + }) + .map_err(|e| Error::Partial(e.to_string()))? + .map_err(Error::from)? + .map_err(|e| Error::Partial(e.to_string()))?; + let mut wire = wire; + if let Value::Object(m) = &mut wire { + m.remove("isDefault"); + } + batch.push((format!("c{local}"), wire)); + } + let outcome = create_batch(net, ty, batch).map_err(Error::from)?; + for (cid, v) in &outcome.created { + if let Some(local) = cid.strip_prefix('c').and_then(|s| s.parse::().ok()) + && let Some(id) = jid(v) + { + maps.insert(ty, local, JmapId(id.clone())); + counts.created += 1; + if to_create.iter().any(|(l, d)| *l == local && *d) { + let mut req = crate::jmap::request::Request::new(); + req.call( + format!("{}/set", ty.jmap_name()), + json!({ "accountId": net.account, "onSuccessSetIsDefault": id }), + "d", + ); + if let Err(e) = req.send(&net.client, &net.api) { + logger.warn(&format!("{} isDefault not set: {e}", ty.jmap_name())); + } + } + } + } + for (cid, err) in &outcome.not_created { + logger.warn(&format!("{} {cid} not created: {err}", ty.jmap_name())); + counts.failed += 1; + } + } + + let objs: Vec = targets + .iter() + .filter_map(|t| { + let id = jid(t)?; + Some(TargetObj { + id: id.clone(), + matched: tmatched.contains(&id), + protected: t.get("isDefault").and_then(Value::as_bool).unwrap_or(false), + may_delete: t + .get("myRights") + .and_then(|r| r.get("mayDelete")) + .and_then(Value::as_bool) + .unwrap_or(true), + parent: None, + }) + }) + .collect(); + Ok(Plan { + prune_candidates: candidates(&objs, false), + active_sieve_target: None, + }) +} diff --git a/src/sync/export/keyed.rs b/src/sync/export/keyed.rs new file mode 100644 index 0000000..c1e565f --- /dev/null +++ b/src/sync/export/keyed.rs @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; + +use serde_json::Value; + +use super::common::{create_batch, target_get_all}; +use super::{Maps, Net, Plan}; +use crate::error::Error; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{ + IDENTITY_SELECT, PARTICIPANT_IDENTITY_SELECT, row_to_identity, row_to_participant_identity, +}; +use crate::sync::keys::{identity_key, participant_identity_key}; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +fn run( + net: &Net, + ty: ObjectType, + counts: &mut TypeCounts, + logger: &Logger, + target_key: impl Fn(&Value) -> Option<[u8; 32]>, + local_rows: Vec<(i64, [u8; 32], Value)>, +) -> Result { + let targets = target_get_all(net, ty).map_err(Error::from)?; + let mut target_keys: HashSet<[u8; 32]> = HashSet::new(); + for t in &targets { + if let Some(k) = target_key(t) { + target_keys.insert(k); + } + } + + let mut seen: HashSet<[u8; 32]> = HashSet::new(); + let mut batch = Vec::new(); + for (local, key, wire) in local_rows { + if target_keys.contains(&key) || !seen.insert(key) { + counts.skipped += 1; + continue; + } + batch.push((format!("c{local}"), wire)); + } + if !batch.is_empty() { + let outcome = create_batch(net, ty, batch).map_err(Error::from)?; + counts.created += outcome.created.len() as u64; + for (cid, err) in &outcome.not_created { + logger.warn(&format!( + "{} {cid} not created (expected for non-owned addresses): {err}", + ty.jmap_name() + )); + counts.skipped += 1; + } + } + Ok(Plan::default()) +} + +pub fn reconcile_identity( + ctx: &Context, + net: &Net, + _maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let mut stmt = ctx + .conn + .prepare(IDENTITY_SELECT) + .map_err(|e| Error::Partial(e.to_string()))?; + let rows: Vec<(i64, [u8; 32], Value)> = stmt + .query_map([], |row| { + let id: i64 = row.get(0)?; + Ok((id, row_to_identity(row))) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + .into_iter() + .map(|(id, w)| { + let w = w.map_err(Error::from)?; + let key = identity_key(&w.name, &w.email); + let v = serde_json::to_value(&w).map_err(|e| Error::Partial(e.to_string()))?; + Ok((id, key, v)) + }) + .collect::>()?; + drop(stmt); + run( + net, + ObjectType::Identity, + counts, + logger, + |t| { + Some(identity_key( + t.get("name").and_then(Value::as_str).unwrap_or(""), + t.get("email").and_then(Value::as_str).unwrap_or(""), + )) + }, + rows, + ) +} + +pub fn reconcile_participant( + ctx: &Context, + net: &Net, + _maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let mut stmt = ctx + .conn + .prepare(PARTICIPANT_IDENTITY_SELECT) + .map_err(|e| Error::Partial(e.to_string()))?; + let rows: Vec<(i64, [u8; 32], Value)> = stmt + .query_map([], |row| { + let id: i64 = row.get(0)?; + Ok((id, row_to_participant_identity(row))) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + .into_iter() + .map(|(id, w)| { + let w = w.map_err(Error::from)?; + let key = participant_identity_key(&w.calendar_address, &w.name); + let v = serde_json::to_value(&w).map_err(|e| Error::Partial(e.to_string()))?; + Ok((id, key, v)) + }) + .collect::>()?; + drop(stmt); + run( + net, + ObjectType::ParticipantIdentity, + counts, + logger, + |t| { + Some(participant_identity_key( + t.get("calendarAddress") + .and_then(Value::as_str) + .unwrap_or(""), + t.get("name").and_then(Value::as_str).unwrap_or(""), + )) + }, + rows, + ) +} diff --git a/src/sync/export/sieve.rs b/src/sync/export/sieve.rs new file mode 100644 index 0000000..1cdb59b --- /dev/null +++ b/src/sync/export/sieve.rs @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use serde_json::{Value, json}; + +use super::common::{create_batch, jid, target_get_all}; +use super::{Maps, Net, Plan, Uploader}; +use crate::db; +use crate::error::Error; +use crate::jmap::blobxfer; +use crate::jmap::request::Request; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{SIEVE_SELECT, row_to_sieve_script}; +use crate::sync::keys::blake3_bytes; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +pub fn reconcile( + ctx: &Context, + net: &Net, + _maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let ty = ObjectType::SieveScript; + let targets = target_get_all(net, ty).map_err(Error::from)?; + + let mut target_by_key: HashMap<[u8; 32], String> = HashMap::new(); + for t in &targets { + let (Some(id), Some(blob)) = (jid(t), t.get("blobId").and_then(Value::as_str)) else { + continue; + }; + let bytes = blobxfer::download_bytes( + &net.client, + &net.session, + &net.account, + blob, + "application/sieve", + "script", + ) + .map_err(Error::from)?; + target_by_key.insert(blake3_bytes(&bytes), id); + } + + let locals: Vec<(i64, Option, bool, i64)> = { + let mut stmt = ctx + .conn + .prepare(SIEVE_SELECT) + .map_err(|e| Error::Partial(e.to_string()))?; + stmt.query_map([], |row| { + let sr = row_to_sieve_script(row); + Ok((row.get::<_, i64>(0)?, sr)) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + .into_iter() + .map(|(id, sr)| { + let sr = sr.map_err(Error::from)?; + Ok((id, sr.name, sr.is_active, sr.blob_local_id)) + }) + .collect::>()? + }; + + let mut active_target: Option = None; + let mut deactivate = false; + let mut uploader = Uploader::new(net, &ctx.conn); + + for (local, name, is_active, blob_local) in &locals { + let bytes = db::blobs::blob_bytes(&ctx.conn, *blob_local) + .map_err(|e| Error::Partial(e.to_string()))? + .ok_or_else(|| Error::Partial("sieve blob missing".to_owned()))?; + let key = blake3_bytes(&bytes); + let target_id = if let Some(id) = target_by_key.get(&key) { + counts.skipped += 1; + id.clone() + } else { + let blob_id = uploader + .upload_with(*blob_local, "application/sieve") + .map_err(Error::from)?; + let mut obj = serde_json::Map::new(); + if let Some(n) = name { + obj.insert("name".to_owned(), Value::String(n.clone())); + } + obj.insert("blobId".to_owned(), Value::String(blob_id.0)); + let outcome = create_batch(net, ty, vec![(format!("c{local}"), Value::Object(obj))]) + .map_err(Error::from)?; + match outcome.created.first().and_then(|(_, v)| jid(v)) { + Some(id) => { + counts.created += 1; + target_by_key.insert(key, id.clone()); + id + } + None => { + for (cid, err) in &outcome.not_created { + logger.warn(&format!("SieveScript {cid} not created: {err}")); + } + counts.failed += 1; + continue; + } + } + }; + if *is_active { + active_target = Some(target_id); + } + } + + if active_target.is_none() && locals.iter().all(|(_, _, a, _)| !*a) { + deactivate = true; + } + + if !net.dry_run { + let mut req = Request::new(); + let args = if let Some(id) = &active_target { + json!({ "accountId": net.account, "onSuccessActivateScript": id }) + } else if deactivate { + json!({ "accountId": net.account, "onSuccessDeactivateScript": true }) + } else { + json!({ "accountId": net.account }) + }; + req.call("SieveScript/set", args, "a"); + if let Err(e) = req.send(&net.client, &net.api) { + logger.warn(&format!("SieveScript activation failed: {e}")); + } + } + + let local_keys: std::collections::HashSet<[u8; 32]> = locals + .iter() + .filter_map(|(_, _, _, b)| { + db::blobs::blob_bytes(&ctx.conn, *b) + .ok() + .flatten() + .map(|by| blake3_bytes(&by)) + }) + .collect(); + let mut prune_candidates: Vec = target_by_key + .iter() + .filter(|(k, _)| !local_keys.contains(*k)) + .map(|(_, id)| id.clone()) + .collect(); + prune_candidates.sort(); + + Ok(Plan { + prune_candidates, + active_sieve_target: active_target, + }) +} diff --git a/src/sync/export/tree.rs b/src/sync/export/tree.rs new file mode 100644 index 0000000..6b5390b --- /dev/null +++ b/src/sync/export/tree.rs @@ -0,0 +1,321 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use rusqlite::params; +use serde_json::Value; + +use super::common::{create_batch, jid, target_query_get}; +use super::{Net, Plan, Uploader}; +use crate::error::Error; +use crate::jmap::wire::JmapId; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{ + FILE_NODE_SELECT, MAILBOX_SELECT, TargetResolver, row_to_file_node, row_to_mailbox, +}; +use crate::sync::keys::fold_name; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +use super::Maps; + +struct LocalNode { + local: i64, + parent: Option, + name: String, + role: Option, +} + +struct TargetNode { + id: String, + parent: Option, + name: String, + role: Option, + may_delete: bool, +} + +fn load_local(ctx: &Context, ty: ObjectType) -> Result, Error> { + let table = crate::sync::table_name(ty); + let mut stmt = ctx + .conn + .prepare(&format!("SELECT id, parent_id, name, role FROM {table}")) + .map_err(|e| Error::Partial(e.to_string()))?; + let rows = stmt + .query_map([], |r| { + Ok(LocalNode { + local: r.get(0)?, + parent: r.get(1)?, + name: r.get(2)?, + role: r.get(3)?, + }) + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))?; + Ok(rows) +} + +fn load_target(net: &Net, ty: ObjectType) -> Result, Error> { + let props: &[&str] = &["role", "name", "parentId", "myRights"]; + let list = target_query_get(net, ty, Some(props)).map_err(Error::from)?; + Ok(list + .iter() + .filter_map(|v| { + Some(TargetNode { + id: jid(v)?, + parent: v.get("parentId").and_then(Value::as_str).map(str::to_owned), + name: v.get("name").and_then(Value::as_str)?.to_owned(), + role: v.get("role").and_then(Value::as_str).map(str::to_owned), + may_delete: v + .get("myRights") + .and_then(|r| r.get("mayDelete")) + .and_then(Value::as_bool) + .unwrap_or(true), + }) + }) + .collect()) +} + +fn depth(local: i64, by: &HashMap>) -> usize { + let mut d = 0; + let mut cur = by.get(&local).copied().flatten(); + let mut seen = HashSet::new(); + while let Some(p) = cur { + if !seen.insert(p) { + break; + } + d += 1; + cur = by.get(&p).copied().flatten(); + } + d +} + +pub fn reconcile( + ctx: &Context, + net: &Net, + ty: ObjectType, + maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let locals = load_local(ctx, ty)?; + let targets = load_target(net, ty)?; + + let mut matched: HashMap = HashMap::new(); + let mut tmatched: HashSet = HashSet::new(); + + if ty == ObjectType::Mailbox { + for t in &targets { + if let Some(r) = &t.role + && let Some(l) = locals.iter().find(|l| { + l.role.as_deref() == Some(r.as_str()) && !matched.contains_key(&l.local) + }) + { + matched.insert(l.local, t.id.clone()); + tmatched.insert(t.id.clone()); + } + } + } + + let mut stack: Vec<(Option, Option)> = vec![(None, None)]; + for (l, tid) in matched.clone() { + stack.push((Some(l), Some(tid))); + } + while let Some((lp, tp)) = stack.pop() { + let lchildren: Vec<&LocalNode> = locals + .iter() + .filter(|n| n.parent == lp && !matched.contains_key(&n.local)) + .collect(); + for ln in lchildren { + if let Some(tn) = targets.iter().find(|t| { + t.parent.as_deref() == tp.as_deref() + && !tmatched.contains(&t.id) + && fold_name(&t.name) == fold_name(&ln.name) + }) { + matched.insert(ln.local, tn.id.clone()); + tmatched.insert(tn.id.clone()); + stack.push((Some(ln.local), Some(tn.id.clone()))); + } + } + } + + for (l, tid) in &matched { + maps.insert(ty, *l, JmapId(tid.clone())); + } + counts.skipped += matched.len() as u64; + + let by: HashMap> = locals.iter().map(|n| (n.local, n.parent)).collect(); + let mut to_create: Vec<&LocalNode> = locals + .iter() + .filter(|n| !matched.contains_key(&n.local)) + .collect(); + to_create.sort_by_key(|n| depth(n.local, &by)); + let max_depth = to_create + .iter() + .map(|n| depth(n.local, &by)) + .max() + .unwrap_or(0); + + let mut uploader = Uploader::new(net, &ctx.conn); + let interleave = ty == ObjectType::FileNode; + for d in 0..=max_depth { + let level: Vec<&LocalNode> = to_create + .iter() + .copied() + .filter(|n| depth(n.local, &by) == d) + .collect(); + if interleave { + for n in &level { + if let Some(p) = n.parent + && maps.target(ty, p).is_none() + { + logger.warn(&format!( + "{} local {} skipped: parent {} not created", + ty.jmap_name(), + n.local, + p + )); + counts.failed += 1; + continue; + } + let obj = match build_create(ctx, ty, n.local, maps, &mut uploader) { + Ok(o) => o, + Err(e) => { + logger.warn(&format!( + "{} local {} skipped: {e}", + ty.jmap_name(), + n.local + )); + counts.failed += 1; + continue; + } + }; + let outcome = create_batch(net, ty, vec![(format!("c{}", n.local), obj)]) + .map_err(Error::from)?; + for (cid, v) in &outcome.created { + if let Some(local) = cid.strip_prefix('c').and_then(|s| s.parse::().ok()) + && let Some(id) = jid(v) + { + maps.insert(ty, local, JmapId(id)); + counts.created += 1; + } + } + for (cid, err) in &outcome.not_created { + logger.warn(&format!("{} {cid} not created: {err}", ty.jmap_name())); + counts.failed += 1; + } + } + continue; + } + let mut batch: Vec<(String, Value)> = Vec::new(); + for n in &level { + if let Some(p) = n.parent + && maps.target(ty, p).is_none() + { + logger.warn(&format!( + "{} local {} skipped: parent {} not created", + ty.jmap_name(), + n.local, + p + )); + counts.failed += 1; + continue; + } + match build_create(ctx, ty, n.local, maps, &mut uploader) { + Ok(obj) => { + batch.push((format!("c{}", n.local), obj)); + } + Err(e) => { + logger.warn(&format!( + "{} local {} skipped: {e}", + ty.jmap_name(), + n.local + )); + counts.failed += 1; + } + } + } + if batch.is_empty() { + continue; + } + let outcome = create_batch(net, ty, batch).map_err(Error::from)?; + for (cid, v) in &outcome.created { + if let Some(local) = cid.strip_prefix('c').and_then(|s| s.parse::().ok()) + && let Some(id) = jid(v) + { + maps.insert(ty, local, JmapId(id)); + counts.created += 1; + } + } + for (cid, err) in &outcome.not_created { + logger.warn(&format!("{} {cid} not created: {err}", ty.jmap_name())); + counts.failed += 1; + } + } + + let objs: Vec = targets + .iter() + .map(|t| crate::sync::prune::TargetObj { + id: t.id.clone(), + matched: tmatched.contains(&t.id), + protected: t.role.is_some(), + may_delete: t.may_delete, + parent: t.parent.clone(), + }) + .collect(); + Ok(Plan { + prune_candidates: crate::sync::prune::candidates(&objs, true), + active_sieve_target: None, + }) +} + +fn build_create( + ctx: &Context, + ty: ObjectType, + local: i64, + maps: &Maps, + uploader: &mut Uploader<'_>, +) -> Result { + if ty == ObjectType::Mailbox { + let wire = ctx + .conn + .query_row( + &format!("{MAILBOX_SELECT} WHERE id = ?1"), + params![local], + |row| Ok(row_to_mailbox(row, maps)), + ) + .map_err(|e| Error::Partial(e.to_string()))? + .map_err(Error::from)?; + return serde_json::to_value(&wire).map_err(|e| Error::Partial(e.to_string())); + } + let fnrow = ctx + .conn + .query_row( + &format!("{FILE_NODE_SELECT} WHERE id = ?1"), + params![local], + |row| Ok(row_to_file_node(row, maps)), + ) + .map_err(|e| Error::Partial(e.to_string()))? + .map_err(Error::from)?; + let mut wire = fnrow.wire; + if let (crate::jmap::wire::file_node::NodeType::File, Some(blob_local)) = + (&wire.node_type, fnrow.blob_local_id) + { + let ct = wire + .media_type + .clone() + .unwrap_or_else(|| "application/octet-stream".to_owned()); + let id = uploader.upload_with(blob_local, &ct).map_err(Error::from)?; + wire.blob_id = Some(id); + } + let mut obj = serde_json::to_value(&wire).map_err(|e| Error::Partial(e.to_string()))?; + if let Value::Object(m) = &mut obj { + m.remove("created"); + m.remove("modified"); + m.remove("isSubscribed"); + } + Ok(obj) +} diff --git a/src/sync/export/uidtype.rs b/src/sync/export/uidtype.rs new file mode 100644 index 0000000..61734c2 --- /dev/null +++ b/src/sync/export/uidtype.rs @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; + +use serde_json::Value; + +use super::common::{create_batch, jid, target_query_get}; +use super::{Maps, Net, Plan, Uploader}; +use crate::error::Error; +use crate::logging::Logger; +use crate::sync::import_jmap::mapping::{ + CALENDAR_EVENT_SELECT, CONTACT_CARD_SELECT, calendar_event_to_wire, contact_card_to_wire, +}; +use crate::sync::prune::{TargetObj, candidates}; +use crate::sync::{Context, TypeCounts}; +use crate::types::ObjectType; + +fn target_uid(v: &Value) -> Option { + v.get("uid").and_then(Value::as_str).map(str::to_owned) +} + +pub fn reconcile( + ctx: &Context, + net: &Net, + ty: ObjectType, + maps: &mut Maps, + counts: &mut TypeCounts, + logger: &Logger, +) -> Result { + let targets = target_query_get(net, ty, None).map_err(Error::from)?; + let mut by_uid: std::collections::HashMap = std::collections::HashMap::new(); + for t in &targets { + if let (Some(uid), Some(id)) = (target_uid(t), jid(t)) { + by_uid.entry(uid).or_insert(id); + } + } + + let select = if ty == ObjectType::ContactCard { + CONTACT_CARD_SELECT + } else { + CALENDAR_EVENT_SELECT + }; + let rows: Vec<(i64, String)> = { + let mut stmt = ctx + .conn + .prepare(select) + .map_err(|e| Error::Partial(e.to_string()))?; + stmt.query_map([], |r| { + if ty == ObjectType::ContactCard { + Ok((r.get::<_, i64>(0)?, r.get::<_, String>(1)?)) + } else { + let data: String = r.get(4)?; + let uid = serde_json::from_str::(&data) + .ok() + .and_then(|v| v.get("uid").and_then(Value::as_str).map(str::to_owned)) + .unwrap_or_default(); + Ok((r.get::<_, i64>(0)?, uid)) + } + }) + .and_then(|m| m.collect::, _>>()) + .map_err(|e| Error::Partial(e.to_string()))? + }; + + let mut matched_uids: HashSet = HashSet::new(); + let mut uploader = Uploader::new(net, &ctx.conn); + for (local, uid) in &rows { + if let Some(tid) = by_uid.get(uid) { + maps.insert(ty, *local, crate::jmap::wire::JmapId(tid.clone())); + matched_uids.insert(uid.clone()); + counts.skipped += 1; + continue; + } + let wire = match build_wire(ctx, ty, *local, maps, &mut uploader) { + Ok(w) => w, + Err(e) => { + logger.warn(&format!("{} local {local} skipped: {e}", ty.jmap_name())); + counts.failed += 1; + continue; + } + }; + let outcome = + create_batch(net, ty, vec![(format!("c{local}"), wire)]).map_err(Error::from)?; + for (cid, v) in &outcome.created { + if let Some(parsed) = cid.strip_prefix('c').and_then(|s| s.parse::().ok()) + && let Some(id) = jid(v) + { + maps.insert(ty, parsed, crate::jmap::wire::JmapId(id)); + counts.created += 1; + } + } + for (cid, err) in &outcome.not_created { + logger.warn(&format!("{} {cid} not created: {err}", ty.jmap_name())); + counts.failed += 1; + } + } + + let objs: Vec = targets + .iter() + .filter_map(|t| { + let id = jid(t)?; + let uid = target_uid(t); + Some(TargetObj { + id: id.clone(), + matched: uid.map(|u| matched_uids.contains(&u)).unwrap_or(false), + protected: false, + may_delete: true, + parent: None, + }) + }) + .collect(); + Ok(Plan { + prune_candidates: candidates(&objs, false), + active_sieve_target: None, + }) +} + +fn build_wire( + ctx: &Context, + ty: ObjectType, + local: i64, + maps: &Maps, + up: &mut Uploader<'_>, +) -> Result { + if ty == ObjectType::ContactCard { + let (uid, abids, data): (String, String, String) = ctx + .conn + .query_row( + &format!("{CONTACT_CARD_SELECT} WHERE id = ?1"), + rusqlite::params![local], + |r| Ok((r.get(1)?, r.get(2)?, r.get(3)?)), + ) + .map_err(|e| Error::Partial(e.to_string()))?; + contact_card_to_wire(&uid, &abids, &data, maps, up).map_err(Error::from) + } else { + let (cal, dr, ud, data): (String, i64, i64, String) = ctx + .conn + .query_row( + &format!("{CALENDAR_EVENT_SELECT} AND id = ?1"), + rusqlite::params![local], + |r| Ok((r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)), + ) + .map_err(|e| Error::Partial(e.to_string()))?; + calendar_event_to_wire(&cal, dr != 0, ud != 0, &data, maps, up).map_err(Error::from) + } +} diff --git a/src/sync/import_dav.rs b/src/sync/import_dav.rs new file mode 100644 index 0000000..2b434fb --- /dev/null +++ b/src/sync/import_dav.rs @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod calcard; +pub mod collections; +pub mod coordinator; +pub mod items; +pub mod tree; + +pub use coordinator::{DavAuth, DavImportConfig, DavKindArg, run}; diff --git a/src/sync/import_dav/calcard.rs b/src/sync/import_dav/calcard.rs new file mode 100644 index 0000000..fbf9c3e --- /dev/null +++ b/src/sync/import_dav/calcard.rs @@ -0,0 +1,281 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use calcard::icalendar::ICalendar; +use calcard::vcard::VCard; +use serde_json::{Map, Value}; + +#[derive(Debug, thiserror::Error)] +pub enum CalcardError { + #[error("vcard parse failed")] + VCardParse, + #[error("icalendar parse failed")] + ICalParse, + #[error("jscontact serialisation: {0}")] + Json(#[from] serde_json::Error), + #[error("no parseable entries in iCalendar")] + NoEntries, + #[error("calcard output is not a JSON object")] + NotAnObject, +} + +#[derive(Debug)] +pub struct JsContact { + pub uid: String, + pub data: Value, +} + +pub fn vcard_to_jscontact(text: &str, item_href: &str) -> Result { + let vcard = VCard::parse(text).map_err(|_| CalcardError::VCardParse)?; + let js = vcard.into_jscontact::(); + let value: Value = serde_json::from_str(&js.to_string_pretty())?; + let uid = value + .get("uid") + .and_then(Value::as_str) + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_owned) + .unwrap_or_else(|| synthesise_uid(item_href)); + let mut obj = match value { + Value::Object(map) => map, + _ => return Err(CalcardError::NotAnObject), + }; + obj.remove("uid"); + Ok(JsContact { + uid, + data: Value::Object(obj), + }) +} + +pub fn synthesise_uid(item_href: &str) -> String { + let hash = blake3::hash(item_href.as_bytes()); + format!("vandelay-syn-{}", hash.to_hex()) +} + +#[derive(Debug)] +pub struct JsCalendarEntry { + pub data: Value, + pub data_type: EntryType, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EntryType { + Event, + Task, + Note, + Group, +} + +impl EntryType { + pub fn as_column(self) -> &'static str { + match self { + EntryType::Event => "Event", + EntryType::Task => "Task", + EntryType::Note => "Note", + EntryType::Group => "Group", + } + } +} + +pub fn ical_to_jscalendar_entries(text: &str) -> Result, CalcardError> { + let ical = ICalendar::parse(text).map_err(|_| CalcardError::ICalParse)?; + let js = ical.into_jscalendar::(); + let value: Value = serde_json::from_str(&js.to_string_pretty())?; + let mut out = Vec::new(); + collect_entries(value, &mut out); + if out.is_empty() { + return Err(CalcardError::NoEntries); + } + Ok(out) +} + +fn collect_entries(value: Value, out: &mut Vec) { + match value { + Value::Object(map) => { + let at_type = map.get("@type").and_then(Value::as_str).map(str::to_owned); + match at_type.as_deref() { + Some("Event") if has_nonempty_uid(&map) => out.push(JsCalendarEntry { + data: Value::Object(map), + data_type: EntryType::Event, + }), + Some("Task") if has_nonempty_uid(&map) => out.push(JsCalendarEntry { + data: Value::Object(map), + data_type: EntryType::Task, + }), + Some("Note") if has_nonempty_uid(&map) => out.push(JsCalendarEntry { + data: Value::Object(map), + data_type: EntryType::Note, + }), + Some("Group") => { + if let Some(entries) = map.get("entries").and_then(Value::as_array) { + for entry in entries { + collect_entries(entry.clone(), out); + } + } + } + _ => {} + } + } + Value::Array(items) => { + for item in items { + collect_entries(item, out); + } + } + _ => {} + } +} + +fn has_nonempty_uid(map: &Map) -> bool { + map.get("uid") + .and_then(Value::as_str) + .map(|u| !u.trim().is_empty()) + .unwrap_or(false) +} + +pub fn strip_extracted_fields_from_event(value: &mut Value) -> (bool, bool, Option) { + let Value::Object(map) = value else { + return (false, false, None); + }; + let is_draft = map + .remove("isDraft") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let use_default_alerts = map + .remove("useDefaultAlerts") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + map.remove("calendarIds"); + map.remove("method"); + map.remove("utcStart"); + map.remove("utcEnd"); + map.remove("isOrigin"); + map.remove("baseEventId"); + let uid = map.get("uid").and_then(Value::as_str).map(str::to_owned); + (is_draft, use_default_alerts, uid) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_VCARD: &str = "BEGIN:VCARD\r\n\ +VERSION:4.0\r\n\ +UID:abc-123\r\n\ +FN:Alice Smith\r\n\ +EMAIL:alice@example.com\r\n\ +END:VCARD\r\n"; + + const SAMPLE_ICAL: &str = "BEGIN:VCALENDAR\r\n\ +VERSION:2.0\r\n\ +PRODID:-//Test//EN\r\n\ +BEGIN:VEVENT\r\n\ +UID:event-1@example.com\r\n\ +DTSTAMP:20260101T000000Z\r\n\ +DTSTART:20260101T090000Z\r\n\ +DTEND:20260101T100000Z\r\n\ +SUMMARY:Test Event\r\n\ +END:VEVENT\r\n\ +END:VCALENDAR\r\n"; + + #[test] + fn vcard_extracts_uid_into_separate_field() { + let jc = vcard_to_jscontact(SAMPLE_VCARD, "/dav/card/u/d/a.vcf").expect("parse"); + assert_eq!(jc.uid, "abc-123"); + let obj = jc.data.as_object().unwrap(); + assert!(!obj.contains_key("uid"), "uid stripped from data"); + } + + #[test] + fn ical_extracts_at_least_one_event() { + let events = ical_to_jscalendar_entries(SAMPLE_ICAL).expect("parse"); + assert!(!events.is_empty()); + assert_eq!(events[0].data_type, EntryType::Event); + let uid = events[0] + .data + .as_object() + .and_then(|m| m.get("uid")) + .and_then(Value::as_str) + .unwrap(); + assert!(uid.starts_with("event-1")); + } + + #[test] + fn vcard_without_uid_gets_synthetic_uid_from_href() { + let no_uid = "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Anon\r\nEND:VCARD\r\n"; + let href = "/dav/card/u/d/no-uid.vcf"; + let jc = vcard_to_jscontact(no_uid, href).expect("synthetic uid"); + assert_eq!(jc.uid, synthesise_uid(href)); + assert!(jc.uid.starts_with("vandelay-syn-")); + } + + #[test] + fn synthetic_uid_is_stable_per_href() { + assert_eq!(synthesise_uid("/a"), synthesise_uid("/a")); + assert_ne!(synthesise_uid("/a"), synthesise_uid("/b")); + } + + #[test] + fn ical_with_empty_calendar_fails_no_entries() { + let empty = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nEND:VCALENDAR\r\n"; + let err = ical_to_jscalendar_entries(empty).unwrap_err(); + assert!(matches!(err, CalcardError::NoEntries)); + } + + #[test] + fn vcard_garbage_fails_parse() { + let err = vcard_to_jscontact("not a vcard", "/x").unwrap_err(); + assert!(matches!(err, CalcardError::VCardParse)); + } + + #[test] + fn ical_vtodo_classified_as_task() { + let vtodo = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n\ + BEGIN:VTODO\r\nUID:t1@example\r\nDTSTAMP:20260101T000000Z\r\n\ + SUMMARY:Buy milk\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"; + let entries = ical_to_jscalendar_entries(vtodo).expect("parse vtodo"); + assert_eq!(entries[0].data_type, EntryType::Task); + } + + #[test] + fn ical_recurrence_overrides_collapse_to_single_event() { + let ical = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n\ + BEGIN:VEVENT\r\nUID:r1@example\r\nDTSTAMP:20260101T000000Z\r\n\ + DTSTART:20260101T090000Z\r\nDTEND:20260101T100000Z\r\n\ + RRULE:FREQ=DAILY;COUNT=3\r\nSUMMARY:Standup\r\nEND:VEVENT\r\n\ + BEGIN:VEVENT\r\nUID:r1@example\r\nDTSTAMP:20260101T000000Z\r\n\ + RECURRENCE-ID:20260102T090000Z\r\nDTSTART:20260102T093000Z\r\nDTEND:20260102T103000Z\r\n\ + SUMMARY:Standup (moved)\r\nEND:VEVENT\r\n\ + END:VCALENDAR\r\n"; + let entries = ical_to_jscalendar_entries(ical).expect("parse"); + assert_eq!(entries.len(), 1, "calcard merges overrides into master"); + let master = entries[0].data.as_object().unwrap(); + assert!(master.contains_key("recurrenceOverrides")); + } + + #[test] + fn strip_extracted_fields_pulls_isdraft_use_default_alerts() { + let mut v = serde_json::json!({ + "@type": "Event", + "uid": "x", + "title": "T", + "isDraft": true, + "useDefaultAlerts": true, + "calendarIds": {"a": true}, + "utcStart": "2026-01-01T09:00:00Z", + "method": "PUBLISH" + }); + let (d, u, uid) = strip_extracted_fields_from_event(&mut v); + assert!(d); + assert!(u); + assert_eq!(uid.as_deref(), Some("x")); + let m = v.as_object().unwrap(); + assert!(!m.contains_key("calendarIds")); + assert!(!m.contains_key("isDraft")); + assert!(!m.contains_key("useDefaultAlerts")); + assert!(!m.contains_key("method")); + assert!(!m.contains_key("utcStart")); + } +} diff --git a/src/sync/import_dav/collections.rs b/src/sync/import_dav/collections.rs new file mode 100644 index 0000000..4261308 --- /dev/null +++ b/src/sync/import_dav/collections.rs @@ -0,0 +1,416 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, params}; + +use crate::dav::discover::DiscoveredCollection; +use crate::dav::href::{Href, last_path_component}; +use crate::db::dav_ids; +use crate::error::Error; +use crate::logging::{LEVEL_PROGRESS, Logger}; +use crate::sync::TypeCounts; + +pub fn reconcile_calendars( + conn: &mut Connection, + source_id: i64, + discovered: &[DiscoveredCollection], + counts: &mut TypeCounts, + logger: Logger, +) -> Result, Error> { + reconcile_calendar_like( + conn, + source_id, + discovered, + &RowOps { + type_name: dav_ids::CALENDAR, + upsert: upsert_calendar_row, + delete: delete_calendar_row, + }, + counts, + logger, + ) +} + +pub fn reconcile_address_books( + conn: &mut Connection, + source_id: i64, + discovered: &[DiscoveredCollection], + counts: &mut TypeCounts, + logger: Logger, +) -> Result, Error> { + reconcile_calendar_like( + conn, + source_id, + discovered, + &RowOps { + type_name: dav_ids::ADDRESS_BOOK, + upsert: upsert_address_book_row, + delete: delete_address_book_row, + }, + counts, + logger, + ) +} + +type RowUpsert = + fn(&Connection, &DiscoveredCollection, Option) -> Result; +type RowDelete = fn(&Connection, i64) -> Result<(), rusqlite::Error>; + +struct RowOps { + type_name: &'static str, + upsert: RowUpsert, + delete: RowDelete, +} + +fn reconcile_calendar_like( + conn: &mut Connection, + source_id: i64, + discovered: &[DiscoveredCollection], + ops: &RowOps, + counts: &mut TypeCounts, + logger: Logger, +) -> Result, Error> { + let type_name = ops.type_name; + let upsert_row = ops.upsert; + let delete_row = ops.delete; + let existing: HashMap = dav_ids::collections_of_type(conn, source_id, type_name) + .map_err(|e| Error::Partial(e.to_string()))?; + + let mut upserted: Vec<(String, i64)> = Vec::new(); + let server_hrefs: std::collections::HashSet = discovered + .iter() + .map(|c| c.href.as_str().to_owned()) + .collect(); + + for coll in discovered { + let collection_href = coll.href.as_str().to_owned(); + let existing_local = existing.get(&collection_href).copied(); + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + match upsert_row(&tx, coll, existing_local) { + Ok(local_id) => { + if existing_local.is_none() { + dav_ids::insert( + &tx, + source_id, + type_name, + &collection_href, + &collection_href, + "", + local_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.created += 1; + } else { + counts.fetched += 1; + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + upserted.push((collection_href.clone(), local_id)); + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("collection upserted: {collection_href} -> {local_id}"); + } + } + Err(e) => { + let _ = tx.rollback(); + logger.warn(&format!( + "collection upsert {collection_href:?} failed: {e}" + )); + counts.failed += 1; + } + } + } + + for (collection_href, local_id) in &existing { + if server_hrefs.contains(collection_href) { + continue; + } + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let item_type = item_type_for(type_name); + if let Some(item_type) = item_type { + let _ = dav_ids::delete_collection(&tx, source_id, item_type, collection_href); + } + let _ = dav_ids::delete_collection(&tx, source_id, type_name, collection_href); + match delete_row(&tx, *local_id) { + Ok(()) => { + counts.deleted += 1; + } + Err(e) => { + logger.warn(&format!( + "vanished collection {collection_href:?} delete failed: {e}" + )); + counts.failed += 1; + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + } + + Ok(upserted) +} + +fn item_type_for(collection_type: &str) -> Option<&'static str> { + match collection_type { + dav_ids::CALENDAR => Some(dav_ids::CALENDAR_EVENT), + dav_ids::ADDRESS_BOOK => Some(dav_ids::CONTACT_CARD), + _ => None, + } +} + +fn upsert_calendar_row( + conn: &Connection, + coll: &DiscoveredCollection, + existing_local: Option, +) -> Result { + let name = display_or_fallback(&coll.props.displayname, &coll.href); + let description = coll.props.calendar_description.as_deref(); + let color = coll.props.calendar_color.as_deref(); + let sort_order = coll.props.calendar_order.unwrap_or(0).max(0); + let time_zone = parse_tzid_from_vtimezone(coll.props.calendar_timezone.as_deref()); + + if let Some(local) = existing_local { + conn.execute( + "UPDATE calendars SET name = ?1, description = ?2, color = ?3, sort_order = ?4, + time_zone = ?5 + WHERE id = ?6", + params![name, description, color, sort_order, time_zone, local], + )?; + Ok(local) + } else { + conn.execute( + "INSERT INTO calendars (name, description, color, sort_order, is_subscribed, + is_visible, is_default, include_in_availability, + default_alerts_with_time, default_alerts_without_time, time_zone) + VALUES (?1, ?2, ?3, ?4, 1, 1, 0, 'all', NULL, NULL, ?5)", + params![name, description, color, sort_order, time_zone], + )?; + Ok(conn.last_insert_rowid()) + } +} + +fn delete_calendar_row(conn: &Connection, local_id: i64) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM calendar_events + WHERE EXISTS ( + SELECT 1 FROM json_each(calendar_ids) WHERE value = ?1 + )", + params![local_id], + )?; + conn.execute("DELETE FROM calendars WHERE id = ?1", params![local_id])?; + Ok(()) +} + +fn upsert_address_book_row( + conn: &Connection, + coll: &DiscoveredCollection, + existing_local: Option, +) -> Result { + let name = display_or_fallback(&coll.props.displayname, &coll.href); + let description = coll.props.addressbook_description.as_deref(); + + if let Some(local) = existing_local { + conn.execute( + "UPDATE address_books SET name = ?1, description = ?2 WHERE id = ?3", + params![name, description, local], + )?; + Ok(local) + } else { + conn.execute( + "INSERT INTO address_books (name, description, sort_order, is_default, is_subscribed) + VALUES (?1, ?2, 0, 0, 1)", + params![name, description], + )?; + Ok(conn.last_insert_rowid()) + } +} + +fn delete_address_book_row(conn: &Connection, local_id: i64) -> Result<(), rusqlite::Error> { + conn.execute( + "DELETE FROM contact_cards + WHERE EXISTS ( + SELECT 1 FROM json_each(address_book_ids) WHERE value = ?1 + )", + params![local_id], + )?; + conn.execute("DELETE FROM address_books WHERE id = ?1", params![local_id])?; + Ok(()) +} + +fn display_or_fallback(name: &Option, href: &Href) -> String { + if let Some(n) = name + && !n.trim().is_empty() + { + return n.clone(); + } + let comp = last_path_component(href); + if comp.is_empty() { + "Untitled".to_owned() + } else { + comp + } +} + +fn parse_tzid_from_vtimezone(vt: Option<&str>) -> Option { + let body = vt?; + for line in body.lines() { + let line = line.trim(); + if let Some(rest) = line.strip_prefix("TZID:") { + let v = rest.trim(); + if !v.is_empty() { + return Some(v.to_owned()); + } + } + } + None +} + +pub fn upsert_root_directory( + conn: &Connection, + source_id: i64, + root: &DiscoveredCollection, +) -> Result { + let collection_href = root.href.as_str().to_owned(); + let name = display_or_fallback(&root.props.displayname, &root.href); + if let Some(local) = + dav_ids::local_for_item(conn, source_id, dav_ids::FILE_NODE, &collection_href) + .map_err(|e| Error::Partial(e.to_string()))? + { + conn.execute( + "UPDATE file_nodes SET name = ?1 WHERE id = ?2", + params![name, local], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + return Ok(local); + } + let now = time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .map_err(|e| Error::Partial(format!("clock: {e}")))?; + conn.execute( + "INSERT INTO file_nodes (parent_id, node_type, blob_id, target, name, media_type, + created, modified, is_subscribed, role) + VALUES (NULL, 'directory', NULL, NULL, ?1, NULL, ?2, NULL, 1, NULL)", + params![name, now], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let local_id = conn.last_insert_rowid(); + dav_ids::insert( + conn, + source_id, + dav_ids::FILE_NODE, + &collection_href, + &collection_href, + "", + local_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + Ok(local_id) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_tzid_from_vtimezone_extracts_first() { + let v = "BEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nEND:VTIMEZONE\r\n"; + assert_eq!( + parse_tzid_from_vtimezone(Some(v)).as_deref(), + Some("Europe/Berlin") + ); + } + + #[test] + fn parse_tzid_returns_none_when_absent() { + assert!(parse_tzid_from_vtimezone(Some("nothing here")).is_none()); + assert!(parse_tzid_from_vtimezone(None).is_none()); + } + + #[test] + fn display_or_fallback_uses_href_when_displayname_empty() { + let href = Href::from_normalised("/dav/cal/u/work/".to_owned()); + let name: Option = None; + assert_eq!(display_or_fallback(&name, &href), "work"); + } + + #[test] + fn display_or_fallback_uses_displayname_when_present() { + let href = Href::from_normalised("/dav/cal/u/default/".to_owned()); + let name = Some("Default".to_owned()); + assert_eq!(display_or_fallback(&name, &href), "Default"); + } + + #[test] + fn delete_calendar_row_cascades_events_and_sync_id_rows() { + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + let conn = rusqlite::Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let sid = upsert_source( + &conn, + &SourceKey { + kind: "caldav".to_owned(), + session_url: "https://x".to_owned(), + account_id: "u".to_owned(), + }, + Some("u"), + "u", + ) + .unwrap(); + conn.execute( + "INSERT INTO calendars (name, sort_order, is_subscribed, is_visible, is_default, + include_in_availability) + VALUES ('Work', 0, 1, 1, 0, 'all')", + [], + ) + .unwrap(); + let cal_local = conn.last_insert_rowid(); + conn.execute( + "INSERT INTO calendar_events (calendar_ids, data, data_type) + VALUES (?1, '{}', 'Event')", + rusqlite::params![format!("[{cal_local}]")], + ) + .unwrap(); + let evt_local = conn.last_insert_rowid(); + dav_ids::insert( + &conn, + sid, + dav_ids::CALENDAR, + "/cal/work/", + "/cal/work/", + "", + cal_local, + ) + .unwrap(); + dav_ids::insert( + &conn, + sid, + dav_ids::CALENDAR_EVENT, + "/cal/work/", + "/cal/work/e.ics", + "\"v\"", + evt_local, + ) + .unwrap(); + let _ = dav_ids::delete_collection(&conn, sid, dav_ids::CALENDAR_EVENT, "/cal/work/"); + let _ = dav_ids::delete_collection(&conn, sid, dav_ids::CALENDAR, "/cal/work/"); + delete_calendar_row(&conn, cal_local).unwrap(); + let cal_count: i64 = conn + .query_row("SELECT count(*) FROM calendars", [], |r| r.get(0)) + .unwrap(); + let evt_count: i64 = conn + .query_row("SELECT count(*) FROM calendar_events", [], |r| r.get(0)) + .unwrap(); + let dav_count: i64 = conn + .query_row("SELECT count(*) FROM sync_id_dav", [], |r| r.get(0)) + .unwrap(); + assert_eq!(cal_count, 0, "calendar row gone"); + assert_eq!(evt_count, 0, "event rows cascaded"); + assert_eq!(dav_count, 0, "sync_id_dav rows for both types cleared"); + } +} diff --git a/src/sync/import_dav/coordinator.rs b/src/sync/import_dav/coordinator.rs new file mode 100644 index 0000000..bd37f20 --- /dev/null +++ b/src/sync/import_dav/coordinator.rs @@ -0,0 +1,520 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::Connection; + +use crate::dav::client::DavClient; +use crate::dav::discover::{DavKind, Discovery, DiscoveryError, discover}; +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::jmap::http::{Auth, RetryPolicy}; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::collections; +use super::items; +use super::tree; + +#[derive(Debug, Clone, Copy)] +pub enum DavKindArg { + Caldav, + Carddav, + Webdav, +} + +impl DavKindArg { + pub fn kind(self) -> DavKind { + match self { + DavKindArg::Caldav => DavKind::Caldav, + DavKindArg::Carddav => DavKind::Carddav, + DavKindArg::Webdav => DavKind::Webdav, + } + } + + pub fn source_kind(self) -> &'static str { + match self { + DavKindArg::Caldav => "caldav", + DavKindArg::Carddav => "carddav", + DavKindArg::Webdav => "webdav", + } + } +} + +#[derive(Debug, Clone)] +pub enum DavAuth { + Basic { user: String, password: String }, + Bearer { token: String }, +} + +impl DavAuth { + pub fn to_jmap_auth(&self) -> Auth { + match self { + DavAuth::Basic { user, password } => Auth::Basic { + user: user.clone(), + password: password.clone(), + }, + DavAuth::Bearer { token } => Auth::Bearer { + token: token.clone(), + }, + } + } + + pub fn username(&self) -> String { + match self { + DavAuth::Basic { user, .. } => user.clone(), + DavAuth::Bearer { .. } => "(bearer)".to_owned(), + } + } +} + +#[derive(Debug)] +pub struct DavImportConfig { + pub kind: DavKindArg, + pub url: String, + pub auth: DavAuth, + pub allow_cleartext: bool, + pub dav_connections: usize, + pub multiget_batch: usize, + pub allow_source_change: bool, +} + +pub fn run(common: CommonConfig, config: DavImportConfig) -> Result { + let logger = common.logger; + enforce_tls_policy(&config.url, config.allow_cleartext)?; + + let mut conn = db::init::open(&common.archive)?; + + let client = DavClient::new( + config.auth.to_jmap_auth(), + RetryPolicy::new(common.max_retries), + common.allow_invalid_certs, + ); + client.set_logger(logger); + + let discovery = + discover(&client, config.kind.kind(), &config.url).map_err(map_discovery_error)?; + + let session_url = normalise_base_url(&config.url); + let account_id_raw = discovery + .principal_url + .clone() + .unwrap_or_else(|| discovery.home_set_url.clone()); + let account_id = normalise_account_url(&account_id_raw); + + let key = SourceKey { + kind: config.kind.source_kind().to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + + if !common.dry_run + && let Some((url, acc)) = db::sources::conflicting_source( + &conn, + config.kind.source_kind(), + &session_url, + &account_id, + ) + .map_err(|e| Error::Partial(e.to_string()))? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records {} source ({url}, account {acc}); \ + pass --allow-source-change to import a different account", + config.kind.source_kind() + ))); + } + + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "DAV discovery: principal={:?} home_set={} collections={}", + discovery.principal_url, + discovery.home_set_url, + discovery.collections.len() + ); + } + + if common.dry_run { + let mut summary = run_dry_diff(&conn, &client, &discovery, &config, logger)?; + summary.retries_observed = client.retries_observed(); + summary.retry_after_sleeps = client.retry_after_sleeps(); + return Ok(summary); + } + + let username = config.auth.username(); + let source_id = db::sources::upsert_source(&conn, &key, Some(&account_id), &username) + .map_err(|e| Error::Partial(e.to_string()))?; + + let summary = match config.kind { + DavKindArg::Caldav => { + run_caldav(&mut conn, &client, source_id, &discovery, &config, logger)? + } + DavKindArg::Carddav => { + run_carddav(&mut conn, &client, source_id, &discovery, &config, logger)? + } + DavKindArg::Webdav => { + run_webdav(&mut conn, &client, source_id, &discovery, &config, logger)? + } + }; + + if !summary.any_failed() + && let Err(e) = run_gc(&conn) + { + logger.warn(&format!("blob GC skipped: {e}")); + } + + let mut summary = summary; + summary.retries_observed = client.retries_observed(); + summary.retry_after_sleeps = client.retry_after_sleeps(); + Ok(summary) +} + +type ReconcileCollections = fn( + &mut Connection, + i64, + &[crate::dav::discover::DiscoveredCollection], + &mut TypeCounts, + Logger, +) -> Result, Error>; + +type ReconcileItems = + fn(&mut Connection, &items::ItemRunCtx<'_>, &str, i64, &mut TypeCounts) -> Result<(), Error>; + +struct ItemPhase { + container_label: &'static str, + item_label: &'static str, + reconcile_collections: ReconcileCollections, + reconcile_items: ReconcileItems, +} + +fn run_collection_phase( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + discovery: &Discovery, + config: &DavImportConfig, + logger: Logger, + phase: ItemPhase, +) -> Result { + let mut summary = Summary::default(); + let mut container_counts = TypeCounts::default(); + let mut item_counts = TypeCounts::default(); + + let upserted = (phase.reconcile_collections)( + conn, + source_id, + &discovery.collections, + &mut container_counts, + logger, + )?; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "import: {} done (upserted={} deleted={} failed={})", + phase.container_label, + container_counts.created + container_counts.fetched, + container_counts.deleted, + container_counts.failed + ); + } + + let ctx = items::ItemRunCtx { + client, + source_id, + base_url: &discovery.home_set_url, + multiget_batch: config.multiget_batch, + dav_connections: config.dav_connections, + logger, + }; + for (collection_href, local_id) in &upserted { + match (phase.reconcile_items)(conn, &ctx, collection_href, *local_id, &mut item_counts) { + Ok(()) => {} + Err(e) => { + logger.warn(&format!( + "{} {collection_href:?}: items failed: {e}", + phase.container_label + )); + item_counts.failed += 1; + } + } + } + + summary + .per_type + .push((phase.container_label, container_counts)); + summary.per_type.push((phase.item_label, item_counts)); + Ok(summary) +} + +fn run_dry_diff( + conn: &rusqlite::Connection, + client: &DavClient, + discovery: &Discovery, + config: &DavImportConfig, + logger: Logger, +) -> Result { + use crate::dav::href::join_absolute; + use crate::dav::xml; + use crate::db::dav_ids; + let mut summary = Summary::default(); + let (container_label, item_label, container_type, item_type) = match config.kind { + DavKindArg::Caldav => ( + "calendar", + "calendarevent", + dav_ids::CALENDAR, + dav_ids::CALENDAR_EVENT, + ), + DavKindArg::Carddav => ( + "addressbook", + "contactcard", + dav_ids::ADDRESS_BOOK, + dav_ids::CONTACT_CARD, + ), + DavKindArg::Webdav => ( + "filenode", + "filenode", + dav_ids::FILE_NODE, + dav_ids::FILE_NODE, + ), + }; + + let mut container_counts = TypeCounts::default(); + let mut item_counts = TypeCounts::default(); + container_counts.created = discovery.collections.len() as u64; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "dry-run: {} new={} (no archive to diff against)", + container_label, + discovery.collections.len(), + ); + } + + if !matches!(config.kind, DavKindArg::Webdav) { + for coll in &discovery.collections { + let url = join_absolute(&discovery.home_set_url, coll.href.as_str()) + .map_err(|e| Error::Partial(e.to_string()))?; + let ms = client + .propfind_responses(&url, 1, &xml::propfind_dav_items(), &url) + .map_err(Error::from)?; + let new_count = ms + .responses + .iter() + .filter(|r| !r.props.is_collection) + .count(); + item_counts.created += new_count as u64; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!(" {} items: {new_count}", coll.href.as_str()); + } + } + } else if let Some(root) = discovery.collections.first() { + let url = join_absolute(&discovery.home_set_url, root.href.as_str()) + .map_err(|e| Error::Partial(e.to_string()))?; + let ms = client + .propfind_responses(&url, 1, &xml::propfind_webdav_listing(), &url) + .map_err(Error::from)?; + let new_count = ms + .responses + .iter() + .filter(|r| !r.props.is_collection) + .count(); + item_counts.created += new_count as u64; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!(" root {} files: {new_count}", root.href.as_str()); + } + } + + let _ = container_type; + let _ = item_type; + let _ = conn; + summary.per_type.push((container_label, container_counts)); + if !matches!(config.kind, DavKindArg::Webdav) { + summary.per_type.push((item_label, item_counts)); + } + Ok(summary) +} + +fn run_caldav( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + discovery: &Discovery, + config: &DavImportConfig, + logger: Logger, +) -> Result { + run_collection_phase( + conn, + client, + source_id, + discovery, + config, + logger, + ItemPhase { + container_label: "calendar", + item_label: "calendarevent", + reconcile_collections: collections::reconcile_calendars, + reconcile_items: items::reconcile_calendar_events, + }, + ) +} + +fn run_carddav( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + discovery: &Discovery, + config: &DavImportConfig, + logger: Logger, +) -> Result { + run_collection_phase( + conn, + client, + source_id, + discovery, + config, + logger, + ItemPhase { + container_label: "addressbook", + item_label: "contactcard", + reconcile_collections: collections::reconcile_address_books, + reconcile_items: items::reconcile_contact_cards, + }, + ) +} + +fn run_webdav( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + discovery: &Discovery, + config: &DavImportConfig, + logger: Logger, +) -> Result { + let mut summary = Summary::default(); + let mut file_counts = TypeCounts::default(); + + if discovery.collections.is_empty() { + summary.per_type.push(("filenode", file_counts)); + return Ok(summary); + } + let root = &discovery.collections[0]; + let ctx = tree::WebDavCtx { + client, + source_id, + base_url: &discovery.home_set_url, + dav_connections: config.dav_connections, + logger, + }; + tree::reconcile_filenodes(conn, &ctx, root, &mut file_counts)?; + + summary.per_type.push(("filenode", file_counts)); + Ok(summary) +} + +fn map_discovery_error(err: DiscoveryError) -> Error { + match err { + DiscoveryError::NotFound { url } => Error::Usage(format!( + "no DAV collections found under --url {url:?}; \ + check the URL and that the account has access" + )), + DiscoveryError::Transport(e) => Error::from(e), + DiscoveryError::Parse(e) => { + Error::Connection(format!("DAV discovery: malformed multistatus: {e}")) + } + DiscoveryError::Href(e) => Error::Connection(format!("DAV discovery: bad href: {e}")), + } +} + +fn run_gc(conn: &Connection) -> Result<(), Error> { + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + db::blobs::gc_orphan_blobs(&tx).map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + Ok(()) +} + +fn enforce_tls_policy(url: &str, allow_cleartext: bool) -> Result<(), Error> { + let parsed = + url::Url::parse(url).map_err(|e| Error::Usage(format!("invalid --url {url:?}: {e}")))?; + match parsed.scheme() { + "https" => Ok(()), + "http" => { + if allow_cleartext { + Ok(()) + } else { + Err(Error::Connection( + "http:// URL requires --allow-cleartext".to_owned(), + )) + } + } + other => Err(Error::Usage(format!( + "--url scheme must be http or https, got {other}" + ))), + } +} + +fn normalise_base_url(url: &str) -> String { + let parsed = match url::Url::parse(url) { + Ok(u) => u, + Err(_) => return url.to_owned(), + }; + let scheme = parsed.scheme(); + let host = parsed.host_str().unwrap_or(""); + let port = parsed.port().map(|p| format!(":{p}")).unwrap_or_default(); + format!("{scheme}://{host}{port}") +} + +fn normalise_account_url(url: &str) -> String { + let mut parsed = match url::Url::parse(url) { + Ok(u) => u, + Err(_) => return url.to_owned(), + }; + parsed.set_query(None); + parsed.set_fragment(None); + let path = parsed.path().to_owned(); + if !path.ends_with('/') { + parsed.set_path(&format!("{path}/")); + } + parsed.to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn https_url_is_accepted_without_allow_cleartext() { + assert!(enforce_tls_policy("https://x/", false).is_ok()); + } + + #[test] + fn http_url_requires_allow_cleartext() { + let err = enforce_tls_policy("http://x/", false).unwrap_err(); + assert!(matches!(err, Error::Connection(_))); + assert!(enforce_tls_policy("http://x/", true).is_ok()); + } + + #[test] + fn ftp_url_rejected_as_usage() { + let err = enforce_tls_policy("ftp://x/", false).unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } + + #[test] + fn normalise_base_url_strips_path() { + assert_eq!( + normalise_base_url("https://dav.example.com/cal/"), + "https://dav.example.com" + ); + } + + #[test] + fn normalise_base_url_keeps_explicit_port() { + assert_eq!( + normalise_base_url("https://dav.example.com:8443/cal/"), + "https://dav.example.com:8443" + ); + } +} diff --git a/src/sync/import_dav/items.rs b/src/sync/import_dav/items.rs new file mode 100644 index 0000000..fed37df --- /dev/null +++ b/src/sync/import_dav/items.rs @@ -0,0 +1,653 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use rusqlite::{Connection, params}; + +use crate::dav::client::{DavClient, MultiStatus}; +use crate::dav::href::{Href, join_absolute, normalise}; +use crate::dav::parse::DavResponse; +use crate::dav::xml; +use crate::db::dav_ids; +use crate::error::Error; +use crate::jmap::error::JmapError; +use crate::logging::{LEVEL_PROGRESS, Logger}; +use crate::sync::TypeCounts; +use crate::sync::import_jmap::pool::Pool; + +use super::calcard; + +pub struct ItemRunCtx<'a> { + pub client: &'a DavClient, + pub source_id: i64, + pub base_url: &'a str, + pub multiget_batch: usize, + pub dav_connections: usize, + pub logger: Logger, +} + +struct PerCollectionCtx<'a> { + run: &'a ItemRunCtx<'a>, + kind: ItemKind, + collection_href: &'a str, + container_local_id: i64, + absolute_url: &'a str, +} + +pub fn reconcile_calendar_events( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + collection_href: &str, + calendar_local_id: i64, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let absolute = absolute_collection_url(ctx.base_url, collection_href)?; + let server_items = enumerate_items(ctx.client, &absolute)?; + let pcc = PerCollectionCtx { + run: ctx, + kind: ItemKind::CalendarEvent, + collection_href, + container_local_id: calendar_local_id, + absolute_url: &absolute, + }; + reconcile_items_generic(conn, &pcc, &server_items, counts) +} + +pub fn reconcile_contact_cards( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + collection_href: &str, + address_book_local_id: i64, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let absolute = absolute_collection_url(ctx.base_url, collection_href)?; + let server_items = enumerate_items(ctx.client, &absolute)?; + let pcc = PerCollectionCtx { + run: ctx, + kind: ItemKind::ContactCard, + collection_href, + container_local_id: address_book_local_id, + absolute_url: &absolute, + }; + reconcile_items_generic(conn, &pcc, &server_items, counts) +} + +#[derive(Debug, Clone, Copy)] +enum ItemKind { + CalendarEvent, + ContactCard, +} + +impl ItemKind { + fn type_name(self) -> &'static str { + match self { + ItemKind::CalendarEvent => dav_ids::CALENDAR_EVENT, + ItemKind::ContactCard => dav_ids::CONTACT_CARD, + } + } + fn data_query(self) -> fn(&[Href]) -> String { + match self { + ItemKind::CalendarEvent => xml::calendar_multiget, + ItemKind::ContactCard => xml::addressbook_multiget, + } + } + fn data_field(self) -> &'static str { + match self { + ItemKind::CalendarEvent => "calendar_data", + ItemKind::ContactCard => "address_data", + } + } +} + +#[derive(Debug, Clone)] +struct ServerItem { + href: String, + etag: String, +} + +fn enumerate_items(client: &DavClient, url: &str) -> Result, Error> { + let body = xml::propfind_dav_items(); + let ms = client + .propfind_responses(url, 1, &body, url) + .map_err(Error::from)?; + if ms.status >= 400 { + return Err(Error::Partial(format!( + "enumerate {url}: http {}", + ms.status + ))); + } + let self_href = normalise(url, "") + .map(|h| h.into_string()) + .unwrap_or_default(); + let mut out = Vec::new(); + for r in ms.responses { + if r.href.as_str() == self_href { + continue; + } + if r.props.is_collection { + continue; + } + out.push(ServerItem { + href: r.href.into_string(), + etag: r.props.etag.unwrap_or_default(), + }); + } + Ok(out) +} + +struct MultigetJob { + body: String, + hrefs: Vec, +} + +struct MultigetReply { + hrefs: Vec, + result: Result, +} + +struct GetJob { + url: String, + item: ServerItem, +} + +struct GetReply { + item: ServerItem, + result: Result, +} + +struct GetBody { + status: u16, + bytes: Vec, + etag: Option, +} + +fn reconcile_items_generic( + conn: &mut Connection, + pcc: &PerCollectionCtx<'_>, + server_items: &[ServerItem], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let type_name = pcc.kind.type_name(); + let local_rows = + dav_ids::items_in_collection(conn, pcc.run.source_id, type_name, pcc.collection_href) + .map_err(|e| Error::Partial(e.to_string()))?; + let local_map: HashMap = local_rows + .iter() + .map(|r| (r.item_href.clone(), (r.etag.clone(), r.local_id))) + .collect(); + let server_set: HashSet<&str> = server_items.iter().map(|s| s.href.as_str()).collect(); + + let mut to_fetch: Vec = Vec::new(); + for s in server_items { + match local_map.get(&s.href) { + None => to_fetch.push(s.clone()), + Some((local_etag, _)) => { + if s.etag.is_empty() || s.etag != *local_etag { + to_fetch.push(s.clone()); + } + } + } + } + + let vanished: Vec = local_map + .keys() + .filter(|h| !server_set.contains(h.as_str())) + .cloned() + .collect(); + + if !to_fetch.is_empty() { + let fallback = run_multiget_pool(conn, pcc, &to_fetch, &local_map, counts)?; + if !fallback.is_empty() { + run_get_pool(conn, pcc, &fallback, &local_map, counts)?; + } + } + + for href in &vanished { + let local_id = match local_map.get(href) { + Some((_, id)) => *id, + None => continue, + }; + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let table = match pcc.kind { + ItemKind::CalendarEvent => "calendar_events", + ItemKind::ContactCard => "contact_cards", + }; + tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + params![local_id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + dav_ids::delete_item(&tx, pcc.run.source_id, type_name, href) + .map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + counts.deleted += 1; + } + + if pcc.run.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "items in {}: fetched={} deleted={} failed={}", + pcc.collection_href, counts.fetched, counts.deleted, counts.failed + ); + } + Ok(()) +} + +fn run_multiget_pool( + conn: &mut Connection, + pcc: &PerCollectionCtx<'_>, + to_fetch: &[ServerItem], + local_map: &HashMap, + counts: &mut TypeCounts, +) -> Result, Error> { + let logger = pcc.run.logger; + let absolute_url = pcc.absolute_url; + let batch_size = pcc.run.multiget_batch.max(1); + let workers = pcc.run.dav_connections.clamp(1, 8); + let by_href: HashMap = to_fetch + .iter() + .map(|s| (s.href.clone(), s.clone())) + .collect(); + let client_for_pool = pcc.run.client.clone(); + let url_for_pool = absolute_url.to_owned(); + let pool: Pool = Pool::new(workers, move |job: MultigetJob| { + let result = client_for_pool.report_responses(&url_for_pool, 1, &job.body, &url_for_pool); + MultigetReply { + hrefs: job.hrefs, + result, + } + }); + let mut submitted: usize = 0; + for chunk in to_fetch.chunks(batch_size) { + let hrefs: Vec = chunk + .iter() + .map(|s| Href::from_normalised(s.href.clone())) + .collect(); + let chunk_hrefs: Vec = chunk.iter().map(|s| s.href.clone()).collect(); + pool.submit(MultigetJob { + body: (pcc.kind.data_query())(&hrefs), + hrefs: chunk_hrefs, + }); + submitted += 1; + } + + let mut fallback: Vec = Vec::new(); + for _ in 0..submitted { + let reply = match pool.results().recv() { + Ok(r) => r, + Err(_) => break, + }; + match reply.result { + Err(JmapError::HttpStatus { status, .. }) if status == 405 || status == 501 => { + logger.warn(&format!( + "multiget {absolute_url} returned {status}; server may not implement multiget, falling back to per-item GET" + )); + fallback.extend(reply.hrefs.iter().filter_map(|h| by_href.get(h).cloned())); + } + Err(e) => { + logger.warn(&format!( + "multiget {absolute_url}: {e}; falling back to per-item GET" + )); + fallback.extend(reply.hrefs.iter().filter_map(|h| by_href.get(h).cloned())); + } + Ok(ms) if ms.status >= 400 => { + logger.warn(&format!( + "multiget {absolute_url} returned {}; chunk failed", + ms.status + )); + counts.failed += reply.hrefs.len() as u64; + } + Ok(ms) => { + let requested: HashSet<&str> = reply.hrefs.iter().map(String::as_str).collect(); + let mut tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let mut pending: usize = 0; + for r in ms.responses { + if !requested.contains(r.href.as_str()) { + logger.warn(&format!( + "multiget {absolute_url}: server returned unsolicited href {}; skipping", + r.href.as_str() + )); + continue; + } + handle_multiget_response(&tx, pcc, &r, local_map, counts); + pending += 1; + if pending >= COMMIT_BATCH { + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + pending = 0; + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + } + } + } + drop(pool); + Ok(fallback) +} + +fn run_get_pool( + conn: &mut Connection, + pcc: &PerCollectionCtx<'_>, + items: &[ServerItem], + local_map: &HashMap, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let logger = pcc.run.logger; + let absolute_url = pcc.absolute_url; + let workers = pcc.run.dav_connections.clamp(1, 8); + let client_for_pool = pcc.run.client.clone(); + let pool: Pool = Pool::new(workers, move |job: GetJob| { + let r = client_for_pool.get(&job.url).map(|resp| GetBody { + status: resp.status, + bytes: resp.body, + etag: resp.etag, + }); + GetReply { + item: job.item, + result: r, + } + }); + + let mut submitted: usize = 0; + for item in items { + let url = match absolute_item_url(absolute_url, &item.href) { + Ok(u) => u, + Err(e) => { + logger.warn(&format!("item url {}: {e}", item.href)); + counts.failed += 1; + continue; + } + }; + pool.submit(GetJob { + url, + item: item.clone(), + }); + submitted += 1; + } + + let mut tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let mut pending: usize = 0; + for _ in 0..submitted { + let reply = match pool.results().recv() { + Ok(r) => r, + Err(_) => break, + }; + match reply.result { + Err(e) => { + logger.warn(&format!("GET {}: {e}", reply.item.href)); + counts.failed += 1; + } + Ok(body) if body.status == 404 || body.status == 410 => {} + Ok(body) if body.status >= 400 => { + logger.warn(&format!("GET {}: http {}", reply.item.href, body.status)); + counts.failed += 1; + } + Ok(body) => { + let etag = body.etag.unwrap_or_else(|| reply.item.etag.clone()); + let raw = match std::str::from_utf8(&body.bytes) { + Ok(s) => s.to_owned(), + Err(e) => { + logger.warn(&format!("GET {}: non-utf8 body: {e}", reply.item.href)); + counts.failed += 1; + continue; + } + }; + if let Err(e) = + insert_or_update_tx(&tx, pcc, &reply.item.href, &etag, &raw, local_map) + { + logger.warn(&format!("item {}: {e}", reply.item.href)); + counts.failed += 1; + } else { + counts.fetched += 1; + pending += 1; + if pending >= COMMIT_BATCH { + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + pending = 0; + } + } + } + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + drop(pool); + Ok(()) +} + +const COMMIT_BATCH: usize = 64; + +fn handle_multiget_response( + tx: &rusqlite::Transaction<'_>, + pcc: &PerCollectionCtx<'_>, + response: &DavResponse, + local_map: &HashMap, + counts: &mut TypeCounts, +) { + let logger = pcc.run.logger; + if response_indicates_vanished(response) { + return; + } + let item_href = response.href.as_str().to_owned(); + let etag = response.props.etag.clone().unwrap_or_default(); + let data = match pcc.kind { + ItemKind::CalendarEvent => response.props.calendar_data.as_deref(), + ItemKind::ContactCard => response.props.address_data.as_deref(), + }; + let Some(raw) = data else { + logger.warn(&format!( + "multiget {item_href}: missing {} payload", + pcc.kind.data_field() + )); + counts.failed += 1; + return; + }; + if let Err(e) = insert_or_update_tx(tx, pcc, &item_href, &etag, raw, local_map) { + logger.warn(&format!("item {item_href}: {e}")); + counts.failed += 1; + } else { + counts.fetched += 1; + } +} + +fn insert_or_update_tx( + tx: &rusqlite::Transaction<'_>, + pcc: &PerCollectionCtx<'_>, + item_href: &str, + etag: &str, + raw: &str, + local_map: &HashMap, +) -> Result<(), Error> { + let existing = local_map.get(item_href).map(|(_, id)| *id); + match pcc.kind { + ItemKind::CalendarEvent => insert_or_update_event( + tx, + pcc.container_local_id, + existing, + raw, + item_href, + pcc.run.logger, + )?, + ItemKind::ContactCard => { + insert_or_update_card(tx, pcc.container_local_id, existing, raw, item_href)? + } + }; + if existing.is_none() { + let new_local = tx.last_insert_rowid(); + dav_ids::insert( + tx, + pcc.run.source_id, + pcc.kind.type_name(), + pcc.collection_href, + item_href, + etag, + new_local, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } else { + dav_ids::update_etag(tx, pcc.run.source_id, pcc.kind.type_name(), item_href, etag) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn insert_or_update_event( + tx: &rusqlite::Transaction<'_>, + calendar_local: i64, + existing: Option, + raw: &str, + item_href: &str, + logger: Logger, +) -> Result<(), Error> { + let entries = calcard::ical_to_jscalendar_entries(raw) + .map_err(|e| Error::Partial(format!("iCalendar parse: {e}")))?; + if entries.len() > 1 && logger.enabled(crate::logging::LEVEL_DEFAULT) { + logger.warn(&format!( + "{item_href}: iCalendar resource has {} entries; storing the first ({}), \ + others dropped (recurrence overrides on the master are preserved)", + entries.len(), + entries[0].data_type.as_column(), + )); + } + let mut first = entries + .into_iter() + .next() + .ok_or_else(|| Error::Partial("iCalendar contained no parseable entries".to_owned()))?; + let (is_draft, use_default_alerts, _uid) = + calcard::strip_extracted_fields_from_event(&mut first.data); + let data_type = first.data_type.as_column(); + let calendar_ids = format!("[{calendar_local}]"); + if let Some(local) = existing { + tx.execute( + "UPDATE calendar_events SET calendar_ids = ?1, is_draft = ?2, + use_default_alerts = ?3, data = ?4, data_type = ?5 + WHERE id = ?6", + params![ + calendar_ids, + is_draft as i64, + use_default_alerts as i64, + first.data.to_string(), + data_type, + local, + ], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } else { + tx.execute( + "INSERT INTO calendar_events (calendar_ids, is_draft, use_default_alerts, data, data_type) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + calendar_ids, + is_draft as i64, + use_default_alerts as i64, + first.data.to_string(), + data_type, + ], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn insert_or_update_card( + tx: &rusqlite::Transaction<'_>, + address_book_local: i64, + existing: Option, + raw: &str, + item_href: &str, +) -> Result<(), Error> { + let card = calcard::vcard_to_jscontact(raw, item_href) + .map_err(|e| Error::Partial(format!("vCard parse: {e}")))?; + let address_book_ids = format!("[{address_book_local}]"); + if let Some(local) = existing { + tx.execute( + "UPDATE contact_cards SET uid = ?1, address_book_ids = ?2, data = ?3 + WHERE id = ?4", + params![card.uid, address_book_ids, card.data.to_string(), local], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } else { + tx.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![card.uid, address_book_ids, card.data.to_string()], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn response_indicates_vanished(response: &DavResponse) -> bool { + if let Some(status) = response.status + && (status == 404 || status == 410) + { + return true; + } + response + .propstat_errors + .iter() + .any(|s| *s == 404 || *s == 410) +} + +fn absolute_collection_url(base_url: &str, href: &str) -> Result { + join_absolute(base_url, href).map_err(|e| Error::Partial(e.to_string())) +} + +fn absolute_item_url(collection_url: &str, item_href: &str) -> Result { + join_absolute(collection_url, item_href).map_err(|e| Error::Partial(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn absolute_collection_url_joins_path() { + let u = absolute_collection_url("https://x/dav/", "/dav/cal/u/d/").unwrap(); + assert_eq!(u, "https://x/dav/cal/u/d/"); + } + + #[test] + fn absolute_item_url_joins_relative() { + let u = absolute_item_url("https://x/dav/cal/u/d/", "/dav/cal/u/d/event.ics").unwrap(); + assert_eq!(u, "https://x/dav/cal/u/d/event.ics"); + } + + fn response(status: Option, propstat_errors: Vec) -> DavResponse { + DavResponse { + href: Href::from_normalised("/dav/cal/u/d/e.ics".to_owned()), + status, + props: crate::dav::parse::ResourceProps::default(), + propstat_errors, + } + } + + #[test] + fn response_level_404_is_vanished() { + assert!(response_indicates_vanished(&response(Some(404), vec![]))); + assert!(response_indicates_vanished(&response(Some(410), vec![]))); + } + + #[test] + fn per_propstat_404_or_410_is_vanished() { + assert!(response_indicates_vanished(&response(None, vec![404]))); + assert!(response_indicates_vanished(&response(None, vec![200, 410]))); + } + + #[test] + fn other_propstat_errors_are_not_vanished() { + assert!(!response_indicates_vanished(&response(None, vec![403]))); + assert!(!response_indicates_vanished(&response(None, vec![]))); + } +} diff --git a/src/sync/import_dav/tree.rs b/src/sync/import_dav/tree.rs new file mode 100644 index 0000000..4aaa35f --- /dev/null +++ b/src/sync/import_dav/tree.rs @@ -0,0 +1,681 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashSet, VecDeque}; +use std::io::Read; + +use rusqlite::{Connection, OptionalExtension, params}; +use time::format_description::well_known::Rfc3339; + +use crate::dav::client::DavClient; +use crate::dav::discover::DiscoveredCollection; +use crate::dav::href::{Href, join_absolute, last_path_component}; +use crate::dav::parse::DavResponse; +use crate::dav::xml; +use crate::db; +use crate::db::dav_ids; +use crate::error::Error; +use crate::jmap::error::JmapError; +use crate::logging::{LEVEL_PROGRESS, Logger}; +use crate::sync::TypeCounts; +use crate::sync::import_jmap::pool::Pool; + +struct FilePlan { + item_href: String, + parent_href: String, + parent_local: i64, + existing_local: Option, + propfind_etag: String, + propfind_content_type: Option, + propfind_last_modified: Option, + propfind_creation_date: Option, + propfind_displayname: Option, + url: String, +} + +struct FileFetch { + plan: FilePlan, +} + +struct FileFetched { + plan: FilePlan, + result: Result, +} + +struct FetchedBody { + status: u16, + bytes: Vec, + etag: Option, + content_type: Option, + last_modified: Option, +} + +pub struct WebDavCtx<'a> { + pub client: &'a DavClient, + pub source_id: i64, + pub base_url: &'a str, + pub dav_connections: usize, + pub logger: Logger, +} + +pub fn reconcile_filenodes( + conn: &mut Connection, + ctx: &WebDavCtx<'_>, + root: &DiscoveredCollection, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let client = ctx.client; + let source_id = ctx.source_id; + let base_url = ctx.base_url; + let dav_connections = ctx.dav_connections; + let logger = ctx.logger; + let absolute_root = absolute(base_url, root.href.as_str())?; + let root_local = super::collections::upsert_root_directory(conn, source_id, root)?; + + let known: Vec<(String, i64)> = + dav_ids::collections_of_type(conn, source_id, dav_ids::FILE_NODE) + .map_err(|e| Error::Partial(e.to_string()))? + .into_iter() + .collect(); + let mut seen: HashSet = HashSet::new(); + seen.insert(root.href.as_str().to_owned()); + + let mut visited: HashSet = HashSet::new(); + visited.insert(root.href.as_str().to_owned()); + let mut queue: VecDeque<(String, String, i64)> = VecDeque::new(); + queue.push_back((absolute_root, root.href.as_str().to_owned(), root_local)); + + let mut file_plans: Vec = Vec::new(); + + while let Some((url, parent_href, parent_local)) = queue.pop_front() { + match walk_one( + conn, + client, + source_id, + WalkPos { + url: &url, + parent_href: &parent_href, + parent_local, + }, + WalkState { + counts, + seen: &mut seen, + file_plans: &mut file_plans, + }, + logger, + ) { + Ok(children) => { + for (child_url, child_href, child_local) in children { + if visited.insert(child_href.clone()) { + queue.push_back((child_url, child_href, child_local)); + } else { + logger.warn(&format!("cycle detected at {child_href}; not recursing")); + } + } + } + Err(e) => { + logger.warn(&format!("PROPFIND {url}: {e}")); + counts.failed += 1; + } + } + } + + fetch_files_parallel( + conn, + client, + source_id, + dav_connections, + file_plans, + counts, + logger, + )?; + + delete_vanished(conn, source_id, &known, &seen, counts, logger)?; + + Ok(()) +} + +fn fetch_files_parallel( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + dav_connections: usize, + plans: Vec, + counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + if plans.is_empty() { + return Ok(()); + } + let workers = dav_connections.clamp(1, 8); + let client_for_pool = client.clone(); + let pool: Pool = Pool::new(workers, move |job: FileFetch| { + let result = match client_for_pool.get_stream(&job.plan.url) { + Ok(mut s) => { + let mut bytes = Vec::new(); + match s.read_to_end(&mut bytes) { + Ok(_) => Ok(FetchedBody { + status: s.status, + bytes, + etag: s.etag, + content_type: s.content_type, + last_modified: s.last_modified, + }), + Err(e) => Err(JmapError::Transport(format!("read body: {e}"))), + } + } + Err(e) => Err(e), + }; + FileFetched { + plan: job.plan, + result, + } + }); + + let mut submitted: usize = 0; + for plan in plans { + pool.submit(FileFetch { plan }); + submitted += 1; + } + + const FILE_COMMIT_BATCH: usize = 16; + let mut tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let mut pending: usize = 0; + for _ in 0..submitted { + let fetched = match pool.results().recv() { + Ok(r) => r, + Err(_) => break, + }; + match fetched.result { + Err(e) => { + logger.warn(&format!("file {}: {e}", fetched.plan.item_href)); + counts.failed += 1; + } + Ok(body) if body.status == 404 || body.status == 410 => {} + Ok(body) if body.status >= 400 => { + logger.warn(&format!( + "file {}: http {}", + fetched.plan.item_href, body.status + )); + counts.failed += 1; + } + Ok(body) => match commit_file(&tx, source_id, &fetched.plan, body, logger) { + Ok(()) => { + counts.fetched += 1; + pending += 1; + if pending >= FILE_COMMIT_BATCH { + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + pending = 0; + } + } + Err(e) => { + logger.warn(&format!("file {}: {e}", fetched.plan.item_href)); + counts.failed += 1; + } + }, + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + drop(pool); + Ok(()) +} + +fn commit_file( + tx: &rusqlite::Transaction<'_>, + source_id: i64, + plan: &FilePlan, + body: FetchedBody, + logger: Logger, +) -> Result<(), Error> { + let etag = body + .etag + .clone() + .unwrap_or_else(|| plan.propfind_etag.clone()); + let content_type = body + .content_type + .or_else(|| plan.propfind_content_type.clone()); + let last_modified = normalise_dav_date( + &body + .last_modified + .or_else(|| plan.propfind_last_modified.clone()), + ); + let created = format_or_now(&plan.propfind_creation_date)?; + let name = display_or_basename(plan, logger); + + let blob_local = + db::blobs::intern_blob(tx, &body.bytes).map_err(|e| Error::Partial(e.to_string()))?; + if let Some(local) = plan.existing_local { + tx.execute( + "UPDATE file_nodes SET parent_id = ?1, node_type = 'file', blob_id = ?2, + target = NULL, name = ?3, media_type = ?4, + created = ?5, modified = ?6 + WHERE id = ?7", + params![ + plan.parent_local, + blob_local, + name, + content_type, + created, + last_modified, + local, + ], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + dav_ids::update_etag(tx, source_id, dav_ids::FILE_NODE, &plan.item_href, &etag) + .map_err(|e| Error::Partial(e.to_string()))?; + } else { + tx.execute( + "INSERT INTO file_nodes (parent_id, node_type, blob_id, target, name, media_type, + created, modified, is_subscribed, role) + VALUES (?1, 'file', ?2, NULL, ?3, ?4, ?5, ?6, 1, NULL)", + params![ + plan.parent_local, + blob_local, + name, + content_type, + created, + last_modified, + ], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let local = tx.last_insert_rowid(); + dav_ids::insert( + tx, + source_id, + dav_ids::FILE_NODE, + &plan.parent_href, + &plan.item_href, + &etag, + local, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn display_or_basename(plan: &FilePlan, logger: Logger) -> String { + let href = Href::from_normalised(plan.item_href.clone()); + let basename = decoded_basename(&href); + if let Some(n) = &plan.propfind_displayname + && !n.trim().is_empty() + && n.trim() != basename + && logger.enabled(crate::logging::LEVEL_BODIES) + { + eprintln!( + "webdav: {} displayname={:?} differs from basename={basename:?}; using basename", + plan.item_href, n + ); + } + basename +} + +fn delete_vanished( + conn: &mut Connection, + source_id: i64, + known: &[(String, i64)], + seen: &HashSet, + counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let mut vanished: Vec<(String, i64)> = known + .iter() + .filter(|(href, _)| !seen.contains(href)) + .cloned() + .collect(); + if vanished.is_empty() { + return Ok(()); + } + vanished.sort_by(|a, b| { + let depth_a = a.0.matches('/').count(); + let depth_b = b.0.matches('/').count(); + depth_b.cmp(&depth_a).then_with(|| a.0.cmp(&b.0)) + }); + + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + for (href, local_id) in &vanished { + if let Err(e) = tx.execute("DELETE FROM file_nodes WHERE id = ?1", params![local_id]) { + logger.warn(&format!( + "vanished file_node {href:?} delete failed: {e}; skipping" + )); + counts.failed += 1; + continue; + } + if let Err(e) = dav_ids::delete_item(&tx, source_id, dav_ids::FILE_NODE, href) { + logger.warn(&format!( + "vanished file_node {href:?} sync_id delete failed: {e}" + )); + counts.failed += 1; + continue; + } + counts.deleted += 1; + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("webdav: deleted {} vanished file_nodes", vanished.len()); + } + Ok(()) +} + +struct WalkPos<'a> { + url: &'a str, + parent_href: &'a str, + parent_local: i64, +} + +struct WalkState<'a> { + counts: &'a mut TypeCounts, + seen: &'a mut HashSet, + file_plans: &'a mut Vec, +} + +fn walk_one( + conn: &mut Connection, + client: &DavClient, + source_id: i64, + pos: WalkPos<'_>, + state: WalkState<'_>, + logger: Logger, +) -> Result, Error> { + let url = pos.url; + let parent_href = pos.parent_href; + let parent_local = pos.parent_local; + let counts = state.counts; + let seen = state.seen; + let file_plans = state.file_plans; + let body = xml::propfind_webdav_listing(); + let ms = client + .propfind_responses(url, 1, &body, url) + .map_err(Error::from)?; + if ms.status >= 400 { + return Err(Error::Partial(format!("http {}", ms.status))); + } + let parent_norm = crate::dav::href::normalise(url, "") + .map(|h| h.into_string()) + .unwrap_or_default(); + let mut children = Vec::new(); + for r in ms.responses { + if r.href.as_str() == parent_norm || r.href.as_str() == parent_href { + continue; + } + seen.insert(r.href.as_str().to_owned()); + if r.props.is_collection { + let local = upsert_directory(conn, source_id, &r, parent_local, counts)?; + let abs = absolute(url, r.href.as_str())?; + children.push((abs, r.href.as_str().to_owned(), local)); + } else { + match plan_file(conn, source_id, &r, url, parent_local, parent_href) { + Ok(Some(plan)) => file_plans.push(plan), + Ok(None) => {} + Err(e) => { + logger.warn(&format!("file {}: {e}", r.href.as_str())); + counts.failed += 1; + } + } + } + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "dir {url}: queued {} files (fetched-so-far={} failed-so-far={})", + file_plans.len(), + counts.fetched, + counts.failed + ); + } + Ok(children) +} + +fn plan_file( + conn: &Connection, + source_id: i64, + response: &DavResponse, + collection_url: &str, + parent_local: i64, + parent_href: &str, +) -> Result, Error> { + let item_href = response.href.as_str().to_owned(); + let propfind_etag = response.props.etag.clone().unwrap_or_default(); + let existing = dav_ids::local_for_item(conn, source_id, dav_ids::FILE_NODE, &item_href) + .map_err(|e| Error::Partial(e.to_string()))?; + if let Some(local) = existing { + let stored_etag: Option = conn + .query_row( + "SELECT etag FROM sync_id_dav + WHERE source_id = ?1 AND type_name = ?2 AND item_href = ?3", + params![source_id, dav_ids::FILE_NODE, item_href], + |r| r.get(0), + ) + .optional() + .map_err(|e| Error::Partial(e.to_string()))?; + if let Some(stored) = stored_etag.as_deref() + && !propfind_etag.is_empty() + && stored == propfind_etag + { + return Ok(None); + } + let url = absolute(collection_url, &item_href)?; + Ok(Some(FilePlan { + item_href, + parent_href: parent_href.to_owned(), + parent_local, + existing_local: Some(local), + propfind_etag, + propfind_content_type: response.props.content_type.clone(), + propfind_last_modified: response.props.last_modified.clone(), + propfind_creation_date: response.props.creation_date.clone(), + propfind_displayname: response.props.displayname.clone(), + url, + })) + } else { + let url = absolute(collection_url, &item_href)?; + Ok(Some(FilePlan { + item_href, + parent_href: parent_href.to_owned(), + parent_local, + existing_local: None, + propfind_etag, + propfind_content_type: response.props.content_type.clone(), + propfind_last_modified: response.props.last_modified.clone(), + propfind_creation_date: response.props.creation_date.clone(), + propfind_displayname: response.props.displayname.clone(), + url, + })) + } +} + +fn upsert_directory( + conn: &mut Connection, + source_id: i64, + response: &DavResponse, + parent_local: i64, + counts: &mut TypeCounts, +) -> Result { + let item_href = response.href.as_str().to_owned(); + let name = display_or_path(&response.props.displayname, &response.href); + let modified = normalise_dav_date(&response.props.last_modified); + let existing = dav_ids::local_for_item(conn, source_id, dav_ids::FILE_NODE, &item_href) + .map_err(|e| Error::Partial(e.to_string()))?; + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + if let Some(local) = existing { + tx.execute( + "UPDATE file_nodes SET parent_id = ?1, name = ?2, modified = ?3 + WHERE id = ?4", + params![parent_local, name, modified, local], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + counts.fetched += 1; + Ok(local) + } else { + let created = format_or_now(&response.props.creation_date)?; + tx.execute( + "INSERT INTO file_nodes (parent_id, node_type, blob_id, target, name, media_type, + created, modified, is_subscribed, role) + VALUES (?1, 'directory', NULL, NULL, ?2, NULL, ?3, ?4, 1, NULL)", + params![parent_local, name, created, modified], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let local = tx.last_insert_rowid(); + let parent_href = parent_href_string(&response.href); + dav_ids::insert( + &tx, + source_id, + dav_ids::FILE_NODE, + &parent_href, + &item_href, + "", + local, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + counts.created += 1; + Ok(local) + } +} + +pub(super) fn parent_href_string(href: &Href) -> String { + let s = href.as_str(); + let trimmed = s.trim_end_matches('/'); + if let Some(slash) = trimmed.rfind('/') { + s[..=slash].to_owned() + } else { + "/".to_owned() + } +} + +fn absolute(base: &str, href: &str) -> Result { + join_absolute(base, href).map_err(|e| Error::Partial(e.to_string())) +} + +fn display_or_path(name: &Option, href: &Href) -> String { + if let Some(n) = name + && !n.trim().is_empty() + { + return n.clone(); + } + decoded_basename(href) +} + +fn decoded_basename(href: &Href) -> String { + let c = last_path_component(href); + if c.is_empty() { "_".to_owned() } else { c } +} + +fn normalise_dav_date(input: &Option) -> Option { + let raw = input.as_deref()?.trim(); + if raw.is_empty() { + return None; + } + if let Ok(t) = time::OffsetDateTime::parse(raw, &Rfc3339) { + return t.format(&Rfc3339).ok(); + } + let imf_fixdate = time::macros::format_description!( + "[weekday repr:short], [day] [month repr:short] [year] \ + [hour]:[minute]:[second] GMT" + ); + if let Ok(p) = time::PrimitiveDateTime::parse(raw, imf_fixdate) { + return p.assume_utc().format(&Rfc3339).ok(); + } + None +} + +fn format_or_now(input: &Option) -> Result { + if let Some(s) = normalise_dav_date(input) { + return Ok(s); + } + time::OffsetDateTime::now_utc() + .format(&Rfc3339) + .map_err(|e| Error::Partial(format!("clock: {e}"))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parent_href_string_of_root_is_root() { + let h = Href::from_normalised("/".to_owned()); + assert_eq!(parent_href_string(&h), "/"); + } + + #[test] + fn parent_href_string_of_dir_strips_trailing_segment() { + let h = Href::from_normalised("/dav/file/u/work/".to_owned()); + assert_eq!(parent_href_string(&h), "/dav/file/u/"); + } + + #[test] + fn decoded_basename_handles_percent_encoding() { + let h = Href::from_normalised("/dav/file/u/work/note%201.txt".to_owned()); + assert_eq!(decoded_basename(&h), "note 1.txt"); + } + + fn plan_with(displayname: Option<&str>, href: &str) -> FilePlan { + FilePlan { + item_href: href.to_owned(), + parent_href: "/dav/file/u/".to_owned(), + parent_local: 1, + existing_local: None, + propfind_etag: String::new(), + propfind_content_type: None, + propfind_last_modified: None, + propfind_creation_date: None, + propfind_displayname: displayname.map(str::to_owned), + url: format!("https://x{href}"), + } + } + + #[test] + fn display_or_basename_prefers_url_basename_over_displayname() { + let plan = plan_with(Some("Pretty Name.txt"), "/dav/file/u/photo.jpg"); + let logger = Logger::from_flags(false, 0); + assert_eq!(display_or_basename(&plan, logger), "photo.jpg"); + } + + #[test] + fn display_or_basename_returns_basename_when_no_displayname() { + let plan = plan_with(None, "/dav/file/u/photo.jpg"); + let logger = Logger::from_flags(false, 0); + assert_eq!(display_or_basename(&plan, logger), "photo.jpg"); + } + + #[test] + fn normalise_dav_date_parses_imf_fixdate() { + let n = normalise_dav_date(&Some("Wed, 27 May 2026 07:59:07 GMT".to_owned())) + .expect("imf parse"); + assert!(n.starts_with("2026-05-27T07:59:07")); + } + + #[test] + fn normalise_dav_date_returns_none_on_garbage() { + assert!(normalise_dav_date(&Some("not a date".to_owned())).is_none()); + assert!(normalise_dav_date(&None).is_none()); + } + + #[test] + fn visited_hashset_breaks_recursion_on_cyclic_listing() { + let mut visited: HashSet = HashSet::new(); + visited.insert("/files/u/".to_owned()); + assert!(visited.insert("/files/u/a/".to_owned())); + assert!(visited.insert("/files/u/a/b/".to_owned())); + assert!( + !visited.insert("/files/u/".to_owned()), + "cycle back to root not re-recursed" + ); + assert!( + !visited.insert("/files/u/a/".to_owned()), + "cycle back to a/ not re-recursed" + ); + } +} diff --git a/src/sync/import_exchange_ews.rs b/src/sync/import_exchange_ews.rs new file mode 100644 index 0000000..877a6b7 --- /dev/null +++ b/src/sync/import_exchange_ews.rs @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod attachments; +pub mod calendar; +pub mod contacts; +pub mod coordinator; +pub mod folders; +pub mod items; +pub mod messages; + +pub use coordinator::{EwsAuth, EwsImportConfig, run}; diff --git a/src/sync/import_exchange_ews/attachments.rs b/src/sync/import_exchange_ews/attachments.rs new file mode 100644 index 0000000..7f4fd3e --- /dev/null +++ b/src/sync/import_exchange_ews/attachments.rs @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; + +use crate::db::blobs; +use crate::error::Error; +use crate::exchange_ews::error::EwsError; +use crate::exchange_ews::parse::parse_get_attachment_inline; +use crate::exchange_ews::xml::get_attachment_body; + +use super::items::ItemRunCtx; + +#[derive(Debug, Clone)] +pub struct FetchedAttachment { + pub attachment_id: String, + pub bytes: Vec, + pub media_type: String, +} + +pub fn fetch_contact_photo( + conn: &rusqlite::Connection, + ctx: &ItemRunCtx<'_>, + attachment_id: &str, +) -> Result, Error> { + let Some(att) = fetch_attachment_bytes(ctx, attachment_id).map_err(Error::from)? else { + return Ok(None); + }; + let blob_id = intern_attachment(conn, &att.bytes)?; + Ok(Some((blob_id, att.media_type))) +} + +pub fn fetch_attachment_bytes( + ctx: &ItemRunCtx<'_>, + attachment_id: &str, +) -> Result, EwsError> { + let mut out = fetch_attachments(ctx, &[attachment_id])?; + Ok(out.pop()) +} + +pub fn fetch_attachments( + ctx: &ItemRunCtx<'_>, + attachment_ids: &[&str], +) -> Result, EwsError> { + if attachment_ids.is_empty() { + return Ok(Vec::new()); + } + let batch = ctx.attachment_batch.max(1); + let mut out: Vec = Vec::with_capacity(attachment_ids.len()); + for chunk in attachment_ids.chunks(batch) { + let body = get_attachment_body(chunk); + let resp = ctx.client.call(ctx.url, "GetAttachment", &body)?; + let inline = parse_get_attachment_inline(&resp.body)?; + for att in inline { + let cleaned = strip_ascii_whitespace(att.content_base64.as_bytes()); + if cleaned.is_empty() { + continue; + } + let bytes = STANDARD.decode(&cleaned).map_err(|e| { + EwsError::Malformed(format!("attachment {}: base64: {e}", att.attachment_id)) + })?; + let media_type = att + .content_type + .unwrap_or_else(|| "application/octet-stream".to_owned()); + out.push(FetchedAttachment { + attachment_id: att.attachment_id, + bytes, + media_type, + }); + } + } + Ok(out) +} + +pub fn intern_attachment(conn: &rusqlite::Connection, bytes: &[u8]) -> Result { + blobs::intern_blob(conn, bytes).map_err(|e| Error::Partial(e.to_string())) +} + +fn strip_ascii_whitespace(input: &[u8]) -> Vec { + let mut out = Vec::with_capacity(input.len()); + for b in input { + if !matches!(b, b' ' | b'\t' | b'\n' | b'\r') { + out.push(*b); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::strip_ascii_whitespace; + + #[test] + fn strips_whitespace_efficiently() { + let input = b"AB CD\nEF\tGH\r\nIJ"; + let cleaned = strip_ascii_whitespace(input); + assert_eq!(cleaned, b"ABCDEFGHIJ"); + } +} diff --git a/src/sync/import_exchange_ews/calendar.rs b/src/sync/import_exchange_ews/calendar.rs new file mode 100644 index 0000000..819abd0 --- /dev/null +++ b/src/sync/import_exchange_ews/calendar.rs @@ -0,0 +1,292 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, params}; +use serde_json::Value; + +use crate::db::exchange_ews_ids; +use crate::error::Error; +use crate::exchange_ews::calendar_map::to_jscalendar; +use crate::exchange_ews::parse::{CalendarItemRaw, parse_calendar_item}; +use crate::exchange_ews::types::{CalendarItemType, ItemId}; +use crate::exchange_ews::xml::ItemShape; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; + +use super::attachments::{fetch_attachments, intern_attachment}; +use super::folders::FolderPlan; +use super::items::{ + EnumerationMode, ItemRunCtx, delete_vanished, enumerate_folder, get_items, plan_for, +}; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + plan: &FolderPlan, + counts: &mut TypeCounts, +) -> Result<(), Error> { + for folder in &plan.calendar { + let folder_id = &folder.folder.folder_id; + let local_folder_id = match exchange_ews_ids::local_for_item( + conn, + ctx.source_id, + exchange_ews_ids::CALENDAR, + &folder_id.id, + ) + .map_err(|e| Error::Partial(e.to_string()))? + { + Some(id) => id, + None => continue, + }; + if let Err(e) = reconcile_one(conn, ctx, folder_id, local_folder_id, counts) { + ctx.logger + .warn(&format!("calendar folder {} failed: {}", folder_id.id, e)); + counts.failed += 1; + } + } + Ok(()) +} + +fn reconcile_one( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + folder: &crate::exchange_ews::types::FolderId, + local_folder_id: i64, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let prior = exchange_ews_ids::get_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::CALENDAR, + &folder.id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let mut outcome = enumerate_folder(ctx, folder, prior.as_deref()).map_err(Error::from)?; + outcome.items.retain(|s| { + matches!( + s.element.to_ascii_lowercase().as_str(), + "calendaritem" | "item" + ) + }); + let local = exchange_ews_ids::items_in_folder( + conn, + ctx.source_id, + exchange_ews_ids::CALENDAR_EVENT, + &folder.id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let plan = plan_for(&outcome, &local); + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "EWS calendar folder {}: new={} changed={} vanished={}", + folder.id, + plan.new.len(), + plan.present_changed.len(), + plan.vanished.len() + ); + } + let mut to_fetch: Vec = plan.new.clone(); + for (id, _) in &plan.present_changed { + to_fetch.push(id.clone()); + } + if !to_fetch.is_empty() { + let outcome = get_items(ctx, ItemShape::CalendarItem, &to_fetch).map_err(Error::from)?; + counts.failed += outcome.failed_items; + for msg in outcome.messages { + if !msg.success { + if matches!( + msg.response_code, + crate::exchange_ews::types::ResponseCode::ItemNotFound + ) { + counts.skipped += 1; + } else { + counts.failed += 1; + ctx.logger + .warn(&format!("GetItem (calendar) error: {}", msg.response_code)); + } + continue; + } + let parsed = parse_calendar_item(&msg.inner_xml).map_err(Error::from)?; + if parsed.id.id.is_empty() { + counts.failed += 1; + continue; + } + if matches!( + parsed.calendar_item_type, + Some(CalendarItemType::Occurrence) | Some(CalendarItemType::Exception) + ) { + counts.skipped += 1; + continue; + } + let existing = plan + .present_changed + .iter() + .find(|(id, _)| id.id == parsed.id.id) + .map(|(_, local)| *local); + apply_event( + conn, + ctx, + &parsed, + local_folder_id, + &folder.id, + existing, + counts, + )?; + } + } + delete_vanished( + conn, + ctx.source_id, + exchange_ews_ids::CALENDAR_EVENT, + "calendar_events", + &plan.vanished, + counts, + )?; + if let EnumerationMode::Delta { new_sync_state, .. } = &outcome.mode { + exchange_ews_ids::set_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::CALENDAR, + &folder.id, + new_sync_state, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn apply_event( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + raw: &CalendarItemRaw, + local_folder_id: i64, + folder_id: &str, + existing_local_id: Option, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let mut built = to_jscalendar(raw); + attach_calendar_links(conn, ctx, raw, &mut built.data)?; + let calendar_ids = serde_json::json!([local_folder_id]).to_string(); + let data = built.data.to_string(); + let is_draft = if built.is_draft { 1 } else { 0 }; + let use_default_alerts = if built.use_default_alerts { 1 } else { 0 }; + + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + match existing_local_id { + Some(id) => { + tx.execute( + "UPDATE calendar_events SET calendar_ids = ?1, is_draft = ?2, \ + use_default_alerts = ?3, data = ?4, data_type = 'Event' WHERE id = ?5", + params![calendar_ids, is_draft, use_default_alerts, data, id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::CALENDAR_EVENT, + &raw.id.id, + &raw.id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.fetched += 1; + } + None => { + tx.execute( + "INSERT INTO calendar_events (calendar_ids, is_draft, use_default_alerts, data, data_type) \ + VALUES (?1, ?2, ?3, ?4, 'Event')", + params![calendar_ids, is_draft, use_default_alerts, data], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::CALENDAR_EVENT, + folder_id, + &raw.id.id, + &raw.id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.created += 1; + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + Ok(()) +} + +fn attach_calendar_links( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + raw: &CalendarItemRaw, + data: &mut Value, +) -> Result<(), Error> { + let refs: Vec<_> = raw + .attachments + .iter() + .filter(|a| !a.is_item_attachment && !a.attachment_id.is_empty()) + .collect(); + if refs.is_empty() { + return Ok(()); + } + let ids: Vec<&str> = refs.iter().map(|a| a.attachment_id.as_str()).collect(); + let fetched = match fetch_attachments(ctx, &ids) { + Ok(f) => f, + Err(e) => { + ctx.logger.warn(&format!( + "calendar attachments fetch failed: {e}; continuing without enclosures" + )); + return Ok(()); + } + }; + let mut links = serde_json::Map::new(); + for (idx, att) in (1u32..).zip(fetched) { + let blob_id = intern_attachment(conn, &att.bytes)?; + let declared = refs + .iter() + .find(|r| r.attachment_id == att.attachment_id) + .and_then(|r| r.content_type.as_deref()); + let name = refs + .iter() + .find(|r| r.attachment_id == att.attachment_id) + .and_then(|r| r.name.clone()); + let key = idx.to_string(); + let mut entry = serde_json::Map::new(); + entry.insert("@type".to_owned(), Value::String("Link".to_owned())); + entry.insert("@blob".to_owned(), Value::from(blob_id)); + entry.insert( + "contentType".to_owned(), + Value::String(content_type_or_from(declared, &att.media_type)), + ); + if let Some(n) = name { + entry.insert("title".to_owned(), Value::String(n)); + } + entry.insert("rel".to_owned(), Value::String("enclosure".to_owned())); + links.insert(key, Value::Object(entry)); + } + if links.is_empty() { + return Ok(()); + } + if let Some(obj) = data.as_object_mut() { + if let Some(existing) = obj.get_mut("links").and_then(Value::as_object_mut) { + for (k, v) in links { + existing.insert(k, v); + } + } else { + obj.insert("links".to_owned(), Value::Object(links)); + } + } + Ok(()) +} + +fn content_type_or_from(declared: Option<&str>, fallback: &str) -> String { + declared + .filter(|s| !s.is_empty()) + .map(str::to_owned) + .unwrap_or_else(|| fallback.to_owned()) +} diff --git a/src/sync/import_exchange_ews/contacts.rs b/src/sync/import_exchange_ews/contacts.rs new file mode 100644 index 0000000..2415948 --- /dev/null +++ b/src/sync/import_exchange_ews/contacts.rs @@ -0,0 +1,227 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, params}; + +use crate::db::exchange_ews_ids; +use crate::error::Error; +use crate::exchange_ews::contact_map::{synthetic_uid, to_jscontact}; +use crate::exchange_ews::parse::{ContactItemRaw, parse_contact_item}; +use crate::exchange_ews::types::ItemId; +use crate::exchange_ews::xml::ItemShape; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; + +use super::attachments::fetch_contact_photo; +use super::folders::FolderPlan; +use super::items::{ + EnumerationMode, ItemRunCtx, delete_vanished, enumerate_folder, get_items, plan_for, +}; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + plan: &FolderPlan, + counts: &mut TypeCounts, +) -> Result<(), Error> { + for folder in &plan.contacts { + let folder_id = &folder.folder.folder_id; + let local_folder_id = match exchange_ews_ids::local_for_item( + conn, + ctx.source_id, + exchange_ews_ids::ADDRESS_BOOK, + &folder_id.id, + ) + .map_err(|e| Error::Partial(e.to_string()))? + { + Some(id) => id, + None => continue, + }; + if let Err(e) = reconcile_one(conn, ctx, folder_id, local_folder_id, counts) { + ctx.logger + .warn(&format!("contact folder {} failed: {}", folder_id.id, e)); + counts.failed += 1; + } + } + Ok(()) +} + +fn reconcile_one( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + folder: &crate::exchange_ews::types::FolderId, + local_folder_id: i64, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let prior = exchange_ews_ids::get_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::ADDRESS_BOOK, + &folder.id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let outcome = enumerate_folder(ctx, folder, prior.as_deref()).map_err(Error::from)?; + let local = exchange_ews_ids::items_in_folder( + conn, + ctx.source_id, + exchange_ews_ids::CONTACT_CARD, + &folder.id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let plan = plan_for(&outcome, &local); + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "EWS contacts folder {}: new={} changed={} vanished={}", + folder.id, + plan.new.len(), + plan.present_changed.len(), + plan.vanished.len() + ); + } + let mut to_fetch: Vec = plan.new.clone(); + for (id, _) in &plan.present_changed { + to_fetch.push(id.clone()); + } + if !to_fetch.is_empty() { + let outcome = get_items(ctx, ItemShape::Contact, &to_fetch).map_err(Error::from)?; + counts.failed += outcome.failed_items; + for msg in outcome.messages { + if !msg.success { + if matches!( + msg.response_code, + crate::exchange_ews::types::ResponseCode::ItemNotFound + ) { + counts.skipped += 1; + } else { + counts.failed += 1; + ctx.logger + .warn(&format!("GetItem (contact) error: {}", msg.response_code)); + } + continue; + } + let parsed = parse_contact_item(&msg.inner_xml).map_err(Error::from)?; + if parsed.id.id.is_empty() { + counts.failed += 1; + continue; + } + let existing = plan + .present_changed + .iter() + .find(|(id, _)| id.id == parsed.id.id) + .map(|(_, local)| *local); + apply_contact( + conn, + ctx, + &parsed, + local_folder_id, + &folder.id, + existing, + counts, + )?; + } + } + delete_vanished( + conn, + ctx.source_id, + exchange_ews_ids::CONTACT_CARD, + "contact_cards", + &plan.vanished, + counts, + )?; + if let EnumerationMode::Delta { new_sync_state, .. } = &outcome.mode { + exchange_ews_ids::set_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::ADDRESS_BOOK, + &folder.id, + new_sync_state, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn apply_contact( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + item: &ContactItemRaw, + local_folder_id: i64, + folder_id: &str, + existing_local_id: Option, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let mut card = to_jscontact(item); + if let Some(photo_att) = item.attachments.iter().find(|a| a.is_contact_photo) { + match fetch_contact_photo(conn, ctx, &photo_att.attachment_id) { + Ok(Some((blob_id, media_type))) => { + let mut media = serde_json::Map::new(); + media.insert( + "photo".to_owned(), + serde_json::json!({ + "@type": "Media", + "kind": "photo", + "@blob": blob_id, + "mediaType": media_type, + }), + ); + if let Some(m) = card.as_object_mut() { + m.insert("media".to_owned(), serde_json::Value::Object(media)); + } + } + Ok(None) => {} + Err(e) => { + ctx.logger + .warn(&format!("contact photo fetch failed: {e}; continuing")); + } + } + } + let uid = synthetic_uid(&item.id.id); + let address_book_ids = serde_json::json!([local_folder_id]).to_string(); + let data = card.to_string(); + + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + match existing_local_id { + Some(id) => { + tx.execute( + "UPDATE contact_cards SET uid = ?1, address_book_ids = ?2, data = ?3 WHERE id = ?4", + params![uid, address_book_ids, data, id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::CONTACT_CARD, + &item.id.id, + &item.id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.fetched += 1; + } + None => { + tx.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![uid, address_book_ids, data], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::CONTACT_CARD, + folder_id, + &item.id.id, + &item.id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.created += 1; + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + Ok(()) +} diff --git a/src/sync/import_exchange_ews/coordinator.rs b/src/sync/import_exchange_ews/coordinator.rs new file mode 100644 index 0000000..eaee836 --- /dev/null +++ b/src/sync/import_exchange_ews/coordinator.rs @@ -0,0 +1,521 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::Connection; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::exchange_ews::EwsClient; +use crate::exchange_ews::autodiscover::{DiscoveryResult, discover}; +use crate::exchange_ews::oauth::{AcquiredToken, OAuthFlow, acquire}; +use crate::exchange_ews::types::MailboxKind; +use crate::jmap::http::{Auth, RetryPolicy}; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS}; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::folders::{self, plan_folders}; +use super::{calendar, contacts, messages}; + +#[derive(Debug, Clone)] +pub enum EwsAuth { + Basic { user: String, password: String }, + Bearer { token: String }, + OAuth(OAuthFlow), +} + +#[derive(Debug, Clone)] +pub struct EwsImportConfig { + pub url: Option, + pub mailbox: Option, + pub mailbox_kind: MailboxKind, + pub auth: EwsAuth, + pub ews_connections: usize, + pub getitem_batch: usize, + pub attachment_batch: usize, + pub use_syncfolderitems: bool, + pub allow_source_change: bool, +} + +pub fn run(common: CommonConfig, config: EwsImportConfig) -> Result { + let logger = common.logger; + let mut conn = db::init::open(&common.archive)?; + + let (auth, acquired) = resolve_auth(&config.auth, common.allow_invalid_certs)?; + let discovery = run_autodiscover(&config, &acquired, common.allow_invalid_certs)?; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "EWS discovery: url={} source={:?}", + discovery.ews_url, discovery.source + ); + } + let mailbox = resolve_mailbox(&config, &acquired)?; + let account_id = synthetic_account_id(&mailbox, config.mailbox_kind); + let session_url = normalise_ews_url(&discovery.ews_url); + enforce_basic_auth_policy(&config.auth, &session_url)?; + let key = SourceKey { + kind: "exchange_ews".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + + if !common.dry_run + && let Some((url, acc)) = + db::sources::conflicting_source(&conn, "exchange_ews", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records exchange_ews source ({url}, account {acc}); \ + pass --allow-source-change to import a different account" + ))); + } + + let client = EwsClient::new( + auth, + RetryPolicy::new(common.max_retries), + common.allow_invalid_certs, + ); + client.set_logger(logger); + if matches!(config.mailbox_kind, MailboxKind::PublicFolders) { + client.set_anchor_mailbox(None); + } else { + client.set_anchor_mailbox(Some(mailbox.clone())); + } + if let EwsAuth::OAuth(OAuthFlow::ClientCredentials { .. }) = &config.auth { + client.set_impersonation(Some(mailbox.clone())); + } + spawn_token_refresher( + &client, + &config.auth, + &acquired, + common.allow_invalid_certs, + logger, + ); + + let username = match &config.auth { + EwsAuth::Basic { user, .. } => user.clone(), + EwsAuth::Bearer { .. } | EwsAuth::OAuth(_) => acquired + .as_ref() + .and_then(|a| a.upn.clone()) + .unwrap_or_else(|| mailbox.clone()), + }; + + if common.dry_run { + let summary = run_dry(&conn, &client, &session_url, &config, logger)?; + return Ok(summary); + } + + let account_name = acquired.as_ref().and_then(|a| a.name.clone()); + let source_id = db::sources::upsert_source(&conn, &key, account_name.as_deref(), &username)?; + + let mut summary = Summary::default(); + let mut mailbox_counts = TypeCounts::default(); + let mut calendar_counts = TypeCounts::default(); + let mut addressbook_counts = TypeCounts::default(); + let mut email_counts = TypeCounts::default(); + let mut calendar_event_counts = TypeCounts::default(); + let mut contact_counts = TypeCounts::default(); + + let plan = + plan_folders(&client, &session_url, config.mailbox_kind, logger).map_err(Error::from)?; + + folders::reconcile( + &mut conn, + source_id, + &plan, + &mut mailbox_counts, + &mut calendar_counts, + &mut addressbook_counts, + logger, + )?; + + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "import: mailbox={} calendar={} addressbook={}", + mailbox_counts.created + mailbox_counts.fetched, + calendar_counts.created + calendar_counts.fetched, + addressbook_counts.created + addressbook_counts.fetched, + ); + } + + let item_ctx = super::items::ItemRunCtx { + client: &client, + url: &session_url, + source_id, + batch_size: config.getitem_batch.max(1), + attachment_batch: config.attachment_batch.max(1), + connections: config.ews_connections.clamp(1, 8), + use_syncfolderitems: config.use_syncfolderitems, + logger, + }; + + messages::reconcile_all(&mut conn, &item_ctx, &plan, &mut email_counts)?; + contacts::reconcile_all(&mut conn, &item_ctx, &plan, &mut contact_counts)?; + calendar::reconcile_all(&mut conn, &item_ctx, &plan, &mut calendar_event_counts)?; + + summary.per_type.push(("mailbox", mailbox_counts)); + summary.per_type.push(("calendar", calendar_counts)); + summary.per_type.push(("addressbook", addressbook_counts)); + summary.per_type.push(("email", email_counts)); + summary + .per_type + .push(("calendarevent", calendar_event_counts)); + summary.per_type.push(("contactcard", contact_counts)); + + if !summary.any_failed() + && let Err(e) = run_gc(&conn) + { + logger.warn(&format!("blob GC skipped: {e}")); + } + + summary.retries_observed = client.retries_observed(); + summary.retry_after_sleeps = client.retry_after_sleeps(); + Ok(summary) +} + +fn run_dry( + _conn: &Connection, + client: &EwsClient, + session_url: &str, + config: &EwsImportConfig, + logger: crate::logging::Logger, +) -> Result { + let plan = + plan_folders(client, session_url, config.mailbox_kind, logger).map_err(Error::from)?; + let mut summary = Summary::default(); + let mut mailbox_counts = TypeCounts::default(); + let mut calendar_counts = TypeCounts::default(); + let mut addressbook_counts = TypeCounts::default(); + mailbox_counts.created = plan.mail.len() as u64; + calendar_counts.created = plan.calendar.len() as u64; + addressbook_counts.created = plan.contacts.len() as u64; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "dry-run: mailbox={} calendar={} addressbook={}", + mailbox_counts.created, calendar_counts.created, addressbook_counts.created + ); + } + summary.per_type.push(("mailbox", mailbox_counts)); + summary.per_type.push(("calendar", calendar_counts)); + summary.per_type.push(("addressbook", addressbook_counts)); + Ok(summary) +} + +fn resolve_auth( + auth: &EwsAuth, + allow_invalid_certs: bool, +) -> Result<(Auth, Option), Error> { + match auth { + EwsAuth::Basic { user, password } => Ok(( + Auth::Basic { + user: user.clone(), + password: password.clone(), + }, + None, + )), + EwsAuth::Bearer { token } => { + let acq = acquire( + &OAuthFlow::PreAcquired { + token: token.clone(), + }, + allow_invalid_certs, + ) + .map_err(Error::from)?; + Ok(( + Auth::Bearer { + token: acq.access_token.clone(), + }, + Some(acq), + )) + } + EwsAuth::OAuth(flow) => { + let acq = acquire(flow, allow_invalid_certs).map_err(Error::from)?; + Ok(( + Auth::Bearer { + token: acq.access_token.clone(), + }, + Some(acq), + )) + } + } +} + +fn run_autodiscover( + config: &EwsImportConfig, + acquired: &Option, + allow_invalid_certs: bool, +) -> Result { + let email = config + .mailbox + .clone() + .or_else(|| acquired.as_ref().and_then(|a| a.upn.clone())); + let result = discover( + config.url.as_deref(), + email.as_deref(), + None, + allow_invalid_certs, + ) + .map_err(Error::from)?; + Ok(result) +} + +fn resolve_mailbox( + config: &EwsImportConfig, + acquired: &Option, +) -> Result { + if let Some(mb) = config.mailbox.as_ref() { + return Ok(mb.clone()); + } + if let Some(acq) = acquired.as_ref() + && let Some(upn) = acq.upn.as_ref() + { + return Ok(upn.clone()); + } + if let EwsAuth::Basic { user, .. } = &config.auth + && user.contains('@') + { + return Ok(user.clone()); + } + Err(Error::Account( + "could not resolve mailbox SMTP; pass --mailbox".to_owned(), + )) +} + +pub fn synthetic_account_id(mailbox: &str, kind: MailboxKind) -> String { + match kind { + MailboxKind::Primary => mailbox.to_owned(), + MailboxKind::Archive => format!("{mailbox}#archive"), + MailboxKind::PublicFolders => { + let domain = mailbox.split('@').nth(1).unwrap_or(""); + format!("__public_folders__@{domain}") + } + } +} + +pub fn normalise_ews_url(raw: &str) -> String { + let Ok(parsed) = url::Url::parse(raw) else { + return raw.to_owned(); + }; + let scheme = parsed.scheme().to_ascii_lowercase(); + let host = parsed.host_str().unwrap_or("").to_ascii_lowercase(); + let default_port = match scheme.as_str() { + "https" => 443, + "http" => 80, + _ => 0, + }; + let port = parsed.port().filter(|p| *p != default_port); + let path = if parsed.path().is_empty() || parsed.path() == "/" { + "/EWS/Exchange.asmx".to_owned() + } else { + let last = parsed.path().rsplit('/').next().unwrap_or(""); + if last.eq_ignore_ascii_case("Exchange.asmx") { + let mut p = String::new(); + let mut parts: Vec<&str> = parsed.path().split('/').collect(); + if let Some(last_part) = parts.last_mut() { + *last_part = "Exchange.asmx"; + } + if let Some(second_last_idx) = parts.len().checked_sub(2) + && parts[second_last_idx].eq_ignore_ascii_case("EWS") + { + parts[second_last_idx] = "EWS"; + } + p.push_str(&parts.join("/")); + p + } else { + parsed.path().to_owned() + } + }; + match port { + Some(p) => format!("{scheme}://{host}:{p}{path}"), + None => format!("{scheme}://{host}{path}"), + } +} + +fn enforce_basic_auth_policy(auth: &EwsAuth, url: &str) -> Result<(), Error> { + if let EwsAuth::Basic { .. } = auth { + let parsed = url::Url::parse(url) + .map_err(|e| Error::Usage(format!("invalid EWS URL {url:?}: {e}")))?; + if parsed.scheme() != "https" { + return Err(Error::Connection( + "EWS requires https:// (basic auth refused on cleartext)".to_owned(), + )); + } + let host = parsed.host_str().unwrap_or("").to_ascii_lowercase(); + let blocked = [ + "outlook.office365.com", + "outlook.office.com", + "outlook.office365.us", + "office365.us", + ]; + if blocked.iter().any(|b| host == *b || host.ends_with(b)) { + return Err(Error::Connection( + "Basic auth is disabled on Exchange Online; use --auth-bearer".to_owned(), + )); + } + } + Ok(()) +} + +fn spawn_token_refresher( + client: &EwsClient, + auth: &EwsAuth, + initial: &Option, + allow_invalid_certs: bool, + logger: crate::logging::Logger, +) { + let flow = match auth { + EwsAuth::OAuth(flow) => flow.clone(), + _ => return, + }; + let exp = initial.as_ref().and_then(|t| { + crate::exchange_ews::oauth::decode_jwt_claims(&t.access_token).and_then(|c| c.exp) + }); + let Some(exp_secs) = exp else { + return; + }; + let client = client.clone(); + let initial_refresh = initial.as_ref().and_then(|t| t.refresh_token.clone()); + std::thread::Builder::new() + .name("vandelay-ews-token-refresh".to_owned()) + .spawn(move || { + let mut next_exp = exp_secs; + let mut refresh_token = initial_refresh; + loop { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + let refresh_at = next_exp.saturating_sub(5 * 60); + if refresh_at > now { + std::thread::sleep(std::time::Duration::from_secs(refresh_at - now)); + } + let result = if let (Some(rt), OAuthFlow::DeviceCode { tenant, client_id }) = + (refresh_token.as_deref(), &flow) + { + crate::exchange_ews::oauth::refresh_with_token( + tenant, + client_id, + rt, + allow_invalid_certs, + ) + } else { + crate::exchange_ews::oauth::acquire(&flow, allow_invalid_certs) + }; + match result { + Ok(tok) => { + client.set_auth(crate::jmap::http::Auth::Bearer { + token: tok.access_token.clone(), + }); + if tok.refresh_token.is_some() { + refresh_token = tok.refresh_token.clone(); + } + if let Some(new_exp) = + crate::exchange_ews::oauth::decode_jwt_claims(&tok.access_token) + .and_then(|c| c.exp) + { + next_exp = new_exp; + } else { + next_exp = next_exp.saturating_add(50 * 60); + } + } + Err(e) => { + logger.warn(&format!( + "EWS token refresh failed: {e}; sleeping 60s before retry" + )); + std::thread::sleep(std::time::Duration::from_secs(60)); + } + } + } + }) + .ok(); +} + +fn run_gc(conn: &Connection) -> Result<(), Error> { + let tx = conn.unchecked_transaction()?; + db::blobs::gc_orphan_blobs(&tx)?; + tx.commit()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn synthetic_account_id_uses_smtp_for_primary() { + assert_eq!( + synthetic_account_id("alice@contoso.com", MailboxKind::Primary), + "alice@contoso.com" + ); + } + + #[test] + fn synthetic_account_id_archive_suffix() { + assert_eq!( + synthetic_account_id("alice@contoso.com", MailboxKind::Archive), + "alice@contoso.com#archive" + ); + } + + #[test] + fn synthetic_account_id_public_folders_uses_tenant() { + assert_eq!( + synthetic_account_id("alice@contoso.com", MailboxKind::PublicFolders), + "__public_folders__@contoso.com" + ); + } + + #[test] + fn basic_against_office365_endpoint_is_refused() { + let auth = EwsAuth::Basic { + user: "alice@x".to_owned(), + password: "p".to_owned(), + }; + let res = + enforce_basic_auth_policy(&auth, "https://outlook.office365.com/EWS/Exchange.asmx"); + assert!(matches!(res, Err(Error::Connection(_)))); + } + + #[test] + fn basic_over_https_on_prem_is_allowed() { + let auth = EwsAuth::Basic { + user: "alice@x".to_owned(), + password: "p".to_owned(), + }; + assert!( + enforce_basic_auth_policy(&auth, "https://exchange.example.com/EWS/Exchange.asmx") + .is_ok() + ); + } + + #[test] + fn basic_over_http_is_refused() { + let auth = EwsAuth::Basic { + user: "alice".to_owned(), + password: "p".to_owned(), + }; + assert!( + enforce_basic_auth_policy(&auth, "http://exchange.example.com/EWS/Exchange.asmx") + .is_err() + ); + } + + #[test] + fn normalise_ews_url_lowercases_host_and_keeps_canonical_path() { + assert_eq!( + normalise_ews_url("https://OUTLOOK.OFFICE365.COM/EWS/Exchange.asmx"), + "https://outlook.office365.com/EWS/Exchange.asmx" + ); + assert_eq!( + normalise_ews_url("https://outlook.office365.com:443/ews/exchange.asmx"), + "https://outlook.office365.com/EWS/Exchange.asmx" + ); + assert_eq!( + normalise_ews_url("https://srv.example.com:8443/EWS/Exchange.asmx"), + "https://srv.example.com:8443/EWS/Exchange.asmx" + ); + } +} diff --git a/src/sync/import_exchange_ews/folders.rs b/src/sync/import_exchange_ews/folders.rs new file mode 100644 index 0000000..99d0029 --- /dev/null +++ b/src/sync/import_exchange_ews/folders.rs @@ -0,0 +1,588 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, params}; + +use crate::db::exchange_ews_ids; +use crate::error::Error; +use crate::exchange_ews::EwsClient; +use crate::exchange_ews::error::EwsError; +use crate::exchange_ews::parse::{ + FolderEntry, parse_find_folder_response, parse_folder_inner, parse_get_folder_response, + parse_response_messages, +}; +use crate::exchange_ews::types::{DistinguishedFolderId, FolderClass, MailboxKind}; +use crate::exchange_ews::xml::{ + FolderRef, FolderShape, Traversal, find_folder_body, get_folder_body, +}; +use crate::logging::{LEVEL_PROGRESS, Logger}; +use crate::sync::TypeCounts; + +pub struct FolderPlan { + pub mail: Vec, + pub calendar: Vec, + pub contacts: Vec, + pub well_known_roles: HashMap, + pub root_folder_id: String, +} + +#[derive(Debug, Clone)] +pub struct ClassifiedFolder { + pub folder: FolderEntry, + pub kind: FolderClass, +} + +pub fn plan_folders( + client: &EwsClient, + url: &str, + mailbox_kind: MailboxKind, + logger: Logger, +) -> Result { + let root = mailbox_kind.distinguished_root(); + let request = find_folder_body(FolderRef::Distinguished(root), Traversal::Deep); + let resp = client.call(url, "FindFolder", &request)?; + let parsed = parse_find_folder_response(&resp.body)?; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("FindFolder returned {} folders", parsed.folders.len()); + } + let mut mail = Vec::new(); + let mut calendar = Vec::new(); + let mut contacts = Vec::new(); + for folder in parsed.folders { + let class = FolderClass::from_ipf(&folder.folder_class); + match (mailbox_kind, class) { + (MailboxKind::Archive, FolderClass::Mail) => { + mail.push(ClassifiedFolder { + folder, + kind: class, + }); + } + (MailboxKind::Archive, _) => {} + (_, FolderClass::Mail) => mail.push(ClassifiedFolder { + folder, + kind: class, + }), + (_, FolderClass::Calendar) => { + calendar.push(ClassifiedFolder { + folder, + kind: class, + }); + } + (_, FolderClass::Contacts) => { + contacts.push(ClassifiedFolder { + folder, + kind: class, + }); + } + (_, FolderClass::Skipped) => {} + } + } + let well_known_roles = resolve_well_known_roles(client, url, mailbox_kind, logger)?; + let root_folder_id = resolve_root_folder_id(client, url, mailbox_kind)?; + Ok(FolderPlan { + mail, + calendar, + contacts, + well_known_roles, + root_folder_id, + }) +} + +fn resolve_well_known_roles( + client: &EwsClient, + url: &str, + mailbox_kind: MailboxKind, + logger: Logger, +) -> Result, EwsError> { + if !matches!(mailbox_kind, MailboxKind::Primary) { + return Ok(HashMap::new()); + } + let folders: Vec> = WELL_KNOWN_ROLES + .iter() + .map(|(d, _)| FolderRef::Distinguished(*d)) + .collect(); + let request = get_folder_body(&folders, FolderShape::IdOnly); + let resp = match client.call(url, "GetFolder", &request) { + Ok(r) => r, + Err(e) => { + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("GetFolder for distinguished ids failed: {e}; roles will be empty"); + } + return Ok(HashMap::new()); + } + }; + let messages = + parse_response_messages(&resp.body, b"GetFolderResponseMessage").unwrap_or_default(); + let mut map: HashMap = HashMap::new(); + for (msg, (_, role)) in messages.into_iter().zip(WELL_KNOWN_ROLES.iter()) { + if !msg.success { + continue; + } + let Ok(Some(entry)) = parse_folder_inner(&msg.inner_xml) else { + continue; + }; + if entry.folder_id.id.is_empty() { + continue; + } + if let Some(r) = role { + map.insert(entry.folder_id.id, r); + } + } + Ok(map) +} + +fn resolve_root_folder_id( + client: &EwsClient, + url: &str, + mailbox_kind: MailboxKind, +) -> Result { + let root = mailbox_kind.distinguished_root(); + let body = get_folder_body(&[FolderRef::Distinguished(root)], FolderShape::IdOnly); + let resp = client.call(url, "GetFolder", &body)?; + let entries = parse_get_folder_response(&resp.body)?; + Ok(entries + .into_iter() + .next() + .map(|e| e.folder_id.id) + .unwrap_or_default()) +} + +const WELL_KNOWN_ROLES: &[(DistinguishedFolderId, Option<&'static str>)] = &[ + (DistinguishedFolderId::Inbox, Some("inbox")), + (DistinguishedFolderId::SentItems, Some("sent")), + (DistinguishedFolderId::Drafts, Some("drafts")), + (DistinguishedFolderId::DeletedItems, Some("trash")), + (DistinguishedFolderId::JunkEmail, Some("junk")), + (DistinguishedFolderId::Archive, Some("archive")), + (DistinguishedFolderId::Outbox, None), + (DistinguishedFolderId::ConversationHistory, None), +]; + +pub fn reconcile( + conn: &mut Connection, + source_id: i64, + plan: &FolderPlan, + mailbox_counts: &mut TypeCounts, + calendar_counts: &mut TypeCounts, + addressbook_counts: &mut TypeCounts, + _logger: Logger, +) -> Result<(), Error> { + let local_mailbox: HashMap = + exchange_ews_ids::folders_of_type(conn, source_id, exchange_ews_ids::MAILBOX) + .map_err(|e| Error::Partial(e.to_string()))?; + let local_calendar: HashMap = + exchange_ews_ids::folders_of_type(conn, source_id, exchange_ews_ids::CALENDAR) + .map_err(|e| Error::Partial(e.to_string()))?; + let local_addressbook: HashMap = + exchange_ews_ids::folders_of_type(conn, source_id, exchange_ews_ids::ADDRESS_BOOK) + .map_err(|e| Error::Partial(e.to_string()))?; + + let mut id_to_local: HashMap = HashMap::new(); + for (id, row) in local_mailbox + .iter() + .chain(local_calendar.iter()) + .chain(local_addressbook.iter()) + { + id_to_local.insert(id.clone(), row.local_id); + } + + let ordered_mail = order_by_parent(&plan.mail); + let ordered_calendar = order_by_parent(&plan.calendar); + let ordered_contacts = order_by_parent(&plan.contacts); + + let mut server_ids_mail: Vec = Vec::new(); + let mut server_ids_calendar: Vec = Vec::new(); + let mut server_ids_addressbook: Vec = Vec::new(); + + for folder in &ordered_mail { + server_ids_mail.push(folder.folder.folder_id.id.clone()); + let role = plan + .well_known_roles + .get(&folder.folder.folder_id.id) + .copied(); + let mut ctx = UpsertCtx { + conn, + source_id, + id_to_local: &mut id_to_local, + local: &local_mailbox, + counts: mailbox_counts, + root_folder_id: &plan.root_folder_id, + }; + upsert_mailbox(&mut ctx, folder, role)?; + } + for folder in &ordered_calendar { + server_ids_calendar.push(folder.folder.folder_id.id.clone()); + let mut ctx = UpsertCtx { + conn, + source_id, + id_to_local: &mut id_to_local, + local: &local_calendar, + counts: calendar_counts, + root_folder_id: &plan.root_folder_id, + }; + upsert_calendar(&mut ctx, folder)?; + } + for folder in &ordered_contacts { + server_ids_addressbook.push(folder.folder.folder_id.id.clone()); + let mut ctx = UpsertCtx { + conn, + source_id, + id_to_local: &mut id_to_local, + local: &local_addressbook, + counts: addressbook_counts, + root_folder_id: &plan.root_folder_id, + }; + upsert_address_book(&mut ctx, folder)?; + } + + delete_vanished( + conn, + source_id, + exchange_ews_ids::MAILBOX, + "mailboxes", + &local_mailbox, + &server_ids_mail, + mailbox_counts, + )?; + delete_vanished( + conn, + source_id, + exchange_ews_ids::CALENDAR, + "calendars", + &local_calendar, + &server_ids_calendar, + calendar_counts, + )?; + delete_vanished( + conn, + source_id, + exchange_ews_ids::ADDRESS_BOOK, + "address_books", + &local_addressbook, + &server_ids_addressbook, + addressbook_counts, + )?; + Ok(()) +} + +fn order_by_parent(folders: &[ClassifiedFolder]) -> Vec<&ClassifiedFolder> { + let by_id: HashMap<&str, &ClassifiedFolder> = folders + .iter() + .map(|f| (f.folder.folder_id.id.as_str(), f)) + .collect(); + let mut depths: HashMap<&str, usize> = HashMap::new(); + fn depth_of<'a>( + id: &'a str, + by_id: &HashMap<&'a str, &'a ClassifiedFolder>, + memo: &mut HashMap<&'a str, usize>, + seen: &mut std::collections::HashSet<&'a str>, + ) -> usize { + if let Some(d) = memo.get(id) { + return *d; + } + if !seen.insert(id) { + return 0; + } + let d = match by_id.get(id).and_then(|f| f.folder.parent_id.as_deref()) { + Some(parent) if by_id.contains_key(parent) => 1 + depth_of(parent, by_id, memo, seen), + _ => 0, + }; + memo.insert(id, d); + d + } + for f in folders { + let id = f.folder.folder_id.id.as_str(); + let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new(); + let d = depth_of(id, &by_id, &mut depths, &mut seen); + depths.insert(id, d); + } + let mut out: Vec<&ClassifiedFolder> = folders.iter().collect(); + out.sort_by_key(|f| { + depths + .get(f.folder.folder_id.id.as_str()) + .copied() + .unwrap_or(0) + }); + out +} + +struct UpsertCtx<'a> { + conn: &'a mut Connection, + source_id: i64, + id_to_local: &'a mut HashMap, + local: &'a HashMap, + counts: &'a mut TypeCounts, + root_folder_id: &'a str, +} + +fn upsert_mailbox( + ctx: &mut UpsertCtx<'_>, + folder: &ClassifiedFolder, + role: Option<&'static str>, +) -> Result<(), Error> { + let parent_local = parent_local_id(folder, ctx.id_to_local, ctx.root_folder_id); + let tx = ctx + .conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let existing = ctx.local.get(&folder.folder.folder_id.id); + let local_id = if let Some(row) = existing { + tx.execute( + "UPDATE mailboxes SET name = ?1, parent_id = ?2, role = ?3 WHERE id = ?4", + params![folder.folder.display_name, parent_local, role, row.local_id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::MAILBOX, + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.fetched += 1; + row.local_id + } else { + tx.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) \ + VALUES (?1, ?2, ?3, 0, 1)", + params![folder.folder.display_name, parent_local, role], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::MAILBOX, + folder.folder.parent_id.as_deref().unwrap_or(""), + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.created += 1; + new_id + }; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + ctx.id_to_local + .insert(folder.folder.folder_id.id.clone(), local_id); + Ok(()) +} + +fn upsert_calendar(ctx: &mut UpsertCtx<'_>, folder: &ClassifiedFolder) -> Result<(), Error> { + let tx = ctx + .conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let existing = ctx.local.get(&folder.folder.folder_id.id); + let local_id = if let Some(row) = existing { + tx.execute( + "UPDATE calendars SET name = ?1 WHERE id = ?2", + params![folder.folder.display_name, row.local_id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::CALENDAR, + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.fetched += 1; + row.local_id + } else { + tx.execute( + "INSERT INTO calendars (name, sort_order, is_subscribed, is_visible) \ + VALUES (?1, 0, 1, 1)", + params![folder.folder.display_name], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::CALENDAR, + folder.folder.parent_id.as_deref().unwrap_or(""), + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.created += 1; + new_id + }; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + ctx.id_to_local + .insert(folder.folder.folder_id.id.clone(), local_id); + Ok(()) +} + +fn upsert_address_book(ctx: &mut UpsertCtx<'_>, folder: &ClassifiedFolder) -> Result<(), Error> { + let tx = ctx + .conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let existing = ctx.local.get(&folder.folder.folder_id.id); + let local_id = if let Some(row) = existing { + tx.execute( + "UPDATE address_books SET name = ?1 WHERE id = ?2", + params![folder.folder.display_name, row.local_id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::ADDRESS_BOOK, + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.fetched += 1; + row.local_id + } else { + tx.execute( + "INSERT INTO address_books (name, sort_order, is_subscribed) \ + VALUES (?1, 0, 1)", + params![folder.folder.display_name], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::ADDRESS_BOOK, + folder.folder.parent_id.as_deref().unwrap_or(""), + &folder.folder.folder_id.id, + &folder.folder.folder_id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + ctx.counts.created += 1; + new_id + }; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + ctx.id_to_local + .insert(folder.folder.folder_id.id.clone(), local_id); + Ok(()) +} + +fn parent_local_id( + folder: &ClassifiedFolder, + id_to_local: &HashMap, + root_folder_id: &str, +) -> Option { + let parent_id = folder.folder.parent_id.as_deref()?; + if parent_id == root_folder_id || parent_id.is_empty() { + return None; + } + id_to_local.get(parent_id).copied() +} + +fn delete_vanished( + conn: &mut Connection, + source_id: i64, + type_name: &str, + table: &str, + local: &HashMap, + server_ids: &[String], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let server_set: std::collections::HashSet<&str> = + server_ids.iter().map(String::as_str).collect(); + let mut vanished: Vec<(&str, &exchange_ews_ids::FolderRow)> = local + .iter() + .filter(|(id, _)| !server_set.contains(id.as_str())) + .map(|(id, row)| (id.as_str(), row)) + .collect(); + if type_name == exchange_ews_ids::MAILBOX { + vanished.sort_by_key(|(_, row)| std::cmp::Reverse(folder_depth(row, local))); + } + for (item_id, row) in vanished { + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let result = tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + params![row.local_id], + ); + match result { + Ok(_) => { + exchange_ews_ids::delete_item(&tx, source_id, type_name, item_id) + .map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + counts.deleted += 1; + } + Err(_) => { + let _ = tx.rollback(); + counts.failed += 1; + } + } + } + Ok(()) +} + +fn folder_depth( + row: &exchange_ews_ids::FolderRow, + local: &HashMap, +) -> usize { + let mut depth = 0; + let mut cursor = row.folder_id.as_str(); + let mut visited: std::collections::HashSet<&str> = std::collections::HashSet::new(); + while !cursor.is_empty() && visited.insert(cursor) { + match local.get(cursor) { + Some(parent) => { + depth += 1; + cursor = parent.folder_id.as_str(); + } + None => break, + } + } + depth +} + +#[cfg(test)] +mod tests { + use super::*; + + fn row(id: &str, parent_ews_id: &str, local_id: i64) -> exchange_ews_ids::FolderRow { + exchange_ews_ids::FolderRow { + item_id: id.to_owned(), + folder_id: parent_ews_id.to_owned(), + change_key: String::new(), + local_id, + } + } + + #[test] + fn folder_depth_walks_parent_chain() { + let mut local: HashMap = HashMap::new(); + local.insert("ROOT".to_owned(), row("ROOT", "", 1)); + local.insert("CHILD".to_owned(), row("CHILD", "ROOT", 2)); + local.insert("GRAND".to_owned(), row("GRAND", "CHILD", 3)); + assert_eq!(folder_depth(local.get("ROOT").unwrap(), &local), 0); + assert_eq!(folder_depth(local.get("CHILD").unwrap(), &local), 1); + assert_eq!(folder_depth(local.get("GRAND").unwrap(), &local), 2); + } + + #[test] + fn folder_depth_handles_unknown_parent() { + let mut local: HashMap = HashMap::new(); + local.insert("X".to_owned(), row("X", "ORPHAN", 1)); + assert_eq!(folder_depth(local.get("X").unwrap(), &local), 0); + } + + #[test] + fn folder_depth_stops_on_cycle() { + let mut local: HashMap = HashMap::new(); + local.insert("A".to_owned(), row("A", "B", 1)); + local.insert("B".to_owned(), row("B", "A", 2)); + let d = folder_depth(local.get("A").unwrap(), &local); + assert!(d <= 2); + } +} diff --git a/src/sync/import_exchange_ews/items.rs b/src/sync/import_exchange_ews/items.rs new file mode 100644 index 0000000..722b474 --- /dev/null +++ b/src/sync/import_exchange_ews/items.rs @@ -0,0 +1,469 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use crate::error::Error; +use crate::exchange_ews::EwsClient; +use crate::exchange_ews::error::EwsError; +use crate::exchange_ews::parse::{ + SyncChange, parse_find_item_response, parse_response_messages, parse_sync_folder_items_response, +}; +use crate::exchange_ews::types::{FolderId, ItemId, ResponseCode}; +use crate::exchange_ews::xml::{ + FolderRef, ItemShape, Traversal, find_item_body, get_item_body, sync_folder_items_body, +}; +use crate::logging::Logger; + +pub struct ItemRunCtx<'a> { + pub client: &'a EwsClient, + pub url: &'a str, + pub source_id: i64, + pub batch_size: usize, + pub attachment_batch: usize, + pub connections: usize, + pub use_syncfolderitems: bool, + pub logger: Logger, +} + +#[derive(Debug, Clone)] +pub struct EnumeratedItem { + pub element: String, + pub id: ItemId, +} + +#[derive(Debug, Clone)] +pub struct EnumerationOutcome { + pub items: Vec, + pub mode: EnumerationMode, +} + +#[derive(Debug, Clone)] +pub enum EnumerationMode { + Full, + Delta { + deletions: Vec, + new_sync_state: String, + }, +} + +pub fn enumerate_folder( + ctx: &ItemRunCtx<'_>, + folder: &FolderId, + prior_sync_state: Option<&str>, +) -> Result { + if ctx.use_syncfolderitems + && let Some(state) = prior_sync_state + && !state.is_empty() + && let Some(outcome) = try_sync_folder_items(ctx, folder, state)? + { + return Ok(outcome); + } + enumerate_via_find_item(ctx, folder).map(|items| EnumerationOutcome { + items, + mode: EnumerationMode::Full, + }) +} + +fn sync_fallback_codes(code: &ResponseCode) -> bool { + matches!( + code, + ResponseCode::InvalidIdMalformed | ResponseCode::AccessDenied | ResponseCode::Other(_) + ) +} + +fn try_sync_folder_items( + ctx: &ItemRunCtx<'_>, + folder: &FolderId, + prior_state: &str, +) -> Result, EwsError> { + let mut sync_state = prior_state.to_owned(); + let mut items: Vec = Vec::new(); + let mut deletions: Vec = Vec::new(); + let mut iters = 0; + loop { + let body = sync_folder_items_body(folder, &sync_state, 512); + let resp = match ctx.client.call(ctx.url, "SyncFolderItems", &body) { + Ok(r) => r, + Err(EwsError::SoapFault { + code: ResponseCode::InvalidSyncStateData, + .. + }) => { + return Ok(None); + } + Err(EwsError::SoapFault { code, .. }) if sync_fallback_codes(&code) => { + return Ok(None); + } + Err(e) => return Err(e), + }; + let parsed = parse_sync_folder_items_response(&resp.body)?; + for change in parsed.changes { + match change { + SyncChange::Create { id, element } | SyncChange::Update { id, element } => { + items.push(EnumeratedItem { element, id }); + } + SyncChange::Delete { id } => { + deletions.push(id.id); + } + SyncChange::ReadFlagChange { id, .. } => { + items.push(EnumeratedItem { + element: "Message".to_owned(), + id, + }); + } + } + } + sync_state = parsed.sync_state; + if !parsed.more { + break; + } + iters += 1; + if iters > 200 { + break; + } + } + Ok(Some(EnumerationOutcome { + items, + mode: EnumerationMode::Delta { + deletions, + new_sync_state: sync_state, + }, + })) +} + +fn enumerate_via_find_item( + ctx: &ItemRunCtx<'_>, + folder: &FolderId, +) -> Result, EwsError> { + let mut items: Vec = Vec::new(); + let mut offset: u32 = 0; + let page_size: u32 = 500; + loop { + let body = find_item_body( + FolderRef::Concrete(folder), + Traversal::Shallow, + offset, + page_size, + ); + let resp = ctx.client.call(ctx.url, "FindItem", &body)?; + let parsed = parse_find_item_response(&resp.body)?; + let returned = parsed.items.len() as u32; + for entry in parsed.items { + items.push(EnumeratedItem { + element: entry.element, + id: entry.id, + }); + } + if !parsed.more { + break; + } + if returned == 0 { + break; + } + offset = offset.saturating_add(page_size); + if offset > 1_000_000 { + break; + } + } + Ok(items) +} + +#[derive(Debug, Clone)] +pub struct DiffPlan { + pub new: Vec, + pub vanished: Vec<(String, i64)>, + pub present_changed: Vec<(ItemId, i64)>, + pub present_unchanged: Vec<(String, i64)>, +} + +pub fn plan_for( + outcome: &EnumerationOutcome, + local: &[crate::db::exchange_ews_ids::ItemRow], +) -> DiffPlan { + match &outcome.mode { + EnumerationMode::Full => diff(&outcome.items, local), + EnumerationMode::Delta { deletions, .. } => diff_delta(&outcome.items, deletions, local), + } +} + +fn diff_delta( + server_changes: &[EnumeratedItem], + deletions: &[String], + local: &[crate::db::exchange_ews_ids::ItemRow], +) -> DiffPlan { + let local_map: HashMap<&str, &crate::db::exchange_ews_ids::ItemRow> = + local.iter().map(|r| (r.item_id.as_str(), r)).collect(); + let mut plan = DiffPlan { + new: Vec::new(), + vanished: Vec::new(), + present_changed: Vec::new(), + present_unchanged: Vec::new(), + }; + for s in server_changes { + match local_map.get(s.id.id.as_str()) { + None => plan.new.push(s.id.clone()), + Some(row) => plan.present_changed.push((s.id.clone(), row.local_id)), + } + } + for d in deletions { + if let Some(row) = local_map.get(d.as_str()) { + plan.vanished.push((d.clone(), row.local_id)); + } + } + plan +} + +pub fn diff(server: &[EnumeratedItem], local: &[crate::db::exchange_ews_ids::ItemRow]) -> DiffPlan { + let local_map: HashMap<&str, &crate::db::exchange_ews_ids::ItemRow> = + local.iter().map(|r| (r.item_id.as_str(), r)).collect(); + let server_map: HashMap<&str, &EnumeratedItem> = + server.iter().map(|s| (s.id.id.as_str(), s)).collect(); + let mut plan = DiffPlan { + new: Vec::new(), + vanished: Vec::new(), + present_changed: Vec::new(), + present_unchanged: Vec::new(), + }; + for s in server { + match local_map.get(s.id.id.as_str()) { + None => plan.new.push(s.id.clone()), + Some(row) => { + if s.id.change_key.is_empty() || s.id.change_key == row.change_key { + plan.present_unchanged + .push((row.item_id.clone(), row.local_id)); + } else { + plan.present_changed.push((s.id.clone(), row.local_id)); + } + } + } + } + for row in local { + if !server_map.contains_key(row.item_id.as_str()) { + plan.vanished.push((row.item_id.clone(), row.local_id)); + } + } + plan +} + +pub struct GetItemBatchOutcome { + pub messages: Vec, + pub failed_items: u64, +} + +fn is_per_batch_fault(err: &EwsError) -> bool { + matches!( + err, + EwsError::SoapFault { .. } | EwsError::HttpStatus { .. } | EwsError::Malformed(_) + ) +} + +pub fn get_items( + ctx: &ItemRunCtx<'_>, + shape: ItemShape, + ids: &[ItemId], +) -> Result { + let batch = ctx.batch_size.max(1); + let workers = ctx.connections.clamp(1, 8); + let mut failed_items: u64 = 0; + if workers <= 1 || ids.len() <= batch { + let mut all = Vec::new(); + for chunk in ids.chunks(batch) { + let body = get_item_body(shape, chunk); + match ctx.client.call(ctx.url, "GetItem", &body) { + Ok(resp) => match parse_response_messages(&resp.body, b"GetItemResponseMessage") { + Ok(mut msgs) => all.append(&mut msgs), + Err(e) if is_per_batch_fault(&e) => { + ctx.logger.warn(&format!( + "GetItem batch parse failed ({e}); {} ids left for retry next run", + chunk.len() + )); + failed_items += chunk.len() as u64; + } + Err(e) => return Err(e), + }, + Err(e) if is_per_batch_fault(&e) => { + ctx.logger.warn(&format!( + "GetItem batch failed ({e}); {} ids left for retry next run", + chunk.len() + )); + failed_items += chunk.len() as u64; + } + Err(e) => return Err(e), + } + } + return Ok(GetItemBatchOutcome { + messages: all, + failed_items, + }); + } + let client = ctx.client.clone(); + let url = ctx.url.to_owned(); + type BatchResult = ( + usize, + Result, EwsError>, + ); + let pool: crate::sync::import_jmap::pool::Pool, BatchResult> = + crate::sync::import_jmap::pool::Pool::new(workers, move |chunk: Vec| { + let body = get_item_body(shape, &chunk); + let n = chunk.len(); + let result = match client.call(&url, "GetItem", &body) { + Ok(resp) => parse_response_messages(&resp.body, b"GetItemResponseMessage"), + Err(e) => Err(e), + }; + (n, result) + }); + let mut submitted = 0usize; + for chunk in ids.chunks(batch) { + pool.submit(chunk.to_vec()); + submitted += 1; + } + let mut all = Vec::new(); + let mut abort_err: Option = None; + for _ in 0..submitted { + match pool.results().recv() { + Ok((_, Ok(mut msgs))) => all.append(&mut msgs), + Ok((n, Err(e))) => { + if is_per_batch_fault(&e) { + ctx.logger.warn(&format!( + "GetItem batch failed ({e}); {n} ids left for retry next run" + )); + failed_items += n as u64; + } else if abort_err.is_none() { + abort_err = Some(e); + } + } + Err(_) => break, + } + } + if let Some(e) = abort_err { + return Err(e); + } + Ok(GetItemBatchOutcome { + messages: all, + failed_items, + }) +} + +pub fn delete_vanished( + conn: &mut rusqlite::Connection, + source_id: i64, + type_name: &str, + table: &str, + vanished: &[(String, i64)], + counts: &mut crate::sync::TypeCounts, +) -> Result<(), Error> { + use rusqlite::params; + for (item_id, local_id) in vanished { + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + match tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + params![local_id], + ) { + Ok(_) => { + crate::db::exchange_ews_ids::delete_item(&tx, source_id, type_name, item_id) + .map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + counts.deleted += 1; + } + Err(_) => { + let _ = tx.rollback(); + counts.failed += 1; + } + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn diff_delta_only_yields_changes_from_server_response() { + use crate::db::exchange_ews_ids::ItemRow; + let outcome = EnumerationOutcome { + items: vec![EnumeratedItem { + element: "Message".to_owned(), + id: ItemId::new("A", "ck-2"), + }], + mode: EnumerationMode::Delta { + deletions: vec!["Z".to_owned()], + new_sync_state: "STATE2".to_owned(), + }, + }; + let local = vec![ + ItemRow { + item_id: "A".to_owned(), + change_key: "ck-1".to_owned(), + local_id: 1, + }, + ItemRow { + item_id: "B".to_owned(), + change_key: "ck-1".to_owned(), + local_id: 2, + }, + ItemRow { + item_id: "Z".to_owned(), + change_key: "ck-9".to_owned(), + local_id: 99, + }, + ]; + let plan = plan_for(&outcome, &local); + assert!(plan.new.is_empty()); + assert_eq!(plan.present_changed.len(), 1); + assert_eq!(plan.present_changed[0].0.id, "A"); + assert_eq!(plan.vanished.len(), 1); + assert_eq!(plan.vanished[0].0, "Z"); + assert_eq!(plan.present_unchanged.len(), 0); + } + + #[test] + fn diff_splits_into_new_unchanged_changed_vanished() { + use crate::db::exchange_ews_ids::ItemRow; + let server = vec![ + EnumeratedItem { + element: "Message".to_owned(), + id: ItemId::new("A", "ck-1"), + }, + EnumeratedItem { + element: "Message".to_owned(), + id: ItemId::new("B", "ck-1"), + }, + EnumeratedItem { + element: "Message".to_owned(), + id: ItemId::new("C", "ck-2"), + }, + ]; + let local = vec![ + ItemRow { + item_id: "A".to_owned(), + change_key: "ck-1".to_owned(), + local_id: 1, + }, + ItemRow { + item_id: "B".to_owned(), + change_key: "ck-0".to_owned(), + local_id: 2, + }, + ItemRow { + item_id: "Z".to_owned(), + change_key: "ck-9".to_owned(), + local_id: 99, + }, + ]; + let plan = diff(&server, &local); + assert_eq!(plan.new.len(), 1); + assert_eq!(plan.new[0].id, "C"); + assert_eq!(plan.present_unchanged.len(), 1); + assert_eq!(plan.present_unchanged[0].0, "A"); + assert_eq!(plan.present_changed.len(), 1); + assert_eq!(plan.present_changed[0].0.id, "B"); + assert_eq!(plan.vanished.len(), 1); + assert_eq!(plan.vanished[0].0, "Z"); + } +} diff --git a/src/sync/import_exchange_ews/messages.rs b/src/sync/import_exchange_ews/messages.rs new file mode 100644 index 0000000..247c2be --- /dev/null +++ b/src/sync/import_exchange_ews/messages.rs @@ -0,0 +1,262 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use rusqlite::{Connection, params}; +use serde_json::{Value, json}; + +use crate::db::{blobs, exchange_ews_ids}; +use crate::error::Error; +use crate::exchange_ews::parse::{MessageItem, parse_message_item}; +use crate::exchange_ews::types::ItemId; +use crate::exchange_ews::xml::ItemShape; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; +use crate::sync::emailmeta::email_meta_from_blob; +use crate::sync::keys::index_to_json; + +use super::folders::FolderPlan; +use super::items::{ + EnumerationMode, ItemRunCtx, delete_vanished, enumerate_folder, get_items, plan_for, +}; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + plan: &FolderPlan, + counts: &mut TypeCounts, +) -> Result<(), Error> { + for folder in &plan.mail { + let folder_id = &folder.folder.folder_id; + let local_folder_id = match exchange_ews_ids::local_for_item( + conn, + ctx.source_id, + exchange_ews_ids::MAILBOX, + &folder_id.id, + ) + .map_err(|e| Error::Partial(e.to_string()))? + { + Some(id) => id, + None => continue, + }; + if let Err(e) = reconcile_one_folder(conn, ctx, folder_id, local_folder_id, counts) { + ctx.logger + .warn(&format!("email folder {} failed: {}", folder_id.id, e)); + counts.failed += 1; + } + } + Ok(()) +} + +fn reconcile_one_folder( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + folder: &crate::exchange_ews::types::FolderId, + local_folder_id: i64, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let prior = exchange_ews_ids::get_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::MAILBOX, + &folder.id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let outcome = enumerate_folder(ctx, folder, prior.as_deref()).map_err(Error::from)?; + let local = + exchange_ews_ids::items_in_folder(conn, ctx.source_id, exchange_ews_ids::EMAIL, &folder.id) + .map_err(|e| Error::Partial(e.to_string()))?; + let plan = plan_for(&outcome, &local); + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "EWS folder {}: new={} changed={} vanished={} unchanged={}", + folder.id, + plan.new.len(), + plan.present_changed.len(), + plan.vanished.len(), + plan.present_unchanged.len() + ); + } + let mut to_fetch: Vec = plan.new.clone(); + for (id, _local_id) in &plan.present_changed { + to_fetch.push(id.clone()); + } + if !to_fetch.is_empty() { + let outcome = get_items(ctx, ItemShape::Message, &to_fetch).map_err(Error::from)?; + counts.failed += outcome.failed_items; + for msg in outcome.messages { + if !msg.success { + if matches!( + msg.response_code, + crate::exchange_ews::types::ResponseCode::ItemNotFound + ) { + counts.skipped += 1; + } else { + counts.failed += 1; + ctx.logger.warn(&format!( + "GetItem (message) error: {} {}", + msg.response_code, msg.message_text + )); + } + continue; + } + let parsed = parse_message_item(&msg.inner_xml).map_err(Error::from)?; + if parsed.id.id.is_empty() { + counts.failed += 1; + continue; + } + let existing = plan + .present_changed + .iter() + .find(|(id, _)| id.id == parsed.id.id) + .map(|(_, local)| *local); + apply_message( + conn, + ctx, + &parsed, + local_folder_id, + &folder.id, + existing, + counts, + )?; + } + } + delete_vanished( + conn, + ctx.source_id, + exchange_ews_ids::EMAIL, + "emails", + &plan.vanished, + counts, + )?; + if let EnumerationMode::Delta { new_sync_state, .. } = &outcome.mode { + exchange_ews_ids::set_sync_state( + conn, + ctx.source_id, + exchange_ews_ids::MAILBOX, + &folder.id, + new_sync_state, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn apply_message( + conn: &mut Connection, + ctx: &ItemRunCtx<'_>, + item: &MessageItem, + local_folder_id: i64, + folder_id: &str, + existing_local_id: Option, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let mime_b64 = match item.mime_content.as_ref() { + Some(s) => s.replace(['\n', '\r', ' ', '\t'], ""), + None => { + counts.skipped += 1; + ctx.logger.warn(&format!( + "message {} has no MimeContent; skipping", + item.id.id + )); + return Ok(()); + } + }; + let bytes = match STANDARD.decode(mime_b64.as_bytes()) { + Ok(b) => b, + Err(e) => { + counts.failed += 1; + ctx.logger.warn(&format!( + "message {} MimeContent base64 decode failed: {e}", + item.id.id + )); + return Ok(()); + } + }; + let (idx, _date_header) = email_meta_from_blob(&bytes); + let message_match = index_to_json(&idx); + let received_at = item + .date_time_received + .clone() + .unwrap_or_else(|| "1970-01-01T00:00:00Z".to_owned()); + let keywords = keyword_array(item); + let mailbox_ids = json!([local_folder_id]).to_string(); + + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let blob_id = blobs::intern_blob(&tx, &bytes).map_err(|e| Error::Partial(e.to_string()))?; + match existing_local_id { + Some(id) => { + tx.execute( + "UPDATE emails SET blob_id = ?1, received_at = ?2, mailbox_ids = ?3, \ + keywords = ?4, message_match = ?5 WHERE id = ?6", + params![ + blob_id, + received_at, + mailbox_ids, + keywords, + message_match, + id + ], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + exchange_ews_ids::update_change_key( + &tx, + ctx.source_id, + exchange_ews_ids::EMAIL, + &item.id.id, + &item.id.change_key, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.fetched += 1; + } + None => { + tx.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) \ + VALUES (?1, ?2, ?3, ?4, ?5)", + params![blob_id, received_at, mailbox_ids, keywords, message_match], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + let new_id = tx.last_insert_rowid(); + exchange_ews_ids::insert( + &tx, + ctx.source_id, + exchange_ews_ids::EMAIL, + folder_id, + &item.id.id, + &item.id.change_key, + new_id, + ) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.created += 1; + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + Ok(()) +} + +fn keyword_array(item: &MessageItem) -> String { + let mut kws: Vec = Vec::new(); + if matches!(item.is_read, Some(true)) { + kws.push("$seen".to_owned()); + } + if matches!(item.is_draft, Some(true)) { + kws.push("$draft".to_owned()); + } + if matches!(item.is_read_receipt_requested, Some(true)) { + kws.push("$notified".to_owned()); + } + if matches!(item.flag_status.as_deref(), Some("Flagged")) { + kws.push("$flagged".to_owned()); + } + for cat in &item.categories { + kws.push(cat.to_ascii_lowercase()); + } + let value: Value = Value::Array(kws.into_iter().map(Value::String).collect()); + value.to_string() +} diff --git a/src/sync/import_exchange_graph.rs b/src/sync/import_exchange_graph.rs new file mode 100644 index 0000000..17c3c05 --- /dev/null +++ b/src/sync/import_exchange_graph.rs @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod calendar; +pub mod contacts; +pub mod coordinator; +pub mod folders; +pub mod messages; + +pub use coordinator::{GraphAuth, GraphImportConfig, run}; diff --git a/src/sync/import_exchange_graph/calendar.rs b/src/sync/import_exchange_graph/calendar.rs new file mode 100644 index 0000000..02f0923 --- /dev/null +++ b/src/sync/import_exchange_graph/calendar.rs @@ -0,0 +1,443 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, Transaction, params}; +use serde_json::{Value, json}; + +use crate::db::exchange_graph_ids; +use crate::error::Error; +use crate::exchange_graph::api::{self, PREFER_BODY_HTML, PREFER_BODY_TEXT, PREFER_TIMEZONE_UTC}; +use crate::exchange_graph::calendar_map::{ + ConvertedEvent, EventType, classify_event_type, convert_event, +}; +use crate::exchange_graph::error::GraphError; +use crate::exchange_graph::types::EventBodyFormat; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; +use crate::sync::import_jmap::pool::Pool; + +use super::coordinator::{CHUNK_SIZE, GraphCoordinator}; +use super::folders::CalendarFolder; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + calendars: &[CalendarFolder], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let local: HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::CALENDAR_EVENT)?; + let mut server_total: std::collections::HashSet = std::collections::HashSet::new(); + let mut any_failure = false; + + for cal in calendars { + let url = ctx.endpoints.calendar_events_ids(&cal.graph_id, ctx.top); + let stubs = match api::collect_all_values(ctx.client, &url, &[PREFER_TIMEZONE_UTC]) { + Ok(v) => v, + Err(e) => { + ctx.logger + .warn(&format!("calendar {} stub fetch failed: {e}", cal.graph_id)); + counts.failed += 1; + any_failure = true; + continue; + } + }; + let mut want_ids: Vec = Vec::new(); + let mut occurrence_count = 0usize; + for stub in &stubs { + match classify_event_type(stub) { + EventType::Occurrence => occurrence_count += 1, + _ => { + if let Some(id) = stub.get("id").and_then(Value::as_str) { + server_total.insert(id.to_owned()); + if !local.contains_key(id) { + want_ids.push(id.to_owned()); + } + } + } + } + } + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "graph calendar {} events: stubs={} new={} occurrences_skipped={}", + cal.graph_id, + stubs.len(), + want_ids.len(), + occurrence_count + ); + } + if want_ids.is_empty() { + continue; + } + let fetched = fetch_events(ctx, &want_ids); + let mut masters: Vec<(String, ConvertedEvent)> = Vec::new(); + let mut exceptions: Vec<(String, ConvertedEvent)> = Vec::new(); + for (graph_id, result) in fetched { + match result { + Ok(raw) => match convert_event(&raw, None) { + Ok(c) => match c.event_type { + EventType::Exception => exceptions.push((graph_id, c)), + _ => masters.push((graph_id, c)), + }, + Err(e) => { + counts.failed += 1; + ctx.logger + .warn(&format!("graph event {graph_id} convert failed: {e}")); + } + }, + Err(GraphError::Vanished) => counts.skipped += 1, + Err(e) => { + counts.failed += 1; + ctx.logger + .warn(&format!("graph event {graph_id} fetch failed: {e}")); + } + } + } + + let mut master_by_graph_id: HashMap = masters.into_iter().collect(); + for (ex_graph_id, ex) in exceptions { + let Some(master_graph_id) = ex.series_master_id.clone() else { + ctx.logger.warn(&format!( + "graph exception event {ex_graph_id} has no seriesMasterId; dropping" + )); + counts.skipped += 1; + continue; + }; + if let Some(master) = master_by_graph_id.get_mut(&master_graph_id) { + merge_exception_into(master, &ex); + } else if local.contains_key(&master_graph_id) { + merge_exception_into_existing(conn, ctx, &master_graph_id, &ex, counts); + } else { + counts.skipped += 1; + ctx.logger.warn(&format!( + "graph exception {ex_graph_id} references missing master {master_graph_id}; orphaned" + )); + } + } + + let pairs: Vec<(String, ConvertedEvent)> = master_by_graph_id.into_iter().collect(); + insert_events_chunked(conn, ctx, cal.local_id, &pairs, counts)?; + } + + if any_failure { + ctx.logger.warn( + "graph event vanished-cleanup skipped: one or more calendars failed to enumerate; \ + a clean re-run will reconcile deletions", + ); + } else { + delete_vanished(conn, ctx.source_id, &local, &server_total, counts)?; + } + Ok(()) +} + +fn fetch_events( + ctx: &GraphCoordinator<'_>, + ids: &[String], +) -> Vec<(String, Result)> { + type R = (String, Result); + let client = ctx.client.clone(); + let endpoints: crate::exchange_graph::api::Endpoints = (*ctx.endpoints).clone(); + let body_prefer = match ctx.event_body_format { + EventBodyFormat::Text => PREFER_BODY_TEXT, + EventBodyFormat::Html => PREFER_BODY_HTML, + }; + let prefer: Vec = vec![PREFER_TIMEZONE_UTC.to_owned(), body_prefer.to_owned()]; + let pool: Pool = Pool::new(ctx.workers, move |id: String| { + let url = endpoints.event(&id); + let prefer_refs: Vec<&str> = prefer.iter().map(String::as_str).collect(); + let result = client.get_json_with_prefer(&url, &prefer_refs); + (id, result) + }); + for id in ids { + pool.submit(id.clone()); + } + let mut out = Vec::with_capacity(ids.len()); + for _ in 0..ids.len() { + if let Ok(r) = pool.results().recv() { + out.push(r); + } + } + out +} + +fn merge_exception_into(master: &mut ConvertedEvent, ex: &ConvertedEvent) { + let Some(raw_key) = ex.original_start.as_deref() else { + return; + }; + let master_tz = master + .data + .get("timeZone") + .and_then(Value::as_str) + .map(str::to_owned); + let key = normalise_override_key(raw_key, master_tz.as_deref()); + let Value::Object(map) = &mut master.data else { + return; + }; + let overrides = map + .entry("recurrenceOverrides".to_owned()) + .or_insert_with(|| Value::Object(serde_json::Map::new())); + let Value::Object(overrides) = overrides else { + return; + }; + overrides.insert(key, ex.data.clone()); +} + +fn merge_exception_into_existing( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + master_graph_id: &str, + ex: &ConvertedEvent, + counts: &mut TypeCounts, +) { + let local_id = match exchange_graph_ids::local_for_graph_id( + conn, + ctx.source_id, + exchange_graph_ids::CALENDAR_EVENT, + master_graph_id, + ) { + Ok(Some(id)) => id, + Ok(None) => { + counts.skipped += 1; + return; + } + Err(e) => { + counts.failed += 1; + ctx.logger.warn(&format!( + "graph exception merge: lookup of master {master_graph_id} failed: {e}" + )); + return; + } + }; + let Some(raw_key) = ex.original_start.as_deref() else { + counts.skipped += 1; + return; + }; + if let Err(e) = merge_persisted_master(conn, local_id, raw_key, &ex.data) { + counts.failed += 1; + ctx.logger.warn(&format!( + "graph exception merge into stored master {master_graph_id} failed: {e}" + )); + } else { + counts.fetched += 1; + } +} + +fn merge_persisted_master( + conn: &Connection, + local_id: i64, + raw_key: &str, + ex_data: &Value, +) -> Result<(), String> { + let tx = conn.unchecked_transaction().map_err(|e| e.to_string())?; + let row: String = tx + .query_row( + "SELECT data FROM calendar_events WHERE id = ?1", + params![local_id], + |row| row.get(0), + ) + .map_err(|e| e.to_string())?; + let mut data: Value = serde_json::from_str(&row).map_err(|e| e.to_string())?; + let master_tz = data + .get("timeZone") + .and_then(Value::as_str) + .map(str::to_owned); + let key = normalise_override_key(raw_key, master_tz.as_deref()); + if let Value::Object(map) = &mut data { + let entry = map + .entry("recurrenceOverrides".to_owned()) + .or_insert_with(|| Value::Object(serde_json::Map::new())); + if let Value::Object(overrides) = entry { + overrides.insert(key, ex_data.clone()); + } + } + tx.execute( + "UPDATE calendar_events SET data = ?1 WHERE id = ?2", + params![data.to_string(), local_id], + ) + .map_err(|e| e.to_string())?; + tx.commit().map_err(|e| e.to_string())?; + Ok(()) +} + +pub fn normalise_override_key(raw: &str, master_tz: Option<&str>) -> String { + if let Some(tz_name) = master_tz + && let Some(local) = utc_offset_to_local(raw, tz_name) + { + return local; + } + strip_offset_and_fractional(raw) +} + +fn utc_offset_to_local(raw: &str, tz_name: &str) -> Option { + use chrono::{DateTime, NaiveDateTime, TimeZone}; + use chrono_tz::Tz; + let tz: Tz = tz_name.parse().ok()?; + let parsed: DateTime = if let Ok(dt) = DateTime::parse_from_rfc3339(raw) { + dt + } else if let Ok(naive) = NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f") { + return Some(format_local(naive)); + } else if let Ok(naive) = NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S") { + return Some(format_local(naive)); + } else { + return None; + }; + let local = tz.from_utc_datetime(&parsed.naive_utc()).naive_local(); + Some(format_local(local)) +} + +fn format_local(dt: chrono::NaiveDateTime) -> String { + dt.format("%Y-%m-%dT%H:%M:%S").to_string() +} + +fn strip_offset_and_fractional(raw: &str) -> String { + let no_offset = if let Some(idx) = raw.find('Z') { + &raw[..idx] + } else if let Some(idx) = raw.find('+') { + &raw[..idx] + } else if let Some(idx) = raw.rfind('-') + && idx > 10 + { + &raw[..idx] + } else { + raw + }; + let trimmed = no_offset.split('.').next().unwrap_or(no_offset); + if trimmed.contains('T') { + trimmed.to_owned() + } else { + format!("{trimmed}T00:00:00") + } +} + +fn insert_events_chunked( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + calendar_local_id: i64, + pairs: &[(String, ConvertedEvent)], + counts: &mut TypeCounts, +) -> Result<(), Error> { + for chunk in pairs.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, event) in chunk { + insert_event_in_tx(&tx, ctx, calendar_local_id, graph_id, event, counts)?; + } + tx.commit()?; + } + Ok(()) +} + +fn insert_event_in_tx( + tx: &Transaction<'_>, + ctx: &GraphCoordinator<'_>, + calendar_local_id: i64, + graph_id: &str, + event: &ConvertedEvent, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let calendar_ids = json!([calendar_local_id]).to_string(); + let data = event.data.to_string(); + tx.execute( + "INSERT INTO calendar_events (calendar_ids, is_draft, use_default_alerts, data, data_type) + VALUES (?1, ?2, ?3, ?4, 'Event')", + params![ + calendar_ids, + event.is_draft as i64, + event.use_default_alerts as i64, + data, + ], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + tx, + ctx.source_id, + exchange_graph_ids::CALENDAR_EVENT, + graph_id, + new_id, + )?; + counts.created += 1; + Ok(()) +} + +fn delete_vanished( + conn: &mut Connection, + source_id: i64, + local: &HashMap, + server: &std::collections::HashSet, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let vanished: Vec<(&String, &i64)> = local + .iter() + .filter(|(graph_id, _)| !server.contains(graph_id.as_str())) + .collect(); + for chunk in vanished.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, local_id) in chunk { + let result = tx.execute( + "DELETE FROM calendar_events WHERE id = ?1", + params![local_id], + ); + match result { + Ok(_) => { + exchange_graph_ids::delete( + &tx, + source_id, + exchange_graph_ids::CALENDAR_EVENT, + graph_id, + )?; + counts.deleted += 1; + } + Err(_) => { + counts.failed += 1; + } + } + } + tx.commit()?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn override_key_utc_converts_to_master_local_time() { + let local = normalise_override_key("2026-05-11T15:00:00Z", Some("America/New_York")); + assert_eq!(local, "2026-05-11T11:00:00"); + } + + #[test] + fn override_key_no_tz_strips_z_only() { + let local = normalise_override_key("2026-05-11T15:00:00Z", None); + assert_eq!(local, "2026-05-11T15:00:00"); + } + + #[test] + fn override_key_with_explicit_offset_converts() { + let local = normalise_override_key("2026-05-11T15:00:00+00:00", Some("Europe/London")); + assert_eq!(local, "2026-05-11T16:00:00"); + } + + #[test] + fn override_key_naive_localdatetime_passes_through() { + let local = normalise_override_key("2026-05-11T15:00:00", Some("America/New_York")); + assert_eq!(local, "2026-05-11T15:00:00"); + } + + #[test] + fn override_key_unknown_timezone_strips_offset_only() { + let local = normalise_override_key("2026-05-11T15:00:00Z", Some("Not/A_Zone")); + assert_eq!(local, "2026-05-11T15:00:00"); + } + + #[test] + fn override_key_fractional_seconds_stripped() { + let local = normalise_override_key("2026-05-11T15:00:00.1234567Z", None); + assert_eq!(local, "2026-05-11T15:00:00"); + } +} diff --git a/src/sync/import_exchange_graph/contacts.rs b/src/sync/import_exchange_graph/contacts.rs new file mode 100644 index 0000000..1c46b6e --- /dev/null +++ b/src/sync/import_exchange_graph/contacts.rs @@ -0,0 +1,201 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, Transaction, params}; +use serde_json::{Value, json}; + +use crate::db::exchange_graph_ids; +use crate::error::Error; +use crate::exchange_graph::api; +use crate::exchange_graph::contact_map::{ConvertedContact, convert_contact}; +use crate::exchange_graph::error::GraphError; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; +use crate::sync::import_jmap::pool::Pool; + +use super::coordinator::{CHUNK_SIZE, GraphCoordinator}; +use super::folders::ContactFolder; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + books: &[ContactFolder], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let local: HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::CONTACT_CARD)?; + let mut server_total: std::collections::HashSet = std::collections::HashSet::new(); + let mut any_failure = false; + + for book in books { + let url = ctx + .endpoints + .contact_folder_contacts_ids(&book.graph_id, ctx.top); + let ids = match api::collect_all_ids(ctx.client, &url, &[]) { + Ok(v) => v, + Err(e) => { + ctx.logger.warn(&format!( + "contact folder {} enumeration failed: {e}", + book.graph_id + )); + counts.failed += 1; + any_failure = true; + continue; + } + }; + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "graph contact folder {} contacts: {}", + book.graph_id, + ids.len() + ); + } + for id in &ids { + server_total.insert(id.clone()); + } + let new_ids: Vec = ids + .into_iter() + .filter(|id| !local.contains_key(id)) + .collect(); + if new_ids.is_empty() { + continue; + } + let fetched = fetch_contacts(ctx, &new_ids); + let mut converted: Vec<(String, ConvertedContact)> = Vec::new(); + for (graph_id, result) in fetched { + match result { + Ok(raw) => match convert_contact(&raw) { + Ok(c) => converted.push((graph_id, c)), + Err(e) => { + counts.failed += 1; + ctx.logger + .warn(&format!("graph contact {graph_id} convert failed: {e}")); + } + }, + Err(GraphError::Vanished) => counts.skipped += 1, + Err(e) => { + counts.failed += 1; + ctx.logger + .warn(&format!("graph contact {graph_id} fetch failed: {e}")); + } + } + } + insert_contacts_chunked(conn, ctx, book.local_id, &converted, counts)?; + } + + if any_failure { + ctx.logger.warn( + "graph contact vanished-cleanup skipped: one or more contact folders failed to enumerate; \ + a clean re-run will reconcile deletions", + ); + } else { + delete_vanished(conn, ctx.source_id, &local, &server_total, counts)?; + } + Ok(()) +} + +fn fetch_contacts( + ctx: &GraphCoordinator<'_>, + ids: &[String], +) -> Vec<(String, Result)> { + type R = (String, Result); + let client = ctx.client.clone(); + let endpoints: crate::exchange_graph::api::Endpoints = (*ctx.endpoints).clone(); + let pool: Pool = Pool::new(ctx.workers, move |id: String| { + let url = endpoints.contact(&id); + (id, client.get_json_with_prefer(&url, &[])) + }); + for id in ids { + pool.submit(id.clone()); + } + let mut out = Vec::with_capacity(ids.len()); + for _ in 0..ids.len() { + if let Ok(r) = pool.results().recv() { + out.push(r); + } + } + out +} + +fn insert_contacts_chunked( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + book_local_id: i64, + pairs: &[(String, ConvertedContact)], + counts: &mut TypeCounts, +) -> Result<(), Error> { + for chunk in pairs.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, contact) in chunk { + insert_contact_in_tx(&tx, ctx, book_local_id, graph_id, contact, counts)?; + } + tx.commit()?; + } + Ok(()) +} + +fn insert_contact_in_tx( + tx: &Transaction<'_>, + ctx: &GraphCoordinator<'_>, + book_local_id: i64, + graph_id: &str, + converted: &ConvertedContact, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let book_ids = json!([book_local_id]).to_string(); + let data = converted.data.to_string(); + tx.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![converted.uid, book_ids, data], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + tx, + ctx.source_id, + exchange_graph_ids::CONTACT_CARD, + graph_id, + new_id, + )?; + counts.created += 1; + Ok(()) +} + +fn delete_vanished( + conn: &mut Connection, + source_id: i64, + local: &HashMap, + server: &std::collections::HashSet, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let vanished: Vec<(&String, &i64)> = local + .iter() + .filter(|(graph_id, _)| !server.contains(graph_id.as_str())) + .collect(); + for chunk in vanished.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, local_id) in chunk { + let result = tx.execute("DELETE FROM contact_cards WHERE id = ?1", params![local_id]); + match result { + Ok(_) => { + exchange_graph_ids::delete( + &tx, + source_id, + exchange_graph_ids::CONTACT_CARD, + graph_id, + )?; + counts.deleted += 1; + } + Err(_) => { + counts.failed += 1; + } + } + } + tx.commit()?; + } + Ok(()) +} diff --git a/src/sync/import_exchange_graph/coordinator.rs b/src/sync/import_exchange_graph/coordinator.rs new file mode 100644 index 0000000..f242087 --- /dev/null +++ b/src/sync/import_exchange_graph/coordinator.rs @@ -0,0 +1,531 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::Connection; +use serde_json::Value; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::exchange_graph::api::{DEFAULT_API_BASE, Endpoints}; +use crate::exchange_graph::client::GraphClient; +use crate::exchange_graph::error::GraphError; +use crate::exchange_graph::oauth::{ + AcquiredToken, OAuthFlow, acquire, default_authority, refresh_access_token, +}; +use crate::exchange_graph::types::{EventBodyFormat, MailboxKind, synthetic_account_id}; +use crate::jmap::http::RetryPolicy; +use crate::logging::LEVEL_DEFAULT; +use crate::sync::{CommonConfig, Summary, TypeCounts}; +use crate::types::ObjectType; + +#[derive(Debug, Clone)] +pub enum GraphAuth { + PreAcquired { + token: String, + }, + DeviceCode { + authority: String, + client_id: String, + }, +} + +#[derive(Debug, Clone)] +pub struct GraphImportConfig { + pub auth: GraphAuth, + pub api_base: String, + pub user_target: Option, + pub mailbox_kind: MailboxKind, + pub objects: Option>, + pub event_body_format: EventBodyFormat, + pub graph_connections: usize, + pub top: usize, + pub allow_source_change: bool, +} + +pub const CHUNK_SIZE: usize = 100; + +pub struct GraphCoordinator<'a> { + pub client: &'a GraphClient, + pub endpoints: &'a Endpoints, + pub source_id: i64, + pub top: usize, + pub workers: usize, + pub logger: crate::logging::Logger, + pub event_body_format: EventBodyFormat, +} + +pub fn run(common: CommonConfig, config: GraphImportConfig) -> Result { + let logger = common.logger; + let mut conn = db::init::open(&common.archive)?; + + let acquired = acquire_with_flow(&config.auth, common.allow_invalid_certs)?; + let client = GraphClient::new( + acquired.access_token.clone(), + RetryPolicy::new(common.max_retries), + common.allow_invalid_certs, + ); + client.set_logger(logger); + + let endpoints = resolve_endpoints(&config, &client)?; + let principal = resolve_principal(&client, &endpoints, acquired.upn.as_deref())?; + + let session_url = canonical_session_url(&config); + let account_id = synthetic_account_id(&principal.id, config.mailbox_kind); + + if !common.dry_run + && let Some((url, acc)) = + db::sources::conflicting_source(&conn, "exchange_graph", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records exchange_graph source ({url}, account {acc}); \ + pass --allow-source-change to import a different account" + ))); + } + + if matches!(config.mailbox_kind, MailboxKind::Archive) { + logger.warn( + "--mailbox-kind archive: Microsoft Online Archive holds only mail; \ + calendar and contact surfaces are skipped for this run", + ); + } + + if common.dry_run { + return run_dry( + &client, + &endpoints, + config.mailbox_kind, + &logger, + config.top, + ); + } + + let source_id = db::sources::upsert_source( + &conn, + &SourceKey { + kind: "exchange_graph".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }, + Some(&principal.user_principal_name), + &principal.user_principal_name, + )?; + + let _refresher = spawn_token_refresher( + &client, + &config.auth, + &acquired, + common.allow_invalid_certs, + logger, + ); + + let mut summary = Summary::default(); + let mut mailbox_counts = TypeCounts::default(); + let mut email_counts = TypeCounts::default(); + let mut calendar_counts = TypeCounts::default(); + let mut event_counts = TypeCounts::default(); + let mut addressbook_counts = TypeCounts::default(); + let mut contact_counts = TypeCounts::default(); + + let ctx = GraphCoordinator { + client: &client, + endpoints: &endpoints, + source_id, + top: config.top.clamp(1, 1000), + workers: config.graph_connections.clamp(1, 16), + logger, + event_body_format: config.event_body_format, + }; + + let want_mail = config + .objects + .as_ref() + .map(|set| { + set.iter() + .any(|o| matches!(o, ObjectType::Mailbox | ObjectType::Email)) + }) + .unwrap_or(true); + let want_calendar = config + .objects + .as_ref() + .map(|set| { + set.iter() + .any(|o| matches!(o, ObjectType::Calendar | ObjectType::CalendarEvent)) + }) + .unwrap_or(true) + && !matches!(config.mailbox_kind, MailboxKind::Archive); + let want_contacts = config + .objects + .as_ref() + .map(|set| { + set.iter() + .any(|o| matches!(o, ObjectType::AddressBook | ObjectType::ContactCard)) + }) + .unwrap_or(true) + && !matches!(config.mailbox_kind, MailboxKind::Archive); + + if want_mail { + let folders = super::folders::reconcile_mail( + &mut conn, + &ctx, + config.mailbox_kind, + &mut mailbox_counts, + )?; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "graph mail folders: created={} fetched={} deleted={}", + mailbox_counts.created, mailbox_counts.fetched, mailbox_counts.deleted + ); + } + super::messages::reconcile_all(&mut conn, &ctx, &folders, &mut email_counts)?; + } + + if want_calendar { + let calendars = super::folders::reconcile_calendars(&mut conn, &ctx, &mut calendar_counts)?; + super::calendar::reconcile_all(&mut conn, &ctx, &calendars, &mut event_counts)?; + } + + if want_contacts { + let books = + super::folders::reconcile_address_books(&mut conn, &ctx, &mut addressbook_counts)?; + super::contacts::reconcile_all(&mut conn, &ctx, &books, &mut contact_counts)?; + } + + summary.per_type.push(("mailbox", mailbox_counts)); + summary.per_type.push(("email", email_counts)); + summary.per_type.push(("calendar", calendar_counts)); + summary.per_type.push(("calendarevent", event_counts)); + summary.per_type.push(("addressbook", addressbook_counts)); + summary.per_type.push(("contactcard", contact_counts)); + + if !summary.any_failed() + && let Err(e) = run_gc(&conn) + { + logger.warn(&format!("blob GC skipped: {e}")); + } + + summary.retries_observed = client.retries_observed(); + summary.retry_after_sleeps = client.retry_after_sleeps(); + Ok(summary) +} + +fn run_dry( + client: &GraphClient, + endpoints: &Endpoints, + mailbox_kind: MailboxKind, + logger: &crate::logging::Logger, + top: usize, +) -> Result { + let mut mailbox_counts = TypeCounts::default(); + let mut calendar_counts = TypeCounts::default(); + let mut addressbook_counts = TypeCounts::default(); + let folders = + enumerate_mail_folders(client, endpoints, mailbox_kind, top).map_err(Error::from)?; + mailbox_counts.created = folders.len() as u64; + if !matches!(mailbox_kind, MailboxKind::Archive) { + let calendars = + crate::exchange_graph::api::collect_all_values(client, &endpoints.calendars(top), &[]) + .map_err(Error::from)?; + calendar_counts.created = calendars.len() as u64; + let books = crate::exchange_graph::api::collect_all_values( + client, + &endpoints.contact_folders(top), + &[], + ) + .map_err(Error::from)?; + addressbook_counts.created = books.len() as u64; + } + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "dry-run: mailbox={} calendar={} addressbook={}", + mailbox_counts.created, calendar_counts.created, addressbook_counts.created + ); + } + let mut summary = Summary::default(); + summary.per_type.push(("mailbox", mailbox_counts)); + summary.per_type.push(("calendar", calendar_counts)); + summary.per_type.push(("addressbook", addressbook_counts)); + Ok(summary) +} + +pub fn enumerate_mail_folders( + client: &GraphClient, + endpoints: &Endpoints, + mailbox_kind: MailboxKind, + top: usize, +) -> Result, GraphError> { + let mut all = Vec::new(); + let mut frontier: Vec = Vec::new(); + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + let initial = endpoints.mail_folders_root(mailbox_kind, top); + let level = crate::exchange_graph::api::collect_all_values(client, &initial, &[])?; + for f in &level { + if let Some(id) = f.get("id").and_then(Value::as_str) + && seen.insert(id.to_owned()) + { + frontier.push(id.to_owned()); + } + } + all.extend(level); + while let Some(parent) = frontier.pop() { + let url = endpoints.mail_folder_child_folders(&parent, top); + let children = crate::exchange_graph::api::collect_all_values(client, &url, &[])?; + for c in &children { + if let Some(id) = c.get("id").and_then(Value::as_str) + && seen.insert(id.to_owned()) + { + frontier.push(id.to_owned()); + } + } + all.extend(children); + } + Ok(all) +} + +fn acquire_with_flow(auth: &GraphAuth, allow_invalid_certs: bool) -> Result { + let flow = match auth { + GraphAuth::PreAcquired { token } => OAuthFlow::PreAcquired { + token: token.clone(), + }, + GraphAuth::DeviceCode { + authority, + client_id, + } => OAuthFlow::DeviceCode { + authority: authority.clone(), + client_id: client_id.clone(), + }, + }; + acquire(&flow, allow_invalid_certs).map_err(Error::from) +} + +fn resolve_endpoints(config: &GraphImportConfig, client: &GraphClient) -> Result { + if let Some(target) = config.user_target.as_deref() { + let resolved = if looks_like_uuid(target) { + target.to_owned() + } else { + resolve_user_id(client, &config.api_base, target) + .map_err(|e| Error::Connection(format!("user resolution: {e}")))? + }; + Ok(Endpoints::for_user(&config.api_base, &resolved)) + } else { + Ok(Endpoints::for_me(&config.api_base)) + } +} + +fn looks_like_uuid(s: &str) -> bool { + s.len() == 36 + && s.chars().enumerate().all(|(i, c)| match i { + 8 | 13 | 18 | 23 => c == '-', + _ => c.is_ascii_hexdigit(), + }) +} + +fn resolve_user_id( + client: &GraphClient, + api_base: &str, + upn_or_id: &str, +) -> Result { + let url = format!( + "{}/users/{}?$select=id,userPrincipalName", + api_base.trim_end_matches('/'), + upn_or_id + ); + let body = client.get_json(&url)?; + let id = body + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| GraphError::Malformed("user resolution missing id".to_owned()))? + .to_owned(); + Ok(id) +} + +fn resolve_principal( + client: &GraphClient, + endpoints: &Endpoints, + fallback_upn: Option<&str>, +) -> Result { + let url = endpoints.me_select_id_upn(); + let value = client.get_json(&url).map_err(Error::from)?; + let id = value + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| Error::Connection("graph principal has no id".to_owned()))? + .to_owned(); + let upn = value + .get("userPrincipalName") + .and_then(Value::as_str) + .map(str::to_owned) + .or_else(|| fallback_upn.map(str::to_owned)) + .unwrap_or_default(); + Ok(crate::exchange_graph::ResolvedPrincipal { + id, + user_principal_name: upn, + }) +} + +fn canonical_session_url(config: &GraphImportConfig) -> String { + let authority = match &config.auth { + GraphAuth::DeviceCode { authority, .. } => authority.clone(), + GraphAuth::PreAcquired { .. } => default_authority("common"), + }; + format!( + "{}|{}", + authority.trim_end_matches('/'), + config.api_base.trim_end_matches('/') + ) +} + +fn spawn_token_refresher( + client: &GraphClient, + auth: &GraphAuth, + initial: &AcquiredToken, + allow_invalid_certs: bool, + logger: crate::logging::Logger, +) -> Option { + let (authority, client_id) = match auth { + GraphAuth::DeviceCode { + authority, + client_id, + } => (authority.clone(), client_id.clone()), + GraphAuth::PreAcquired { .. } => return None, + }; + let refresh = initial.refresh_token.clone()?; + let mut deadline_unix = initial_deadline_unix(initial)?; + let client = client.clone(); + let mut refresh_token = refresh; + let shutdown = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let shutdown_signal = shutdown.clone(); + let handle = std::thread::Builder::new() + .name("vandelay-graph-token-refresh".to_owned()) + .spawn(move || { + while !shutdown_signal.load(std::sync::atomic::Ordering::Relaxed) { + let now = unix_now(); + let refresh_at = deadline_unix.saturating_sub(60); + if refresh_at > now { + let wait = std::time::Duration::from_secs(refresh_at - now); + let chunk = std::time::Duration::from_secs(2); + let mut left = wait; + while left > std::time::Duration::ZERO + && !shutdown_signal.load(std::sync::atomic::Ordering::Relaxed) + { + let step = chunk.min(left); + std::thread::sleep(step); + left = left.saturating_sub(step); + } + if shutdown_signal.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + } + match refresh_access_token( + &authority, + &client_id, + &refresh_token, + allow_invalid_certs, + ) { + Ok(tok) => { + client.set_bearer(tok.access_token.clone()); + if let Some(new_refresh) = tok.refresh_token { + refresh_token = new_refresh; + } + deadline_unix = unix_now() + + tok.expires_in.unwrap_or_else(|| { + crate::exchange_graph::oauth::decode_jwt_claims(&tok.access_token) + .and_then(|c| c.exp) + .and_then(|exp| exp.checked_sub(unix_now())) + .unwrap_or(50 * 60) + }); + } + Err(e) => { + logger.warn(&format!( + "graph token refresh failed: {e}; sleeping 60s before retry" + )); + std::thread::sleep(std::time::Duration::from_secs(60)); + } + } + } + }) + .ok()?; + Some(TokenRefresher { + shutdown, + handle: Some(handle), + }) +} + +pub struct TokenRefresher { + shutdown: std::sync::Arc, + handle: Option>, +} + +impl Drop for TokenRefresher { + fn drop(&mut self) { + self.shutdown + .store(true, std::sync::atomic::Ordering::Relaxed); + if let Some(h) = self.handle.take() { + let _ = h.join(); + } + } +} + +fn unix_now() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0) +} + +fn initial_deadline_unix(initial: &AcquiredToken) -> Option { + if let Some(expires_in) = initial.expires_in { + return Some(unix_now() + expires_in); + } + crate::exchange_graph::oauth::decode_jwt_claims(&initial.access_token).and_then(|c| c.exp) +} + +fn run_gc(conn: &Connection) -> Result<(), Error> { + let tx = conn.unchecked_transaction()?; + db::blobs::gc_orphan_blobs(&tx)?; + tx.commit()?; + Ok(()) +} + +pub fn default_api_base() -> String { + DEFAULT_API_BASE.to_owned() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn canonical_session_url_concatenates_authority_and_base() { + let config = GraphImportConfig { + auth: GraphAuth::DeviceCode { + authority: "https://login.microsoftonline.com/common".to_owned(), + client_id: "uuid".to_owned(), + }, + api_base: "https://graph.microsoft.com/v1.0".to_owned(), + user_target: None, + mailbox_kind: MailboxKind::Primary, + objects: None, + event_body_format: EventBodyFormat::Text, + graph_connections: 4, + top: 100, + allow_source_change: false, + }; + let url = canonical_session_url(&config); + assert_eq!( + url, + "https://login.microsoftonline.com/common|https://graph.microsoft.com/v1.0" + ); + } + + #[test] + fn looks_like_uuid_only_matches_well_formed() { + assert!(looks_like_uuid("12345678-1234-1234-1234-123456789abc")); + assert!(!looks_like_uuid("alice@x.com")); + assert!(!looks_like_uuid("12345678123412341234123456789abc")); + } +} diff --git a/src/sync/import_exchange_graph/folders.rs b/src/sync/import_exchange_graph/folders.rs new file mode 100644 index 0000000..b9468b7 --- /dev/null +++ b/src/sync/import_exchange_graph/folders.rs @@ -0,0 +1,531 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; + +use rusqlite::{Connection, params}; +use serde_json::Value; + +use crate::db::exchange_graph_ids; +use crate::error::Error; +use crate::exchange_graph::api; +use crate::exchange_graph::calendar_map::{graph_calendar_color_to_hex, windows_or_iana_to_iana}; +use crate::exchange_graph::types::MailboxKind; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; + +use super::coordinator::{CHUNK_SIZE, GraphCoordinator, enumerate_mail_folders}; + +#[derive(Debug, Clone)] +pub struct MailFolder { + pub graph_id: String, + pub parent_graph_id: Option, + pub display_name: String, + pub is_hidden: bool, + pub local_id: i64, +} + +#[derive(Debug, Clone)] +pub struct CalendarFolder { + pub graph_id: String, + pub local_id: i64, +} + +#[derive(Debug, Clone)] +pub struct ContactFolder { + pub graph_id: String, + pub local_id: i64, +} + +pub fn reconcile_mail( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + mailbox_kind: MailboxKind, + counts: &mut TypeCounts, +) -> Result, Error> { + let server = enumerate_mail_folders(ctx.client, ctx.endpoints, mailbox_kind, ctx.top) + .map_err(Error::from)?; + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!("graph mailFolders enumerated: {}", server.len()); + } + let well_known = match mailbox_kind { + MailboxKind::Primary => resolve_well_known_roles(ctx), + MailboxKind::Archive => HashMap::new(), + }; + let local: HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::MAILBOX)?; + + let entries = order_by_parent(server); + let mut by_id: HashMap = HashMap::new(); + let mut out: Vec = Vec::new(); + let mut server_ids: Vec = Vec::new(); + + for chunk in entries.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for value in chunk { + let Some(graph_id) = value.get("id").and_then(Value::as_str) else { + continue; + }; + let parent_graph_id = value + .get("parentFolderId") + .and_then(Value::as_str) + .map(str::to_owned); + let display_name = value + .get("displayName") + .and_then(Value::as_str) + .unwrap_or("Unnamed") + .to_owned(); + let is_hidden = value + .get("isHidden") + .and_then(Value::as_bool) + .unwrap_or(false); + let role = well_known.get(graph_id).copied(); + let parent_local_id = parent_graph_id + .as_deref() + .and_then(|p| by_id.get(p)) + .copied(); + let existing = local.get(graph_id).copied(); + server_ids.push(graph_id.to_owned()); + + let local_id = if let Some(id) = existing { + tx.execute( + "UPDATE mailboxes SET name = ?1, parent_id = ?2, role = ?3, is_subscribed = ?4 + WHERE id = ?5", + params![display_name, parent_local_id, role, !is_hidden as i64, id,], + )?; + counts.fetched += 1; + id + } else { + tx.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, 0, ?4)", + params![display_name, parent_local_id, role, !is_hidden as i64], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + &tx, + ctx.source_id, + exchange_graph_ids::MAILBOX, + graph_id, + new_id, + )?; + counts.created += 1; + new_id + }; + by_id.insert(graph_id.to_owned(), local_id); + out.push(MailFolder { + graph_id: graph_id.to_owned(), + parent_graph_id, + display_name, + is_hidden, + local_id, + }); + } + tx.commit()?; + } + + delete_vanished_mailboxes( + conn, + ctx.source_id, + &local, + &server_ids, + counts, + &ctx.logger, + )?; + + Ok(out) +} + +pub fn reconcile_calendars( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + counts: &mut TypeCounts, +) -> Result, Error> { + let server = api::collect_all_values(ctx.client, &ctx.endpoints.calendars(ctx.top), &[]) + .map_err(Error::from)?; + let mailbox_tz = mailbox_timezone(ctx); + let local: HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::CALENDAR)?; + let mut out: Vec = Vec::new(); + let mut server_ids: Vec = Vec::new(); + + for chunk in server.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for value in chunk { + let Some(graph_id) = value.get("id").and_then(Value::as_str) else { + continue; + }; + server_ids.push(graph_id.to_owned()); + let name = value + .get("name") + .and_then(Value::as_str) + .unwrap_or("Calendar") + .to_owned(); + let color = value + .get("hexColor") + .and_then(Value::as_str) + .map(str::to_owned) + .or_else(|| { + value + .get("color") + .and_then(Value::as_str) + .and_then(graph_calendar_color_to_hex) + .map(str::to_owned) + }); + let is_default = value + .get("isDefaultCalendar") + .and_then(Value::as_bool) + .unwrap_or(false); + let existing = local.get(graph_id).copied(); + let tz = mailbox_tz.clone(); + let local_id = if let Some(id) = existing { + tx.execute( + "UPDATE calendars SET name = ?1, color = ?2, is_default = ?3, time_zone = ?4 + WHERE id = ?5", + params![name, color, is_default as i64, tz, id], + )?; + counts.fetched += 1; + id + } else { + tx.execute( + "INSERT INTO calendars (name, color, sort_order, is_subscribed, is_visible, + is_default, include_in_availability, time_zone) + VALUES (?1, ?2, 0, 1, 1, ?3, 'all', ?4)", + params![name, color, is_default as i64, tz], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + &tx, + ctx.source_id, + exchange_graph_ids::CALENDAR, + graph_id, + new_id, + )?; + counts.created += 1; + new_id + }; + out.push(CalendarFolder { + graph_id: graph_id.to_owned(), + local_id, + }); + } + tx.commit()?; + } + + delete_vanished_flat( + conn, + ctx.source_id, + exchange_graph_ids::CALENDAR, + "calendars", + &local, + &server_ids, + counts, + )?; + Ok(out) +} + +pub fn reconcile_address_books( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + counts: &mut TypeCounts, +) -> Result, Error> { + let mut server = + api::collect_all_values(ctx.client, &ctx.endpoints.contact_folders(ctx.top), &[]) + .map_err(Error::from)?; + let mut seen: std::collections::HashSet = server + .iter() + .filter_map(|f| f.get("id").and_then(Value::as_str)) + .map(str::to_owned) + .collect(); + let mut frontier: Vec = seen.iter().cloned().collect(); + while let Some(parent) = frontier.pop() { + let url = ctx.endpoints.contact_folder_children(&parent, ctx.top); + let children = api::collect_all_values(ctx.client, &url, &[]).map_err(Error::from)?; + for c in &children { + if let Some(id) = c.get("id").and_then(Value::as_str) + && seen.insert(id.to_owned()) + { + frontier.push(id.to_owned()); + } + } + server.extend(children); + } + + let local: HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::ADDRESS_BOOK)?; + let mut out: Vec = Vec::new(); + let mut server_ids: Vec = Vec::new(); + for chunk in server.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for value in chunk { + let Some(graph_id) = value.get("id").and_then(Value::as_str) else { + continue; + }; + server_ids.push(graph_id.to_owned()); + let name = value + .get("displayName") + .and_then(Value::as_str) + .unwrap_or("Contacts") + .to_owned(); + let existing = local.get(graph_id).copied(); + let local_id = if let Some(id) = existing { + tx.execute( + "UPDATE address_books SET name = ?1 WHERE id = ?2", + params![name, id], + )?; + counts.fetched += 1; + id + } else { + tx.execute( + "INSERT INTO address_books (name, sort_order, is_subscribed) + VALUES (?1, 0, 1)", + params![name], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + &tx, + ctx.source_id, + exchange_graph_ids::ADDRESS_BOOK, + graph_id, + new_id, + )?; + counts.created += 1; + new_id + }; + out.push(ContactFolder { + graph_id: graph_id.to_owned(), + local_id, + }); + } + tx.commit()?; + } + + delete_vanished_flat( + conn, + ctx.source_id, + exchange_graph_ids::ADDRESS_BOOK, + "address_books", + &local, + &server_ids, + counts, + )?; + Ok(out) +} + +fn mailbox_timezone(ctx: &GraphCoordinator<'_>) -> Option { + let url = ctx.endpoints.mailbox_settings_timezone(); + let value = ctx.client.get_json(&url).ok()?; + let tz = value.get("timeZone").and_then(Value::as_str)?; + windows_or_iana_to_iana(tz) +} + +fn resolve_well_known_roles(ctx: &GraphCoordinator<'_>) -> HashMap { + let mapping: &[(&str, &str)] = &[ + ("inbox", "inbox"), + ("drafts", "drafts"), + ("sentitems", "sent"), + ("deleteditems", "trash"), + ("junkemail", "junk"), + ("archive", "archive"), + ]; + let mut out = HashMap::new(); + for (short_name, role) in mapping { + let url = ctx.endpoints.well_known_folder(short_name); + match ctx.client.get_json(&url) { + Ok(value) => { + if let Some(id) = value.get("id").and_then(Value::as_str) { + out.insert(id.to_owned(), *role); + } + } + Err(_) => continue, + } + } + out +} + +fn order_by_parent(server: Vec) -> Vec { + let mut parents: HashMap> = HashMap::new(); + for v in &server { + if let Some(id) = v.get("id").and_then(Value::as_str) { + let parent = v + .get("parentFolderId") + .and_then(Value::as_str) + .map(str::to_owned); + parents.insert(id.to_owned(), parent); + } + } + fn depth_of( + id: &str, + parents: &HashMap>, + memo: &mut HashMap, + seen: &mut std::collections::HashSet, + ) -> usize { + if let Some(d) = memo.get(id) { + return *d; + } + if !seen.insert(id.to_owned()) { + return 0; + } + let parent = parents.get(id).and_then(|p| p.as_deref()); + let d = match parent { + Some(p) if parents.contains_key(p) => 1 + depth_of(p, parents, memo, seen), + _ => 0, + }; + memo.insert(id.to_owned(), d); + d + } + let mut memo: HashMap = HashMap::new(); + let mut entries: Vec<(usize, Value)> = server + .into_iter() + .map(|v| { + let id = v.get("id").and_then(Value::as_str).unwrap_or("").to_owned(); + let mut seen = std::collections::HashSet::new(); + let d = depth_of(&id, &parents, &mut memo, &mut seen); + (d, v) + }) + .collect(); + entries.sort_by_key(|(d, _)| *d); + entries.into_iter().map(|(_, v)| v).collect() +} + +fn delete_vanished_flat( + conn: &mut Connection, + source_id: i64, + type_name: &str, + table: &str, + local: &HashMap, + server_ids: &[String], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let server_set: std::collections::HashSet<&str> = + server_ids.iter().map(String::as_str).collect(); + let vanished: Vec<(&String, &i64)> = local + .iter() + .filter(|(graph_id, _)| !server_set.contains(graph_id.as_str())) + .collect(); + for chunk in vanished.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, local_id) in chunk { + let result = tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + params![local_id], + ); + match result { + Ok(_) => { + exchange_graph_ids::delete(&tx, source_id, type_name, graph_id)?; + counts.deleted += 1; + } + Err(_) => { + counts.failed += 1; + } + } + } + tx.commit()?; + } + Ok(()) +} + +fn delete_vanished_mailboxes( + conn: &mut Connection, + source_id: i64, + local: &HashMap, + server_ids: &[String], + counts: &mut TypeCounts, + logger: &crate::logging::Logger, +) -> Result<(), Error> { + let server_set: std::collections::HashSet<&str> = + server_ids.iter().map(String::as_str).collect(); + let mut vanished: Vec<(String, i64)> = local + .iter() + .filter(|(id, _)| !server_set.contains(id.as_str())) + .map(|(id, lid)| (id.clone(), *lid)) + .collect(); + let depths = mailbox_depths(conn, &vanished)?; + vanished.sort_by_key(|(_, local_id)| std::cmp::Reverse(*depths.get(local_id).unwrap_or(&0))); + + for (graph_id, local_id) in vanished { + let tx = conn.unchecked_transaction()?; + let result = tx.execute("DELETE FROM mailboxes WHERE id = ?1", params![local_id]); + match result { + Ok(_) => { + exchange_graph_ids::delete(&tx, source_id, exchange_graph_ids::MAILBOX, &graph_id)?; + tx.commit()?; + counts.deleted += 1; + } + Err(e) => { + let _ = tx.rollback(); + logger.warn(&format!( + "mailbox {graph_id} (local id {local_id}) could not be deleted (live children?): {e}" + )); + counts.failed += 1; + } + } + } + Ok(()) +} + +fn mailbox_depths(conn: &Connection, rows: &[(String, i64)]) -> Result, Error> { + let mut parents: HashMap> = HashMap::new(); + { + let mut stmt = conn.prepare("SELECT id, parent_id FROM mailboxes")?; + let mut iter = stmt.query([])?; + while let Some(row) = iter.next()? { + let id: i64 = row.get(0)?; + let parent: Option = row.get(1)?; + parents.insert(id, parent); + } + } + let mut depths: HashMap = HashMap::new(); + for (_, local_id) in rows { + depths.insert(*local_id, depth_of(*local_id, &parents)); + } + Ok(depths) +} + +fn depth_of(id: i64, parents: &HashMap>) -> usize { + let mut depth = 0; + let mut cursor = id; + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + while seen.insert(cursor) { + match parents.get(&cursor).copied().flatten() { + Some(p) => { + depth += 1; + cursor = p; + } + None => break, + } + } + depth +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn order_by_parent_returns_parents_first() { + let input = vec![ + json!({"id": "C", "parentFolderId": "B"}), + json!({"id": "B", "parentFolderId": "A"}), + json!({"id": "A"}), + ]; + let out = order_by_parent(input); + assert_eq!(out[0]["id"], "A"); + assert_eq!(out[1]["id"], "B"); + assert_eq!(out[2]["id"], "C"); + } + + #[test] + fn order_by_parent_tolerates_missing_parent() { + let input = vec![ + json!({"id": "Orphan", "parentFolderId": "Missing"}), + json!({"id": "A"}), + ]; + let out = order_by_parent(input); + assert_eq!(out.len(), 2); + } +} diff --git a/src/sync/import_exchange_graph/messages.rs b/src/sync/import_exchange_graph/messages.rs new file mode 100644 index 0000000..9b136a9 --- /dev/null +++ b/src/sync/import_exchange_graph/messages.rs @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use rusqlite::{Connection, Transaction, params}; +use serde_json::json; + +use crate::db::{blobs, exchange_graph_ids}; +use crate::error::Error; +use crate::exchange_graph::api; +use crate::exchange_graph::client::Accept; +use crate::exchange_graph::error::GraphError; +use crate::logging::LEVEL_PROGRESS; +use crate::sync::TypeCounts; +use crate::sync::emailmeta::email_meta_from_blob; +use crate::sync::import_jmap::pool::Pool; +use crate::sync::keys::index_to_json; + +use super::coordinator::{CHUNK_SIZE, GraphCoordinator}; +use super::folders::MailFolder; + +pub fn reconcile_all( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + folders: &[MailFolder], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let local: std::collections::HashMap = + exchange_graph_ids::ids_of_type(conn, ctx.source_id, exchange_graph_ids::EMAIL)?; + let mut server_total: std::collections::HashSet = std::collections::HashSet::new(); + let mut any_failure = false; + + for folder in folders { + let url = ctx.endpoints.folder_messages_ids(&folder.graph_id, ctx.top); + let ids = match api::collect_all_ids(ctx.client, &url, &[]) { + Ok(v) => v, + Err(e) => { + ctx.logger.warn(&format!( + "folder {} message enumeration failed: {e}", + folder.graph_id + )); + counts.failed += 1; + any_failure = true; + continue; + } + }; + if ctx.logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "graph folder {} enumerated {} messages", + folder.graph_id, + ids.len() + ); + } + for id in &ids { + server_total.insert(id.clone()); + } + let new_ids: Vec = ids + .into_iter() + .filter(|id| !local.contains_key(id)) + .collect(); + if new_ids.is_empty() { + continue; + } + fetch_and_insert(conn, ctx, folder, &new_ids, counts)?; + } + + if any_failure { + ctx.logger.warn( + "graph message vanished-cleanup skipped: one or more folders failed to enumerate; \ + a clean re-run will reconcile deletions", + ); + } else { + delete_vanished(conn, ctx.source_id, &local, &server_total, counts)?; + } + Ok(()) +} + +fn fetch_and_insert( + conn: &mut Connection, + ctx: &GraphCoordinator<'_>, + folder: &MailFolder, + ids: &[String], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let client = ctx.client.clone(); + let endpoints: crate::exchange_graph::api::Endpoints = (*ctx.endpoints).clone(); + type FetchResult = (String, Result, GraphError>); + let pool: Pool = Pool::new(ctx.workers, move |id: String| { + let url = endpoints.message_mime(&id); + match client.get_with_prefer(&url, Accept::Text, &[]) { + Ok(resp) => (id, Ok(resp.body)), + Err(e) => (id, Err(e)), + } + }); + for id in ids { + pool.submit(id.clone()); + } + let mut tx_opt: Option> = None; + let mut in_batch: usize = 0; + for _ in 0..ids.len() { + let Ok((graph_id, result)) = pool.results().recv() else { + break; + }; + match result { + Ok(bytes) => { + if tx_opt.is_none() { + tx_opt = Some(conn.unchecked_transaction()?); + } + let tx = tx_opt.as_mut().expect("tx is Some"); + apply_message_in_tx(tx, ctx, folder, &graph_id, &bytes, counts)?; + in_batch += 1; + if in_batch >= CHUNK_SIZE { + if let Some(t) = tx_opt.take() { + t.commit()?; + } + in_batch = 0; + } + } + Err(GraphError::Vanished) => { + counts.skipped += 1; + } + Err(e) => { + counts.failed += 1; + ctx.logger + .warn(&format!("graph message {graph_id} fetch failed: {e}")); + } + } + } + if let Some(t) = tx_opt.take() { + t.commit()?; + } + Ok(()) +} + +fn apply_message_in_tx( + tx: &Transaction<'_>, + ctx: &GraphCoordinator<'_>, + folder: &MailFolder, + graph_id: &str, + bytes: &[u8], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let (idx, date_header) = email_meta_from_blob(bytes); + let message_match = index_to_json(&idx); + let received_at = date_header.unwrap_or_else(|| "1970-01-01T00:00:00Z".to_owned()); + let mailbox_ids = json!([folder.local_id]).to_string(); + let keywords = "[]".to_owned(); + + let blob_id = blobs::intern_blob(tx, bytes)?; + tx.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![blob_id, received_at, mailbox_ids, keywords, message_match], + )?; + let new_id = tx.last_insert_rowid(); + exchange_graph_ids::insert( + tx, + ctx.source_id, + exchange_graph_ids::EMAIL, + graph_id, + new_id, + )?; + counts.created += 1; + Ok(()) +} + +fn delete_vanished( + conn: &mut Connection, + source_id: i64, + local: &std::collections::HashMap, + server: &std::collections::HashSet, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let vanished: Vec<(&String, &i64)> = local + .iter() + .filter(|(graph_id, _)| !server.contains(graph_id.as_str())) + .collect(); + for chunk in vanished.chunks(CHUNK_SIZE) { + let tx = conn.unchecked_transaction()?; + for (graph_id, local_id) in chunk { + let result = tx.execute("DELETE FROM emails WHERE id = ?1", params![local_id]); + match result { + Ok(_) => { + exchange_graph_ids::delete( + &tx, + source_id, + exchange_graph_ids::EMAIL, + graph_id, + )?; + counts.deleted += 1; + } + Err(_) => { + counts.failed += 1; + } + } + } + tx.commit()?; + } + Ok(()) +} diff --git a/src/sync/import_imap.rs b/src/sync/import_imap.rs new file mode 100644 index 0000000..351d71f --- /dev/null +++ b/src/sync/import_imap.rs @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod fetch; +pub mod folders; +pub mod internaldate; +pub mod keywords; +pub mod messages; +pub mod pool; + +pub mod coordinator; + +pub use coordinator::{ImapAuth, ImapImportConfig, run}; diff --git a/src/sync/import_imap/coordinator.rs b/src/sync/import_imap/coordinator.rs new file mode 100644 index 0000000..d0d96a8 --- /dev/null +++ b/src/sync/import_imap/coordinator.rs @@ -0,0 +1,1141 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use crossbeam_channel::RecvTimeoutError; +use regex::Regex; +use rusqlite::{Connection, OptionalExtension, params}; +use serde_json::Value; +use time::OffsetDateTime; +use time::format_description::well_known::Rfc3339; +use url::Url; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::imap::client::{ConnectMode, ImapClient}; +use crate::imap::command; +use crate::imap::error::ImapError; +use crate::imap::name::encode_mailbox_name_with; +use crate::imap::response::{NamespaceEntry, Untagged}; +use crate::imap::retry::{ + BackoffState, Disposition, RetryPolicy, classify, is_negotiation_failure, +}; +use crate::imap::transport::Connector; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::sync::emailmeta::email_index_from_blob; +use crate::sync::keys::index_to_json; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::fetch; +use super::folders::{ + FolderFilters, ResolvedFolder, apply_filters, collect_from_list, sort_by_depth, + vanished_depth_sort, vanished_folders, +}; +use super::internaldate::imap_internaldate_to_rfc3339; +use super::keywords::translate_flags; +use super::messages::{chunks, diff_uids, select_uids}; +use super::pool::{FetchEvent, FetchJob, WorkerArgs, WorkerPool}; + +const EMAIL_TYPE: &str = "email"; + +#[derive(Clone, Copy)] +pub(super) struct RunOpts { + source_id: i64, + fetch_batch: usize, + include_deleted: bool, + logger: Logger, +} + +#[derive(Clone)] +pub(super) struct ControlCtx { + pub connector: std::sync::Arc, + pub endpoint: std::sync::Arc, + pub mode: ConnectMode, + pub auth: ImapAuth, + pub compress: bool, + pub policy: RetryPolicy, + pub backoff: BackoffState, + pub logger: Logger, +} + +pub(super) fn control_run_collect( + client: &mut ImapClient, + ctx: &ControlCtx, + command: &str, +) -> Result { + let mut transient_attempts: u32 = 0; + let mut transport_attempts: u32 = 0; + loop { + match client.run_collect(command) { + Ok(r) => { + ctx.backoff.reset(); + return Ok(r); + } + Err(e) => match classify(&e) { + Disposition::TransportDrop => { + if transport_attempts >= ctx.policy.max_retries { + return Err(Error::Connection(format!("control connection lost: {e}"))); + } + transport_attempts += 1; + std::thread::sleep(ctx.backoff.transport_delay(transport_attempts)); + match reconnect_control(client, ctx) { + Ok(()) => { + log_at(ctx.logger, LEVEL_DEFAULT, "control connection reconnected"); + } + Err(e2) => { + return Err(Error::Connection(format!( + "control reconnect failed: {e2}" + ))); + } + } + } + Disposition::Transient => { + if transient_attempts >= ctx.policy.max_retries { + return Err(Error::Partial(format!( + "{command:?}: {e} (max retries exhausted)" + ))); + } + transient_attempts += 1; + std::thread::sleep(ctx.backoff.next_shared_delay()); + } + _ => { + return Err(Error::Partial(format!("{command:?}: {e}"))); + } + }, + } + } +} + +pub(super) fn call_with_retry( + client: &mut ImapClient, + ctx: &ControlCtx, + mut attempt: F, +) -> Result +where + F: FnMut(&mut ImapClient) -> Result, +{ + let mut transient_attempts: u32 = 0; + let mut transport_attempts: u32 = 0; + loop { + match attempt(client) { + Ok(v) => { + ctx.backoff.reset(); + return Ok(v); + } + Err(e) => match classify(&e) { + Disposition::TransportDrop => { + if transport_attempts >= ctx.policy.max_retries { + return Err(e); + } + transport_attempts += 1; + std::thread::sleep(ctx.backoff.transport_delay(transport_attempts)); + reconnect_control(client, ctx)?; + } + Disposition::Transient => { + if transient_attempts >= ctx.policy.max_retries { + return Err(e); + } + transient_attempts += 1; + std::thread::sleep(ctx.backoff.next_shared_delay()); + } + _ => return Err(e), + }, + } + } +} + +fn reconnect_control(client: &mut ImapClient, ctx: &ControlCtx) -> Result<(), ImapError> { + let new_client = ImapClient::connect( + &ctx.connector, + &ctx.endpoint.host, + ctx.endpoint.port, + ctx.mode, + )?; + *client = new_client; + authenticate_client(client, &ctx.auth).map_err(|e| ImapError::AuthFailed(e.to_string()))?; + let _ = client.refresh_capabilities(); + if ctx.compress && client.has_capability("COMPRESS=DEFLATE") { + client.compress_deflate()?; + } + if client.has_capability("ENABLE") && client.has_capability("UTF8=ACCEPT") { + let _ = client.enable(&["UTF8=ACCEPT"]); + } + Ok(()) +} + +fn log_at(logger: Logger, level: u8, msg: &str) { + if logger.enabled(level) { + println!("{msg}"); + } +} + +pub struct ImapImportConfig { + pub url: String, + pub auth: ImapAuth, + pub allow_cleartext: bool, + pub compress: bool, + pub include: Vec, + pub exclude: Vec, + pub exclude_special: Vec, + pub folder: Vec, + pub subscribed_only: bool, + pub automap: bool, + pub include_deleted: bool, + pub fetch_batch: usize, + pub imap_connections: usize, + pub allow_source_change: bool, +} + +#[derive(Debug, Clone)] +pub enum ImapAuth { + Basic { user: String, password: String }, + Bearer { user: String, token: String }, +} + +pub fn run(common: CommonConfig, config: ImapImportConfig) -> Result { + let logger = common.logger; + let mut conn = db::init::open(&common.archive)?; + + let endpoint = parse_endpoint(&config.url)?; + let session_url = format!( + "{}://{}:{}", + if endpoint.implicit_tls { + "imaps" + } else { + "imap" + }, + endpoint.host, + endpoint.port + ); + + let connector = std::sync::Arc::new( + Connector::new(common.allow_invalid_certs).map_err(|e| Error::Connection(e.to_string()))?, + ); + let endpoint = std::sync::Arc::new(endpoint); + let mode = if endpoint.implicit_tls { + ConnectMode::ImplicitTls + } else if config.allow_cleartext { + ConnectMode::Plain + } else { + ConnectMode::StartTls + }; + let mut client = ImapClient::connect(&connector, &endpoint.host, endpoint.port, mode) + .map_err(|e| Error::Connection(e.to_string()))?; + + let account_id = authenticate(&mut client, &config.auth)?; + log_at( + logger, + LEVEL_PROGRESS, + &format!("authenticated as {account_id} on {session_url}"), + ); + client + .refresh_capabilities() + .map_err(|e| Error::Connection(format!("post-auth CAPABILITY: {e}")))?; + + if config.compress { + if client.has_capability("COMPRESS=DEFLATE") { + client + .compress_deflate() + .map_err(|e| Error::Connection(format!("COMPRESS DEFLATE: {e}")))?; + log_at(logger, LEVEL_PROGRESS, "COMPRESS DEFLATE enabled"); + } else { + log_at( + logger, + LEVEL_PROGRESS, + "--compress requested but server does not advertise COMPRESS=DEFLATE; ignored", + ); + } + } + + if client.has_capability("ENABLE") && client.has_capability("UTF8=ACCEPT") { + match client.enable(&["UTF8=ACCEPT"]) { + Ok(_) => log_at(logger, LEVEL_PROGRESS, "ENABLE UTF8=ACCEPT"), + Err(e) => log_at( + logger, + LEVEL_PROGRESS, + &format!("ENABLE UTF8=ACCEPT failed, falling back to modified UTF-7: {e}"), + ), + } + } + + let source_key = SourceKey { + kind: "imap".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + if let Some((existing_url, existing_account)) = + db::sources::conflicting_source(&conn, "imap", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records imap source {existing_url} / {existing_account}; \ + re-run with --allow-source-change or use a fresh archive" + ))); + } + let source_id = if common.dry_run { + -1 + } else { + db::sources::upsert_source(&conn, &source_key, Some(&account_id), &account_id)? + }; + + let backoff = BackoffState::new(); + let policy = RetryPolicy::new(common.max_retries); + let control_ctx = ControlCtx { + connector: connector.clone(), + endpoint: endpoint.clone(), + mode, + auth: config.auth.clone(), + compress: config.compress, + policy, + backoff: backoff.clone(), + logger, + }; + + let namespace_prefix = discover_namespace(&mut client, logger)?; + + let extended_supported = client.has_capability("LIST-EXTENDED"); + let list_status_supported = extended_supported && client.has_capability("LIST-STATUS"); + let list_cmd = if list_status_supported { + command::list_extended( + "", + &["*"], + &[], + &[ + "SPECIAL-USE", + "SUBSCRIBED", + "CHILDREN", + "STATUS (UIDVALIDITY UIDNEXT MESSAGES)", + ], + ) + } else if extended_supported { + command::list_extended("", &["*"], &[], &["SPECIAL-USE", "SUBSCRIBED"]) + } else { + command::list("", "*") + }; + let list_resp = control_run_collect(&mut client, &control_ctx, &list_cmd)?; + let mut all_responses = list_resp.untagged; + if (!extended_supported || !list_status_supported) + && let Ok(lsub_resp) = + control_run_collect(&mut client, &control_ctx, &command::lsub("", "*")) + { + all_responses.extend(lsub_resp.untagged); + } + + let discovered = collect_from_list(&all_responses, client.utf8_accept()) + .map_err(|e| Error::Partial(format!("LIST parse: {e}")))?; + let filters = FolderFilters { + include: config.include, + exclude: config.exclude, + exclude_special: config.exclude_special, + explicit: config.folder, + subscribed_only: config.subscribed_only, + automap_enabled: config.automap, + namespace_prefix, + }; + let mut resolved = apply_filters(discovered, &filters); + sort_by_depth(&mut resolved); + + if common.dry_run { + let existing_source = db::sources::find_source(&conn, &source_key)?; + return dry_run_summary( + &conn, + existing_source, + &mut client, + &control_ctx, + &resolved, + logger, + ); + } + + let mut mailbox_counts = TypeCounts::default(); + let mut email_counts = TypeCounts::default(); + + let local_mailboxes = db::imap_ids::mailbox_folders(&conn, source_id)?; + let server_folder_set: HashSet = resolved.iter().map(|f| f.name.clone()).collect(); + + upsert_mailboxes(&mut conn, source_id, &resolved, &mut mailbox_counts)?; + + let server_delimiter = resolved.iter().find_map(|f| f.delimiter).unwrap_or('/'); + + let mut vanished = vanished_folders(&local_mailboxes, &server_folder_set); + vanished_depth_sort(&mut vanished, server_delimiter); + if !vanished.is_empty() { + delete_vanished_folders( + &mut conn, + source_id, + &vanished, + &mut mailbox_counts, + &mut email_counts, + logger, + )?; + } + + let opts = RunOpts { + source_id, + fetch_batch: config.fetch_batch.max(1), + include_deleted: config.include_deleted, + logger, + }; + + let pool = WorkerPool::start( + WorkerArgs { + connector: connector.clone(), + endpoint: endpoint.clone(), + mode, + auth: config.auth.clone(), + compress: config.compress, + policy, + backoff: backoff.clone(), + }, + config.imap_connections.max(1), + ) + .map_err(|e| Error::Connection(format!("worker pool: {e}")))?; + + for (i, folder) in resolved.iter().enumerate() { + if i > 0 { + let _ = control_run_collect(&mut client, &control_ctx, "NOOP"); + } + match reconcile_folder( + &mut conn, + &mut client, + &control_ctx, + &pool, + folder, + opts, + &mut email_counts, + ) { + Ok(()) => {} + Err(e) => { + log_at( + logger, + LEVEL_DEFAULT, + &format!("folder {:?}: {e}", folder.name), + ); + email_counts.failed += 1; + } + } + } + + pool.shutdown(); + let _ = client.logout(); + + Ok(Summary { + per_type: vec![("mailbox", mailbox_counts), ("email", email_counts)], + retries_observed: backoff.total_retries(), + retry_after_sleeps: backoff.transient_retries() as u64, + }) +} + +#[derive(Debug, Clone)] +pub struct Endpoint { + pub host: String, + pub port: u16, + pub implicit_tls: bool, +} + +fn parse_endpoint(url: &str) -> Result { + let parsed = + Url::parse(url).map_err(|e| Error::Usage(format!("invalid --url {url:?}: {e}")))?; + let scheme = parsed.scheme(); + let implicit_tls = match scheme { + "imaps" => true, + "imap" => false, + other => { + return Err(Error::Usage(format!( + "--url scheme must be imap or imaps, got {other}" + ))); + } + }; + let host = parsed + .host_str() + .ok_or_else(|| Error::Usage(format!("--url missing host: {url}")))? + .to_owned(); + let port = parsed + .port() + .unwrap_or(if implicit_tls { 993 } else { 143 }); + Ok(Endpoint { + host, + port, + implicit_tls, + }) +} + +pub(super) fn authenticate_client(client: &mut ImapClient, auth: &ImapAuth) -> Result<(), Error> { + authenticate(client, auth).map(|_| ()) +} + +fn authenticate(client: &mut ImapClient, auth: &ImapAuth) -> Result { + match auth { + ImapAuth::Basic { user, password } => { + if client.has_capability("AUTH=PLAIN") { + match client.authenticate_plain(user, password) { + Ok(()) => return Ok(user.clone()), + Err(e) if is_auth_failed(&e) => { + return Err(Error::Connection(format!("auth failed: {e}"))); + } + Err(e) if is_negotiation_failure(&e) => {} + Err(e) => return Err(Error::Connection(e.to_string())), + } + } + match client.login(user, password) { + Ok(()) => Ok(user.clone()), + Err(e) if is_auth_failed(&e) => Err(Error::Connection(format!("auth failed: {e}"))), + Err(e) => Err(Error::Connection(format!("LOGIN failed: {e}"))), + } + } + ImapAuth::Bearer { user, token } => { + let has_xoauth2 = client.has_capability("AUTH=XOAUTH2"); + let has_oauthbearer = client.has_capability("AUTH=OAUTHBEARER"); + if !has_xoauth2 && !has_oauthbearer { + return Err(Error::Connection( + "server does not advertise AUTH=XOAUTH2 or AUTH=OAUTHBEARER".into(), + )); + } + if has_xoauth2 { + match client.authenticate_xoauth2(user, token) { + Ok(()) => return Ok(user.clone()), + Err(e) if is_auth_failed(&e) => { + return Err(Error::Connection(format!("auth failed: {e}"))); + } + Err(e) if is_negotiation_failure(&e) && has_oauthbearer => {} + Err(e) => return Err(Error::Connection(format!("XOAUTH2 failed: {e}"))), + } + } + client + .authenticate_oauthbearer(user, token) + .map_err(|e| Error::Connection(format!("OAUTHBEARER failed: {e}")))?; + Ok(user.clone()) + } + } +} + +fn is_auth_failed(e: &ImapError) -> bool { + match e { + ImapError::AuthFailed(_) => true, + ImapError::No(no) => no.is_auth_failed(), + _ => false, + } +} + +fn discover_namespace(client: &mut ImapClient, logger: Logger) -> Result { + if !client.has_capability("NAMESPACE") { + return Ok(String::new()); + } + let resp = match client.run_collect(command::namespace()) { + Ok(r) => r, + Err(e) => { + log_at( + logger, + LEVEL_PROGRESS, + &format!("NAMESPACE failed, ignoring: {e}"), + ); + return Ok(String::new()); + } + }; + for u in resp.untagged { + if let Untagged::Namespace { personal, .. } = u + && let Some(NamespaceEntry { prefix, .. }) = personal.into_iter().next() + { + return Ok(prefix); + } + } + Ok(String::new()) +} + +fn upsert_mailboxes( + conn: &mut Connection, + source_id: i64, + folders: &[ResolvedFolder], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let tx = conn.transaction()?; + let mut local_ids: HashMap = HashMap::new(); + for folder in folders { + let parent_local = match &folder.parent_path { + Some(p) => local_ids.get(p.as_str()).copied().or_else(|| { + db::imap_ids::local_for_mailbox(&tx, source_id, p) + .ok() + .flatten() + }), + None => None, + }; + let existing = db::imap_ids::local_for_mailbox(&tx, source_id, &folder.name)?; + let id = if let Some(id) = existing { + tx.execute( + "UPDATE mailboxes SET name = ?1, parent_id = ?2, role = ?3, + is_subscribed = ?4 WHERE id = ?5", + params![ + folder.leaf, + parent_local, + folder.role, + folder.subscribed as i64, + id + ], + )?; + id + } else { + tx.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, 0, ?4)", + params![ + folder.leaf, + parent_local, + folder.role, + folder.subscribed as i64 + ], + )?; + let new_id = tx.last_insert_rowid(); + db::imap_ids::insert_mailbox(&tx, source_id, &folder.name, new_id)?; + counts.created += 1; + counts.fetched += 1; + new_id + }; + local_ids.insert(folder.name.clone(), id); + } + tx.commit()?; + Ok(()) +} + +fn delete_vanished_folders( + conn: &mut Connection, + source_id: i64, + folders: &[String], + mailbox_counts: &mut TypeCounts, + email_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let tx = conn.transaction()?; + for name in folders { + let local_id = match db::imap_ids::local_for_mailbox(&tx, source_id, name)? { + Some(id) => id, + None => continue, + }; + let surviving_child: Option = tx + .query_row( + "SELECT id FROM mailboxes WHERE parent_id = ?1 LIMIT 1", + params![local_id], + |row| row.get(0), + ) + .optional()?; + if surviving_child.is_some() { + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "folder {name:?}: vanished from server but still has child mailboxes in the archive; skipping delete (parent_id RESTRICT)" + ), + ); + mailbox_counts.failed += 1; + continue; + } + let email_ids: Vec = tx + .prepare( + "SELECT local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + )? + .query_map(params![source_id, EMAIL_TYPE, name], |row| row.get(0))? + .collect::, _>>()?; + for eid in &email_ids { + tx.execute("DELETE FROM emails WHERE id = ?1", params![eid])?; + email_counts.deleted += 1; + } + db::imap_ids::delete_all_emails_in_folder(&tx, source_id, name)?; + db::imap_state::delete(&tx, source_id, name)?; + tx.execute("DELETE FROM mailboxes WHERE id = ?1", params![local_id])?; + db::imap_ids::delete_mailbox(&tx, source_id, name)?; + mailbox_counts.deleted += 1; + } + tx.commit()?; + Ok(()) +} + +fn reconcile_folder( + conn: &mut Connection, + client: &mut ImapClient, + control_ctx: &ControlCtx, + pool: &WorkerPool, + folder: &ResolvedFolder, + opts: RunOpts, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let RunOpts { + source_id, + fetch_batch, + include_deleted: _, + logger, + } = opts; + + if let Some(status) = folder.status.as_ref() + && status.messages == Some(0) + && let Some(uv) = status.uidvalidity + && let Some(un) = status.uidnext + { + let local = db::imap_ids::email_uids_in_folder(conn, source_id, &folder.name)?; + if local.is_empty() { + let now = OffsetDateTime::now_utc() + .format(&Rfc3339) + .unwrap_or_else(|_| String::from("1970-01-01T00:00:00Z")); + db::imap_state::upsert(conn, source_id, &folder.name, uv as u32, un as u32, &now)?; + log_at( + logger, + LEVEL_PROGRESS, + &format!("folder {:?}: empty (LIST-STATUS), skipped", folder.name), + ); + return Ok(()); + } + } + + let wire_name = encode_mailbox_name_with(&folder.name, client.utf8_accept()); + let resp = control_run_collect(client, control_ctx, &command::select(&wire_name))?; + let mut uidvalidity: u32 = 0; + let mut uidnext: u32 = 0; + for u in &resp.untagged { + if let Untagged::StatusLine(line) = u + && let Some(code) = &line.code + && let Some(args) = &line.code_args + { + match code.as_str() { + "UIDVALIDITY" => { + if let Ok(n) = args.parse() { + uidvalidity = n; + } + } + "UIDNEXT" => { + if let Ok(n) = args.parse() { + uidnext = n; + } + } + _ => {} + } + } + } + if uidvalidity == 0 { + return Err(Error::Partial(format!( + "SELECT {:?}: server gave no UIDVALIDITY", + folder.name + ))); + } + + if let Some(prev) = db::imap_state::get(conn, source_id, &folder.name)? + && prev.uidvalidity != uidvalidity + { + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "folder {:?}: UIDVALIDITY changed {} -> {} (wiping local rows)", + folder.name, prev.uidvalidity, uidvalidity + ), + ); + wipe_folder_emails(conn, source_id, &folder.name, counts)?; + } + + let server_uids = call_with_retry(client, control_ctx, select_uids) + .map_err(|e| Error::Partial(format!("UID enum {:?}: {e}", folder.name)))?; + let local_uids = + db::imap_ids::email_uids_at_validity(conn, source_id, &folder.name, uidvalidity)?; + let diff = diff_uids(&local_uids, &server_uids); + + log_at( + logger, + LEVEL_PROGRESS, + &format!( + "folder {:?}: new={} vanished={} present={}", + folder.name, + diff.new.len(), + diff.vanished.len(), + diff.present.len() + ), + ); + + if !diff.vanished.is_empty() { + delete_vanished_emails( + conn, + source_id, + &folder.name, + uidvalidity, + &diff.vanished, + counts, + )?; + } + + if !diff.new.is_empty() { + let mailbox_local = db::imap_ids::local_for_mailbox(conn, source_id, &folder.name)? + .ok_or_else(|| { + Error::Partial(format!( + "mailbox {:?} missing from sync_id_imap", + folder.name + )) + })?; + let batches: Vec<&[u32]> = chunks(&diff.new, fetch_batch); + let n_batches = batches.len(); + for batch in &batches { + pool.submit(FetchJob { + folder: folder.name.clone(), + uidvalidity, + uids: batch.to_vec(), + }); + } + let keepalive_interval = std::time::Duration::from_secs(45); + let mut chunks_done: usize = 0; + let tx = conn.transaction()?; + let target = FetchTarget { + folder: folder.name.as_str(), + uidvalidity, + mailbox_local, + }; + while chunks_done < n_batches { + let event = loop { + match pool.recv_timeout(keepalive_interval) { + Ok(r) => break r, + Err(RecvTimeoutError::Timeout) => { + let _ = control_run_collect(client, control_ctx, "NOOP"); + } + Err(RecvTimeoutError::Disconnected) => { + return Err(Error::Partial("worker channel disconnected".to_owned())); + } + } + }; + match event { + FetchEvent::Item { attrs, .. } => { + insert_single_message(&tx, &target, &attrs, opts, counts)?; + } + FetchEvent::ChunkDone { + folder: chunk_folder, + outcome, + uids_requested, + .. + } => { + chunks_done += 1; + if let Err(e) = outcome { + log_at( + logger, + LEVEL_DEFAULT, + &format!("UID FETCH chunk failed for folder {chunk_folder:?}: {e}",), + ); + counts.failed += uids_requested.len() as u64; + } + } + } + } + tx.commit()?; + } + + let now = OffsetDateTime::now_utc() + .format(&Rfc3339) + .unwrap_or_else(|_| String::from("1970-01-01T00:00:00Z")); + db::imap_state::upsert(conn, source_id, &folder.name, uidvalidity, uidnext, &now)?; + Ok(()) +} + +fn wipe_folder_emails( + conn: &mut Connection, + source_id: i64, + folder: &str, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let tx = conn.transaction()?; + let ids: Vec = tx + .prepare( + "SELECT local_id FROM sync_id_imap + WHERE source_id = ?1 AND type_name = ?2 AND folder = ?3", + )? + .query_map(params![source_id, EMAIL_TYPE, folder], |row| row.get(0))? + .collect::, _>>()?; + for id in &ids { + tx.execute("DELETE FROM emails WHERE id = ?1", params![id])?; + counts.deleted += 1; + } + db::imap_ids::delete_all_emails_in_folder(&tx, source_id, folder)?; + tx.commit()?; + Ok(()) +} + +fn delete_vanished_emails( + conn: &mut Connection, + source_id: i64, + folder: &str, + uidvalidity: u32, + uids: &[u32], + counts: &mut TypeCounts, +) -> Result<(), Error> { + let tx = conn.transaction()?; + for &uid in uids { + if let Some(local_id) = + db::imap_ids::local_for_email(&tx, source_id, folder, uidvalidity, uid)? + { + tx.execute("DELETE FROM emails WHERE id = ?1", params![local_id])?; + db::imap_ids::delete_email(&tx, source_id, folder, uidvalidity, uid)?; + counts.deleted += 1; + } + } + tx.commit()?; + Ok(()) +} + +pub(super) struct FetchTarget<'a> { + pub folder: &'a str, + pub uidvalidity: u32, + pub mailbox_local: i64, +} + +fn insert_single_message( + tx: &rusqlite::Transaction<'_>, + target: &FetchTarget<'_>, + attrs: &fetch::FetchAttrs, + opts: RunOpts, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let RunOpts { + source_id, + fetch_batch: _, + include_deleted, + logger, + } = opts; + let folder = target.folder; + let uidvalidity = target.uidvalidity; + let mailbox_local = target.mailbox_local; + + let Some(uid) = attrs.uid else { + return Ok(()); + }; + let Some(body) = attrs.body.as_ref() else { + counts.skipped += 1; + return Ok(()); + }; + if let Some(declared_size) = attrs.size + && (declared_size as usize) != body.len() + { + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "folder {folder:?} uid {uid}: BODY[] {} bytes vs RFC822.SIZE {declared_size}, skipping", + body.len() + ), + ); + counts.skipped += 1; + return Ok(()); + } + let translation = translate_flags(&attrs.flags, include_deleted); + if translation.has_deleted_flag && !include_deleted { + counts.skipped += 1; + return Ok(()); + } + let received_at = match attrs.internaldate.as_deref() { + Some(s) => imap_internaldate_to_rfc3339(s)?, + None => OffsetDateTime::now_utc() + .format(&Rfc3339) + .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()), + }; + let blob_id = db::blobs::intern_blob(tx, body)?; + let message_match = index_to_json(&email_index_from_blob(body)); + let mailbox_ids = Value::Array(vec![Value::from(mailbox_local)]); + let keywords = Value::Array( + translation + .keywords + .iter() + .map(|k| Value::String(k.clone())) + .collect(), + ); + tx.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + blob_id, + received_at, + mailbox_ids.to_string(), + keywords.to_string(), + message_match, + ], + )?; + let email_local = tx.last_insert_rowid(); + db::imap_ids::insert_email(tx, source_id, folder, uidvalidity, uid, email_local)?; + counts.created += 1; + counts.fetched += 1; + Ok(()) +} + +fn dry_run_summary( + conn: &Connection, + source_id: Option, + client: &mut ImapClient, + control_ctx: &ControlCtx, + folders: &[ResolvedFolder], + logger: Logger, +) -> Result { + let mut mailbox = TypeCounts::default(); + let mut email = TypeCounts::default(); + + let server_set: HashSet = folders.iter().map(|f| f.name.clone()).collect(); + let local_mailboxes = match source_id { + Some(sid) => db::imap_ids::mailbox_folders(conn, sid)?, + None => HashMap::new(), + }; + let new_folders: Vec<&str> = folders + .iter() + .map(|f| f.name.as_str()) + .filter(|n| !local_mailboxes.contains_key(*n)) + .collect(); + let vanished_folder_names: Vec<&str> = local_mailboxes + .keys() + .filter(|n| !server_set.contains(n.as_str())) + .map(|s| s.as_str()) + .collect(); + mailbox.created += new_folders.len() as u64; + mailbox.deleted += vanished_folder_names.len() as u64; + mailbox.fetched += new_folders.len() as u64; + if !new_folders.is_empty() { + log_at( + logger, + LEVEL_PROGRESS, + &format!( + "dry-run: folders new={} vanished={}", + new_folders.len(), + vanished_folder_names.len() + ), + ); + } + + for folder in folders { + let wire = encode_mailbox_name_with(&folder.name, client.utf8_accept()); + let select_resp = match control_run_collect(client, control_ctx, &command::select(&wire)) { + Ok(r) => r, + Err(e) => { + log_at( + logger, + LEVEL_DEFAULT, + &format!("dry-run SELECT {:?}: {e}", folder.name), + ); + email.failed += 1; + continue; + } + }; + let mut server_uidvalidity: u32 = 0; + for u in &select_resp.untagged { + if let Untagged::StatusLine(line) = u + && line.code.as_deref() == Some("UIDVALIDITY") + && let Some(args) = &line.code_args + && let Ok(n) = args.parse() + { + server_uidvalidity = n; + break; + } + } + let server_uids = match call_with_retry(client, control_ctx, select_uids) { + Ok(v) => v, + Err(e) => { + log_at( + logger, + LEVEL_DEFAULT, + &format!("dry-run UID enum {:?}: {e}", folder.name), + ); + email.failed += 1; + continue; + } + }; + let local_pairs = match source_id { + Some(sid) => db::imap_ids::email_uids_in_folder(conn, sid, &folder.name)?, + None => HashMap::new(), + }; + let prev_state = match source_id { + Some(sid) => db::imap_state::get(conn, sid, &folder.name)?, + None => None, + }; + let uv_changed = prev_state + .as_ref() + .is_some_and(|p| p.uidvalidity != server_uidvalidity && server_uidvalidity != 0); + let local_uids: Vec = if uv_changed { + Vec::new() + } else { + local_pairs + .keys() + .filter(|(v, _)| server_uidvalidity == 0 || *v == server_uidvalidity) + .map(|(_, u)| *u) + .collect() + }; + let diff = diff_uids(&local_uids, &server_uids); + email.created += diff.new.len() as u64; + email.deleted += diff.vanished.len() as u64; + email.fetched += diff.new.len() as u64; + let uv_note = if uv_changed { + format!( + " (UIDVALIDITY {} -> {})", + prev_state.as_ref().map_or(0, |p| p.uidvalidity), + server_uidvalidity, + ) + } else { + String::new() + }; + log_at( + logger, + LEVEL_PROGRESS, + &format!( + "dry-run: {:?} new={} vanished={} present={}{uv_note}", + folder.name, + diff.new.len(), + diff.vanished.len(), + diff.present.len(), + ), + ); + } + for name in &vanished_folder_names { + if let Some(sid) = source_id { + let n = db::imap_ids::email_uids_in_folder(conn, sid, name)?.len() as u64; + email.deleted += n; + log_at( + logger, + LEVEL_PROGRESS, + &format!( + "dry-run: folder {:?} vanished ({n} emails would be removed)", + name + ), + ); + } + } + + let _ = client.logout(); + Ok(Summary { + per_type: vec![("mailbox", mailbox), ("email", email)], + retries_observed: control_ctx.backoff.total_retries(), + retry_after_sleeps: control_ctx.backoff.transient_retries() as u64, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_endpoint_imaps_defaults_to_993() { + let e = parse_endpoint("imaps://mail.example.com").unwrap(); + assert_eq!(e.host, "mail.example.com"); + assert_eq!(e.port, 993); + assert!(e.implicit_tls); + } + + #[test] + fn parse_endpoint_imap_defaults_to_143() { + let e = parse_endpoint("imap://mail.example.com").unwrap(); + assert_eq!(e.host, "mail.example.com"); + assert_eq!(e.port, 143); + assert!(!e.implicit_tls); + } + + #[test] + fn parse_endpoint_explicit_port() { + let e = parse_endpoint("imaps://mail.example.com:1993").unwrap(); + assert_eq!(e.port, 1993); + } + + #[test] + fn parse_endpoint_rejects_other_schemes() { + let err = parse_endpoint("http://example.com").unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } +} diff --git a/src/sync/import_imap/fetch.rs b/src/sync/import_imap/fetch.rs new file mode 100644 index 0000000..7bc2fc4 --- /dev/null +++ b/src/sync/import_imap/fetch.rs @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::imap::response::{Untagged, Value}; + +#[derive(Debug, Default, Clone)] +pub struct FetchAttrs { + pub uid: Option, + pub flags: Vec, + pub internaldate: Option, + pub size: Option, + pub body: Option>, +} + +pub fn extract(u: &Untagged) -> Option { + let Untagged::Fetch { items, .. } = u else { + return None; + }; + let mut out = FetchAttrs::default(); + for (name, value) in items { + match name.as_str() { + "UID" => { + if let Some(n) = value.as_number() { + out.uid = Some(n as u32); + } + } + "FLAGS" => { + if let Value::List(items) = value { + out.flags = items + .iter() + .filter_map(|v| match v { + Value::Atom(s) | Value::Str(s) => Some(s.clone()), + _ => None, + }) + .collect(); + } + } + "INTERNALDATE" => { + if let Some(s) = value.as_str() { + out.internaldate = Some(s.to_owned()); + } + } + "RFC822.SIZE" => { + if let Some(n) = value.as_number() { + out.size = Some(n); + } + } + n if n == "BODY[]" || n == "RFC822" => { + out.body = match value { + Value::Bytes(b) => Some(b.clone()), + Value::Str(s) => Some(s.clone().into_bytes()), + _ => None, + }; + } + _ => {} + } + } + Some(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + fn parse_fetch(input: &[u8]) -> FetchAttrs { + let resp = crate::imap::response::parse_response(&mut Cursor::new(input)).unwrap(); + match resp { + crate::imap::response::Response::Untagged(u) => extract(&u).expect("fetch attrs"), + _ => panic!("expected untagged"), + } + } + + #[test] + fn extracts_uid_flags_size_internaldate() { + let f = parse_fetch( + b"* 1 FETCH (UID 42 FLAGS (\\Seen) INTERNALDATE \"01-Jan-2024 12:00:00 +0000\" RFC822.SIZE 4242)\r\n", + ); + assert_eq!(f.uid, Some(42)); + assert_eq!(f.flags, vec!["\\Seen"]); + assert_eq!( + f.internaldate.as_deref(), + Some("01-Jan-2024 12:00:00 +0000") + ); + assert_eq!(f.size, Some(4242)); + assert!(f.body.is_none()); + } + + #[test] + fn extracts_body_literal_as_bytes() { + let input = b"* 1 FETCH (UID 5 BODY[] {11}\r\nHello world)\r\n"; + let f = parse_fetch(input); + assert_eq!(f.body.as_deref(), Some(&b"Hello world"[..])); + } + + #[test] + fn ignores_unknown_attrs() { + let f = parse_fetch(b"* 1 FETCH (UID 1 X-CUSTOM \"ignored\" MODSEQ (12345))\r\n"); + assert_eq!(f.uid, Some(1)); + assert!(f.flags.is_empty()); + assert!(f.size.is_none()); + } + + #[test] + fn empty_flags_list() { + let f = parse_fetch(b"* 1 FETCH (UID 7 FLAGS ())\r\n"); + assert_eq!(f.uid, Some(7)); + assert!(f.flags.is_empty()); + } +} diff --git a/src/sync/import_imap/folders.rs b/src/sync/import_imap/folders.rs new file mode 100644 index 0000000..c3810cb --- /dev/null +++ b/src/sync/import_imap/folders.rs @@ -0,0 +1,504 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{BTreeMap, HashMap, HashSet}; + +use regex::Regex; + +use crate::imap::automap; +use crate::imap::name::{canonicalise_inbox, decode_mailbox_name_with}; +use crate::imap::response::Untagged; + +#[derive(Debug, Clone, Default)] +pub struct FolderStatus { + pub uidvalidity: Option, + pub uidnext: Option, + pub messages: Option, +} + +#[derive(Debug, Clone)] +pub struct DiscoveredFolder { + pub name: String, + pub delimiter: Option, + pub attributes: Vec, + pub subscribed: bool, + pub selectable: bool, + pub status: Option, +} + +#[derive(Debug, Clone)] +pub struct ResolvedFolder { + pub name: String, + pub leaf: String, + pub parent_path: Option, + pub delimiter: Option, + pub role: Option<&'static str>, + pub subscribed: bool, + pub status: Option, +} + +pub struct FolderFilters { + pub include: Vec, + pub exclude: Vec, + pub exclude_special: Vec, + pub explicit: Vec, + pub subscribed_only: bool, + pub automap_enabled: bool, + pub namespace_prefix: String, +} + +pub fn collect_from_list( + untagged: &[Untagged], + utf8_accept: bool, +) -> Result, String> { + let mut subscribed = HashSet::new(); + let mut statuses: BTreeMap = BTreeMap::new(); + let mut found = BTreeMap::new(); + for u in untagged { + match u { + Untagged::Lsub { name, .. } => { + let decoded = + decode_mailbox_name_with(name, utf8_accept).map_err(|e| e.to_string())?; + subscribed.insert(canonicalise_inbox(&decoded)); + } + Untagged::List { + attributes, + delimiter, + name, + } => { + let decoded = + decode_mailbox_name_with(name, utf8_accept).map_err(|e| e.to_string())?; + let canonical = canonicalise_inbox(&decoded); + let attrs_lower: Vec = + attributes.iter().map(|a| a.to_ascii_lowercase()).collect(); + let selectable = !attrs_lower + .iter() + .any(|a| a == "\\noselect" || a == "\\nonexistent"); + let extended_subscribed = attrs_lower.iter().any(|a| a == "\\subscribed"); + if extended_subscribed { + subscribed.insert(canonical.clone()); + } + found.insert( + canonical.clone(), + DiscoveredFolder { + name: canonical, + delimiter: *delimiter, + attributes: attributes.clone(), + subscribed: false, + selectable, + status: None, + }, + ); + } + Untagged::Status { mailbox, items } => { + let decoded = + decode_mailbox_name_with(mailbox, utf8_accept).map_err(|e| e.to_string())?; + let canonical = canonicalise_inbox(&decoded); + let mut st = FolderStatus::default(); + if let Some(v) = items.get("UIDVALIDITY") { + st.uidvalidity = Some(*v); + } + if let Some(v) = items.get("UIDNEXT") { + st.uidnext = Some(*v); + } + if let Some(v) = items.get("MESSAGES") { + st.messages = Some(*v); + } + statuses.insert(canonical, st); + } + _ => {} + } + } + let mut out: Vec = found.into_values().collect(); + for f in &mut out { + if subscribed.contains(&f.name) { + f.subscribed = true; + } + if let Some(s) = statuses.remove(&f.name) { + f.status = Some(s); + } + } + Ok(out) +} + +pub fn apply_filters( + folders: Vec, + filters: &FolderFilters, +) -> Vec { + let mut keep: Vec = folders + .into_iter() + .filter(|f| f.selectable) + .filter(|f| match_filters(&f.name, filters)) + .collect(); + if filters.subscribed_only { + keep.retain(|f| f.subscribed); + } + let mut resolved: Vec = Vec::with_capacity(keep.len()); + for f in keep { + let role = automap::role_for_folder( + &f.name, + &f.attributes, + &filters.namespace_prefix, + filters.automap_enabled, + ); + if let Some(r) = role + && filters + .exclude_special + .iter() + .any(|s| s.eq_ignore_ascii_case(r)) + { + continue; + } + let delim = f.delimiter; + let (leaf, parent_path) = split_parent(&f.name, delim); + resolved.push(ResolvedFolder { + name: f.name, + leaf, + parent_path, + delimiter: delim, + role, + subscribed: f.subscribed, + status: f.status, + }); + } + resolved +} + +fn match_filters(name: &str, filters: &FolderFilters) -> bool { + if !filters.explicit.is_empty() { + return filters.explicit.iter().any(|e| e == name); + } + if !filters.include.is_empty() && !filters.include.iter().any(|r| r.is_match(name)) { + return false; + } + if filters.exclude.iter().any(|r| r.is_match(name)) { + return false; + } + true +} + +fn split_parent(name: &str, delim: Option) -> (String, Option) { + let Some(d) = delim else { + return (name.to_owned(), None); + }; + if let Some(idx) = name.rfind(d) { + let parent = &name[..idx]; + let leaf = &name[idx + d.len_utf8()..]; + (leaf.to_owned(), Some(parent.to_owned())) + } else { + (name.to_owned(), None) + } +} + +pub fn sort_by_depth(resolved: &mut [ResolvedFolder]) { + resolved.sort_by(|a, b| { + let da = a.parent_path.as_deref().map_or(0, |s| { + a.delimiter.map(|d| s.matches(d).count() + 1).unwrap_or(0) + }); + let db = b.parent_path.as_deref().map_or(0, |s| { + b.delimiter.map(|d| s.matches(d).count() + 1).unwrap_or(0) + }); + da.cmp(&db).then_with(|| a.name.cmp(&b.name)) + }); +} + +pub fn vanished_folders(local: &HashMap, server: &HashSet) -> Vec { + local + .keys() + .filter(|k| !server.contains(*k)) + .cloned() + .collect() +} + +pub fn vanished_depth_sort(names: &mut [String], delimiter: char) { + names.sort_by(|a, b| { + let depth_a = a.matches(delimiter).count(); + let depth_b = b.matches(delimiter).count(); + depth_b.cmp(&depth_a).then_with(|| a.cmp(b)) + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn lst(name: &str, delim: &str, attrs: &[&str]) -> Untagged { + Untagged::List { + attributes: attrs.iter().map(|s| (*s).to_owned()).collect(), + delimiter: delim.chars().next(), + name: name.to_owned(), + } + } + + #[test] + fn collect_filters_noselect_and_canonicalises_inbox() { + let resps = vec![ + lst("Inbox", "/", &[]), + lst("Sent", "/", &["\\Sent"]), + lst("Hidden", "/", &["\\Noselect"]), + ]; + let folders = collect_from_list(&resps, false).unwrap(); + assert_eq!(folders.len(), 3); + let inbox = folders.iter().find(|f| f.name == "INBOX").unwrap(); + assert!(inbox.selectable); + let hidden = folders.iter().find(|f| f.name == "Hidden").unwrap(); + assert!(!hidden.selectable); + } + + #[test] + fn collect_marks_subscribed_via_lsub_or_extended() { + let resps = vec![ + lst("Inbox", "/", &[]), + lst("Sent", "/", &["\\Subscribed"]), + Untagged::Lsub { + attributes: vec![], + delimiter: Some('/'), + name: "Inbox".to_owned(), + }, + ]; + let folders = collect_from_list(&resps, false).unwrap(); + let inbox = folders.iter().find(|f| f.name == "INBOX").unwrap(); + assert!(inbox.subscribed); + let sent = folders.iter().find(|f| f.name == "Sent").unwrap(); + assert!(sent.subscribed); + } + + #[test] + fn collect_attaches_list_status_data_per_mailbox() { + let mut inbox_status: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + inbox_status.insert("UIDVALIDITY".into(), 12345); + inbox_status.insert("UIDNEXT".into(), 42); + inbox_status.insert("MESSAGES".into(), 7); + let resps = vec![ + lst("INBOX", "/", &[]), + lst("Sent", "/", &["\\Sent"]), + Untagged::Status { + mailbox: "INBOX".to_owned(), + items: inbox_status, + }, + ]; + let folders = collect_from_list(&resps, false).unwrap(); + let inbox = folders.iter().find(|f| f.name == "INBOX").unwrap(); + let st = inbox.status.as_ref().expect("INBOX has status"); + assert_eq!(st.uidvalidity, Some(12345)); + assert_eq!(st.uidnext, Some(42)); + assert_eq!(st.messages, Some(7)); + + let sent = folders.iter().find(|f| f.name == "Sent").unwrap(); + assert!(sent.status.is_none()); + } + + #[test] + fn collect_decodes_modified_utf7_names() { + let resps = vec![lst("&ZeVnLIqe-", "/", &[])]; + let folders = collect_from_list(&resps, false).unwrap(); + assert_eq!(folders[0].name, "日本語"); + } + + fn filters_default() -> FolderFilters { + FolderFilters { + include: Vec::new(), + exclude: Vec::new(), + exclude_special: Vec::new(), + explicit: Vec::new(), + subscribed_only: false, + automap_enabled: true, + namespace_prefix: String::new(), + } + } + + #[test] + fn filters_default_keep_everything() { + let folders = collect_from_list( + &[ + lst("INBOX", "/", &[]), + lst("Sent", "/", &["\\Sent"]), + lst("Drafts", "/", &[]), + ], + false, + ) + .unwrap(); + let res = apply_filters(folders, &filters_default()); + assert_eq!(res.len(), 3); + let sent = res.iter().find(|f| f.name == "Sent").unwrap(); + assert_eq!(sent.role, Some("sent")); + } + + #[test] + fn include_excludes_non_matching() { + let folders = collect_from_list( + &[ + lst("INBOX", "/", &[]), + lst("Trash", "/", &[]), + lst("Sent", "/", &["\\Sent"]), + ], + false, + ) + .unwrap(); + let mut f = filters_default(); + f.include = vec![Regex::new(r"^(INBOX|Sent)$").unwrap()]; + let res = apply_filters(folders, &f); + assert_eq!(res.len(), 2); + assert!(res.iter().any(|x| x.name == "INBOX")); + assert!(res.iter().any(|x| x.name == "Sent")); + } + + #[test] + fn exclude_drops_matching() { + let folders = + collect_from_list(&[lst("INBOX", "/", &[]), lst("Trash", "/", &[])], false).unwrap(); + let mut f = filters_default(); + f.exclude = vec![Regex::new(r"^Trash$").unwrap()]; + let res = apply_filters(folders, &f); + assert_eq!(res.len(), 1); + assert_eq!(res[0].name, "INBOX"); + } + + #[test] + fn exclude_special_drops_by_role() { + let folders = collect_from_list( + &[lst("INBOX", "/", &[]), lst("Trash", "/", &["\\Trash"])], + false, + ) + .unwrap(); + let mut f = filters_default(); + f.exclude_special = vec!["trash".to_owned()]; + let res = apply_filters(folders, &f); + assert_eq!(res.len(), 1); + assert_eq!(res[0].name, "INBOX"); + } + + #[test] + fn explicit_folder_list_overrides_include_exclude() { + let folders = collect_from_list( + &[ + lst("INBOX", "/", &[]), + lst("Sent", "/", &[]), + lst("Trash", "/", &[]), + ], + false, + ) + .unwrap(); + let mut f = filters_default(); + f.explicit = vec!["Sent".to_owned()]; + let res = apply_filters(folders, &f); + assert_eq!(res.len(), 1); + assert_eq!(res[0].name, "Sent"); + } + + #[test] + fn subscribed_only_filters_unsubscribed() { + let mut folders = collect_from_list( + &[lst("INBOX", "/", &["\\Subscribed"]), lst("Other", "/", &[])], + false, + ) + .unwrap(); + + let mut f = filters_default(); + f.subscribed_only = true; + + for x in &mut folders { + if x.name == "INBOX" { + x.subscribed = true; + } + } + let res = apply_filters(folders, &f); + assert_eq!(res.len(), 1); + assert_eq!(res[0].name, "INBOX"); + } + + #[test] + fn split_parent_handles_root_and_nested() { + assert_eq!(split_parent("INBOX", Some('/')), ("INBOX".to_owned(), None)); + assert_eq!( + split_parent("Projects/Alpha", Some('/')), + ("Alpha".to_owned(), Some("Projects".to_owned())) + ); + assert_eq!( + split_parent("Projects/Alpha/Beta", Some('/')), + ("Beta".to_owned(), Some("Projects/Alpha".to_owned())) + ); + } + + #[test] + fn split_parent_with_no_delimiter_returns_root() { + assert_eq!( + split_parent("Anything", None), + ("Anything".to_owned(), None) + ); + } + + #[test] + fn sort_by_depth_places_roots_first() { + let mut r = vec![ + ResolvedFolder { + name: "Projects/Alpha".into(), + leaf: "Alpha".into(), + parent_path: Some("Projects".into()), + delimiter: Some('/'), + role: None, + subscribed: false, + status: None, + }, + ResolvedFolder { + name: "INBOX".into(), + leaf: "INBOX".into(), + parent_path: None, + delimiter: Some('/'), + role: Some("inbox"), + subscribed: true, + status: None, + }, + ResolvedFolder { + name: "Projects".into(), + leaf: "Projects".into(), + parent_path: None, + delimiter: Some('/'), + role: None, + subscribed: false, + status: None, + }, + ]; + sort_by_depth(&mut r); + assert_eq!(r[0].name, "INBOX"); + assert_eq!(r[1].name, "Projects"); + assert_eq!(r[2].name, "Projects/Alpha"); + } + + #[test] + fn vanished_folders_is_set_minus() { + let mut local = HashMap::new(); + local.insert("INBOX".to_owned(), 1); + local.insert("Old".to_owned(), 2); + local.insert("Sent".to_owned(), 3); + let mut server = HashSet::new(); + server.insert("INBOX".to_owned()); + server.insert("Sent".to_owned()); + let v = vanished_folders(&local, &server); + assert_eq!(v, vec!["Old".to_owned()]); + } + + #[test] + fn vanished_depth_sort_deepest_first() { + let mut names = vec!["A".to_owned(), "A/B/C".to_owned(), "A/B".to_owned()]; + vanished_depth_sort(&mut names, '/'); + assert_eq!(names, vec!["A/B/C", "A/B", "A"]); + } + + #[test] + fn untagged_status_does_not_become_a_folder() { + let resps = vec![ + lst("INBOX", "/", &[]), + Untagged::Status { + mailbox: "INBOX".to_owned(), + items: BTreeMap::new(), + }, + ]; + let folders = collect_from_list(&resps, false).unwrap(); + assert_eq!(folders.len(), 1); + } +} diff --git a/src/sync/import_imap/internaldate.rs b/src/sync/import_imap/internaldate.rs new file mode 100644 index 0000000..039a7dd --- /dev/null +++ b/src/sync/import_imap/internaldate.rs @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; + +pub fn imap_internaldate_to_rfc3339(s: &str) -> Result { + let s = s.trim_start_matches(' ').trim_end(); + let (day, rest) = s + .split_once('-') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: missing day in {s:?}")))?; + let (mon, rest) = rest + .split_once('-') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: missing month in {s:?}")))?; + let (year, rest) = rest + .split_once(' ') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: missing year in {s:?}")))?; + let (time, zone) = rest + .split_once(' ') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: missing zone in {s:?}")))?; + let (h, ms) = time + .split_once(':') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: bad time {time:?}")))?; + let (m, sec) = ms + .split_once(':') + .ok_or_else(|| Error::Partial(format!("INTERNALDATE: bad time {time:?}")))?; + let day: u32 = day + .trim() + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE day {day:?}: {e}")))?; + let mon_idx = month_to_num(mon)?; + let year: i32 = year + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE year {year:?}: {e}")))?; + let h: u32 = h + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE hour {h:?}: {e}")))?; + let m: u32 = m + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE minute {m:?}: {e}")))?; + let sec: u32 = sec + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE second {sec:?}: {e}")))?; + let (sign, hh, mm) = parse_zone(zone)?; + let mut out = String::with_capacity(32); + use std::fmt::Write; + let _ = write!( + &mut out, + "{year:04}-{mon_idx:02}-{day:02}T{h:02}:{m:02}:{sec:02}" + ); + if hh == 0 && mm == 0 { + out.push('Z'); + } else { + let _ = write!(&mut out, "{sign}{hh:02}:{mm:02}"); + } + Ok(out) +} + +fn month_to_num(s: &str) -> Result { + let m = match s { + "Jan" => 1, + "Feb" => 2, + "Mar" => 3, + "Apr" => 4, + "May" => 5, + "Jun" => 6, + "Jul" => 7, + "Aug" => 8, + "Sep" => 9, + "Oct" => 10, + "Nov" => 11, + "Dec" => 12, + other => return Err(Error::Partial(format!("INTERNALDATE month {other:?}"))), + }; + Ok(m) +} + +fn parse_zone(s: &str) -> Result<(char, u32, u32), Error> { + let s = s.trim(); + if s.len() != 5 { + return Err(Error::Partial(format!("INTERNALDATE zone {s:?}"))); + } + let sign = s.as_bytes()[0] as char; + if sign != '+' && sign != '-' { + return Err(Error::Partial(format!("INTERNALDATE zone sign {sign}"))); + } + let hh: u32 = s[1..3] + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE zone hour: {e}")))?; + let mm: u32 = s[3..5] + .parse() + .map_err(|e| Error::Partial(format!("INTERNALDATE zone min: {e}")))?; + Ok((sign, hh, mm)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn utc_zone_becomes_z() { + assert_eq!( + imap_internaldate_to_rfc3339("17-Jul-1996 02:44:25 +0000").unwrap(), + "1996-07-17T02:44:25Z" + ); + } + + #[test] + fn positive_offset_preserved() { + assert_eq!( + imap_internaldate_to_rfc3339("17-Jul-1996 02:44:25 +0200").unwrap(), + "1996-07-17T02:44:25+02:00" + ); + } + + #[test] + fn negative_offset_preserved() { + assert_eq!( + imap_internaldate_to_rfc3339("17-Jul-1996 02:44:25 -0700").unwrap(), + "1996-07-17T02:44:25-07:00" + ); + } + + #[test] + fn single_digit_day_padded() { + assert_eq!( + imap_internaldate_to_rfc3339("5-Jan-2024 09:05:01 +0000").unwrap(), + "2024-01-05T09:05:01Z" + ); + } + + #[test] + fn leading_space_single_digit_day() { + assert_eq!( + imap_internaldate_to_rfc3339(" 5-Jan-2024 09:05:01 +0000").unwrap(), + "2024-01-05T09:05:01Z" + ); + } + + #[test] + fn invalid_month_errors() { + assert!(imap_internaldate_to_rfc3339("1-XYZ-2024 00:00:00 +0000").is_err()); + } + + #[test] + fn malformed_input_errors() { + assert!(imap_internaldate_to_rfc3339("garbage").is_err()); + assert!(imap_internaldate_to_rfc3339("17-Jul-1996").is_err()); + } +} diff --git a/src/sync/import_imap/keywords.rs b/src/sync/import_imap/keywords.rs new file mode 100644 index 0000000..9dcc74f --- /dev/null +++ b/src/sync/import_imap/keywords.rs @@ -0,0 +1,173 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlagAction { + Keep(&'static str), + Drop, + Verbatim, + Deleted, +} + +pub fn translate_system_flag(flag: &str) -> FlagAction { + if !flag.starts_with('\\') { + return FlagAction::Verbatim; + } + match &flag[1..].to_ascii_lowercase()[..] { + "seen" => FlagAction::Keep("$seen"), + "flagged" => FlagAction::Keep("$flagged"), + "answered" => FlagAction::Keep("$answered"), + "draft" => FlagAction::Keep("$draft"), + "recent" => FlagAction::Drop, + "deleted" => FlagAction::Deleted, + _ => FlagAction::Verbatim, + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Translation { + pub keywords: Vec, + pub has_deleted_flag: bool, +} + +pub fn translate_flags(imap_flags: &[String], include_deleted: bool) -> Translation { + let mut out: Vec = Vec::with_capacity(imap_flags.len()); + let mut has_deleted = false; + for f in imap_flags { + if f.is_empty() { + continue; + } + match translate_system_flag(f) { + FlagAction::Keep(k) => { + if !out.iter().any(|x| x.eq_ignore_ascii_case(k)) { + out.push(k.to_owned()); + } + } + FlagAction::Drop => {} + FlagAction::Verbatim => { + if !out.iter().any(|x| x.eq_ignore_ascii_case(f)) { + out.push(f.clone()); + } + } + FlagAction::Deleted => { + has_deleted = true; + if include_deleted && !out.iter().any(|x| x.eq_ignore_ascii_case("$deleted")) { + out.push("$deleted".to_owned()); + } + } + } + } + Translation { + keywords: out, + has_deleted_flag: has_deleted, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn flags(s: &[&str]) -> Vec { + s.iter().map(|x| (*x).to_owned()).collect() + } + + #[test] + fn system_flag_mapping() { + assert!(matches!( + translate_system_flag("\\Seen"), + FlagAction::Keep("$seen") + )); + assert!(matches!( + translate_system_flag("\\Flagged"), + FlagAction::Keep("$flagged") + )); + assert!(matches!( + translate_system_flag("\\Answered"), + FlagAction::Keep("$answered") + )); + assert!(matches!( + translate_system_flag("\\Draft"), + FlagAction::Keep("$draft") + )); + assert!(matches!( + translate_system_flag("\\Recent"), + FlagAction::Drop + )); + assert!(matches!( + translate_system_flag("\\Deleted"), + FlagAction::Deleted + )); + } + + #[test] + fn unknown_system_flag_passes_verbatim() { + assert!(matches!( + translate_system_flag("\\Forwarded"), + FlagAction::Verbatim + )); + assert!(matches!( + translate_system_flag("\\MDNSent"), + FlagAction::Verbatim + )); + } + + #[test] + fn custom_keywords_pass_verbatim_case() { + let t = translate_flags(&flags(&["$Junk", "NotJunk", "MyLabel"]), false); + assert_eq!(t.keywords, vec!["$Junk", "NotJunk", "MyLabel"]); + assert!(!t.has_deleted_flag); + } + + #[test] + fn translates_seen_and_flagged() { + let t = translate_flags(&flags(&["\\Seen", "\\Flagged"]), false); + assert_eq!(t.keywords, vec!["$seen", "$flagged"]); + } + + #[test] + fn drops_recent() { + let t = translate_flags(&flags(&["\\Seen", "\\Recent"]), false); + assert_eq!(t.keywords, vec!["$seen"]); + } + + #[test] + fn deleted_flag_without_include_marks_only() { + let t = translate_flags(&flags(&["\\Deleted", "\\Seen"]), false); + assert!(t.has_deleted_flag); + assert_eq!(t.keywords, vec!["$seen"]); + } + + #[test] + fn deleted_flag_with_include_adds_dollar_deleted() { + let t = translate_flags(&flags(&["\\Deleted", "\\Seen"]), true); + assert!(t.has_deleted_flag); + assert_eq!(t.keywords, vec!["$deleted", "$seen"]); + } + + #[test] + fn duplicate_keywords_deduped_case_insensitively() { + let t = translate_flags(&flags(&["mykey", "MYKEY", "MyKey"]), false); + assert_eq!(t.keywords, vec!["mykey"]); + } + + #[test] + fn empty_flag_strings_ignored() { + let t = translate_flags(&flags(&["", "\\Seen", ""]), false); + assert_eq!(t.keywords, vec!["$seen"]); + } + + #[test] + fn order_preserved() { + let t = translate_flags( + &flags(&["MyLabel", "\\Seen", "$Important", "\\Flagged"]), + false, + ); + assert_eq!( + t.keywords, + vec!["MyLabel", "$seen", "$Important", "$flagged"] + ); + } +} diff --git a/src/sync/import_imap/messages.rs b/src/sync/import_imap/messages.rs new file mode 100644 index 0000000..95249ca --- /dev/null +++ b/src/sync/import_imap/messages.rs @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; + +use crate::imap::client::ImapClient; +use crate::imap::command; +use crate::imap::error::ImapError; +use crate::imap::response::Untagged; +use crate::imap::retry::is_negotiation_failure; + +pub fn select_uids(client: &mut ImapClient) -> Result, ImapError> { + if client.has_capability("ESEARCH") { + match client.run_collect(command::uid_search_esearch_all()) { + Ok(resp) => { + for u in &resp.untagged { + if let Untagged::Esearch { all, .. } = u { + return Ok(all.clone()); + } + } + } + Err(e) if is_negotiation_failure(&e) => {} + Err(e) => return Err(e), + } + } + match client.run_collect(command::uid_search_all()) { + Ok(resp) => { + for u in &resp.untagged { + if let Untagged::Search(uids) = u { + return Ok(uids.clone()); + } + } + Ok(Vec::new()) + } + Err(e) if is_negotiation_failure(&e) => uids_via_fetch(client), + Err(e) => Err(e), + } +} + +fn uids_via_fetch(client: &mut ImapClient) -> Result, ImapError> { + let resp = client.run_collect(command::uid_fetch_all_uids())?; + let mut out = Vec::new(); + for u in resp.untagged { + if let Untagged::Fetch { items, .. } = u { + for (n, v) in items { + if n == "UID" + && let Some(num) = v.as_number() + { + out.push(num as u32); + } + } + } + } + Ok(out) +} + +#[derive(Debug, Clone, Default)] +pub struct UidDiff { + pub new: Vec, + pub vanished: Vec, + pub present: Vec, +} + +pub fn diff_uids(local: &[u32], server: &[u32]) -> UidDiff { + let local_set: HashSet = local.iter().copied().collect(); + let server_set: HashSet = server.iter().copied().collect(); + let mut new = Vec::new(); + let mut present = Vec::new(); + for &uid in server { + if local_set.contains(&uid) { + present.push(uid); + } else { + new.push(uid); + } + } + let mut vanished: Vec = local + .iter() + .copied() + .filter(|u| !server_set.contains(u)) + .collect(); + new.sort_unstable(); + new.dedup(); + vanished.sort_unstable(); + vanished.dedup(); + present.sort_unstable(); + present.dedup(); + UidDiff { + new, + vanished, + present, + } +} + +pub fn chunks(uids: &[u32], chunk_size: usize) -> Vec<&[u32]> { + if chunk_size == 0 { + return vec![uids]; + } + uids.chunks(chunk_size).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn diff_basic() { + let local = vec![1, 2, 5]; + let server = vec![2, 3, 5, 7]; + let d = diff_uids(&local, &server); + assert_eq!(d.new, vec![3, 7]); + assert_eq!(d.vanished, vec![1]); + assert_eq!(d.present, vec![2, 5]); + } + + #[test] + fn diff_empty_local_is_all_new() { + let d = diff_uids(&[], &[1, 2, 3]); + assert_eq!(d.new, vec![1, 2, 3]); + assert!(d.vanished.is_empty()); + assert!(d.present.is_empty()); + } + + #[test] + fn diff_empty_server_is_all_vanished() { + let d = diff_uids(&[1, 2, 3], &[]); + assert_eq!(d.vanished, vec![1, 2, 3]); + assert!(d.new.is_empty()); + assert!(d.present.is_empty()); + } + + #[test] + fn diff_dedups_inputs() { + let d = diff_uids(&[1, 1, 2, 2], &[2, 2, 3, 3]); + assert_eq!(d.new, vec![3]); + assert_eq!(d.vanished, vec![1]); + assert_eq!(d.present, vec![2]); + } + + #[test] + fn chunks_splits_evenly() { + let v: Vec = (1..=10).collect(); + let c = chunks(&v, 3); + assert_eq!(c.len(), 4); + assert_eq!(c[0], &[1, 2, 3][..]); + assert_eq!(c[3], &[10][..]); + } + + #[test] + fn chunks_zero_returns_whole() { + let v: Vec = vec![1, 2, 3]; + let c = chunks(&v, 0); + assert_eq!(c.len(), 1); + assert_eq!(c[0], &v[..]); + } +} diff --git a/src/sync/import_imap/pool.rs b/src/sync/import_imap/pool.rs new file mode 100644 index 0000000..bb3afa4 --- /dev/null +++ b/src/sync/import_imap/pool.rs @@ -0,0 +1,256 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::sync::Arc; +use std::thread; + +use crossbeam_channel::{Receiver, Sender, unbounded}; + +use crate::imap::client::{ConnectMode, ImapClient}; +use crate::imap::command; +use crate::imap::error::ImapError; +use crate::imap::name::encode_mailbox_name_with; +use crate::imap::response::Untagged; +use crate::imap::retry::{BackoffState, Disposition, RetryPolicy, classify}; +use crate::imap::transport::Connector; + +use super::coordinator::{Endpoint, ImapAuth, authenticate_client}; +use super::fetch::FetchAttrs; + +pub const HARD_CAP: usize = 8; + +pub struct FetchJob { + pub folder: String, + pub uidvalidity: u32, + pub uids: Vec, +} + +pub enum FetchEvent { + Item { + folder: String, + uidvalidity: u32, + attrs: FetchAttrs, + }, + ChunkDone { + folder: String, + uidvalidity: u32, + uids_requested: Vec, + outcome: Result<(), ImapError>, + }, +} + +pub struct WorkerArgs { + pub connector: Arc, + pub endpoint: Arc, + pub mode: ConnectMode, + pub auth: ImapAuth, + pub compress: bool, + pub policy: RetryPolicy, + pub backoff: BackoffState, +} + +pub struct WorkerPool { + job_tx: Sender, + event_rx: Receiver, + handles: Vec>, +} + +impl WorkerPool { + pub fn start(args: WorkerArgs, pool_size: usize) -> Result { + let size = pool_size.clamp(1, HARD_CAP); + let (job_tx, job_rx) = unbounded::(); + let (event_tx, event_rx) = unbounded::(); + let mut handles = Vec::with_capacity(size); + let args = Arc::new(args); + + for _ in 0..size { + let args = args.clone(); + let job_rx = job_rx.clone(); + let event_tx = event_tx.clone(); + let handle = thread::spawn(move || { + worker_loop(args, job_rx, event_tx); + }); + handles.push(handle); + } + + Ok(WorkerPool { + job_tx, + event_rx, + handles, + }) + } + + pub fn submit(&self, job: FetchJob) { + let _ = self.job_tx.send(job); + } + + pub fn recv(&self) -> Result { + self.event_rx.recv() + } + + pub fn recv_timeout( + &self, + timeout: std::time::Duration, + ) -> Result { + self.event_rx.recv_timeout(timeout) + } + + pub fn shutdown(self) { + drop(self.job_tx); + for h in self.handles { + let _ = h.join(); + } + } +} + +fn worker_loop(args: Arc, job_rx: Receiver, event_tx: Sender) { + let mut client: Option = None; + let mut current_folder: Option = None; + while let Ok(job) = job_rx.recv() { + let job_folder = job.folder.clone(); + let job_uv = job.uidvalidity; + let job_uids = job.uids.clone(); + let event_tx_for_job = event_tx.clone(); + let outcome = match catch_unwind(AssertUnwindSafe(|| { + run_job_with_retry( + &args, + &mut client, + &mut current_folder, + &job, + &event_tx_for_job, + ) + })) { + Ok(r) => r, + Err(_) => { + client = None; + current_folder = None; + Err(ImapError::Protocol("worker thread panicked".into())) + } + }; + let _ = event_tx.send(FetchEvent::ChunkDone { + folder: job_folder, + uidvalidity: job_uv, + uids_requested: job_uids, + outcome, + }); + } +} + +fn run_job_with_retry( + args: &WorkerArgs, + client_slot: &mut Option, + current_folder: &mut Option, + job: &FetchJob, + event_tx: &Sender, +) -> Result<(), ImapError> { + let mut transient_attempts: u32 = 0; + let mut transport_attempts: u32 = 0; + loop { + if client_slot.is_none() { + match connect_and_auth(args) { + Ok(c) => { + *client_slot = Some(c); + *current_folder = None; + } + Err(e) => { + let disp = classify(&e); + if disp == Disposition::TransportDrop + && transport_attempts < args.policy.max_retries + { + transport_attempts += 1; + std::thread::sleep(args.backoff.transport_delay(transport_attempts)); + continue; + } + return Err(e); + } + } + } + let Some(client) = client_slot.as_mut() else { + return Err(ImapError::Protocol( + "worker pool: client slot empty after connect".into(), + )); + }; + match run_one_job(client, current_folder, job, event_tx) { + Ok(()) => { + args.backoff.reset(); + return Ok(()); + } + Err(e) => match classify(&e) { + Disposition::TransportDrop => { + *client_slot = None; + *current_folder = None; + if transport_attempts >= args.policy.max_retries { + return Err(e); + } + transport_attempts += 1; + std::thread::sleep(args.backoff.transport_delay(transport_attempts)); + } + Disposition::Transient => { + if transient_attempts >= args.policy.max_retries { + return Err(e); + } + transient_attempts += 1; + std::thread::sleep(args.backoff.next_shared_delay()); + } + _ => return Err(e), + }, + } + } +} + +fn connect_and_auth(args: &WorkerArgs) -> Result { + let mut client = ImapClient::connect( + &args.connector, + &args.endpoint.host, + args.endpoint.port, + args.mode, + )?; + authenticate_client(&mut client, &args.auth) + .map_err(|e| ImapError::AuthFailed(e.to_string()))?; + let _ = client.refresh_capabilities(); + if args.compress && client.has_capability("COMPRESS=DEFLATE") { + client.compress_deflate()?; + } + if client.has_capability("ENABLE") && client.has_capability("UTF8=ACCEPT") { + let _ = client.enable(&["UTF8=ACCEPT"]); + } + Ok(client) +} + +fn run_one_job( + client: &mut ImapClient, + current_folder: &mut Option, + job: &FetchJob, + event_tx: &Sender, +) -> Result<(), ImapError> { + if current_folder.as_deref() != Some(job.folder.as_str()) { + let wire = encode_mailbox_name_with(&job.folder, client.utf8_accept()); + client.run_collect(&command::select(&wire))?; + *current_folder = Some(job.folder.clone()); + } + let set = command::format_uid_set(&job.uids, true); + let folder = job.folder.clone(); + let uv = job.uidvalidity; + client.run_streamed( + &command::uid_fetch( + &set, + &["UID", "FLAGS", "INTERNALDATE", "RFC822.SIZE", "BODY.PEEK[]"], + ), + |u| { + if let Untagged::Fetch { .. } = &u + && let Some(attrs) = super::fetch::extract(&u) + { + let _ = event_tx.send(FetchEvent::Item { + folder: folder.clone(), + uidvalidity: uv, + attrs, + }); + } + }, + )?; + Ok(()) +} diff --git a/src/sync/import_jmap.rs b/src/sync/import_jmap.rs new file mode 100644 index 0000000..818e322 --- /dev/null +++ b/src/sync/import_jmap.rs @@ -0,0 +1,1012 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod diff; +pub mod mapping; +pub mod pool; +pub mod tree; + +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +use rusqlite::Connection; +use serde_json::Value; + +use self::diff::diff; +use self::mapping::{BlobIntern, LocalResolver}; +use self::pool::{Pool, effective_workers}; +use self::tree::topo_order; +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::jmap::blobxfer; +use crate::jmap::connect::{self, Connected}; +use crate::jmap::error::JmapError; +use crate::jmap::http::{Auth, HttpClient}; +use crate::jmap::request::{get_all, get_objects, query_all_ids}; +use crate::jmap::session::{Limits, Session}; +use crate::jmap::wire::JmapId; +use crate::jmap::wire::email::Email; +use crate::jmap::wire::file_node::{FileNode, NodeType}; +use crate::jmap::wire::sieve_script::SieveScript; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::sync::{CommonConfig, Context, ImportConfig, Summary, TypeCounts}; +use crate::types::ObjectType; + +const IMPORT_ORDER: [ObjectType; 10] = [ + ObjectType::Mailbox, + ObjectType::AddressBook, + ObjectType::Calendar, + ObjectType::FileNode, + ObjectType::Identity, + ObjectType::SieveScript, + ObjectType::ParticipantIdentity, + ObjectType::Email, + ObjectType::ContactCard, + ObjectType::CalendarEvent, +]; + +fn is_queryless(ty: ObjectType) -> bool { + matches!( + ty, + ObjectType::Identity + | ObjectType::AddressBook + | ObjectType::Calendar + | ObjectType::ParticipantIdentity + ) +} + +fn get_props(ty: ObjectType) -> Option<&'static [&'static str]> { + match ty { + ObjectType::Mailbox => Some(&["name", "parentId", "role", "sortOrder", "isSubscribed"]), + ObjectType::Email => Some(&["blobId", "receivedAt", "mailboxIds", "keywords"]), + ObjectType::SieveScript => Some(&["name", "isActive", "blobId"]), + ObjectType::FileNode => Some(&[ + "parentId", + "nodeType", + "blobId", + "target", + "name", + "type", + "created", + "modified", + "isSubscribed", + "role", + ]), + _ => None, + } +} + +type GetMsg = Result<(Vec, usize), JmapError>; +type BlobRef = (String, String, String); +type DlMsg = (String, Result, JmapError>); + +#[derive(Clone)] +struct Net { + client: HttpClient, + api: String, + account: String, + limits: Limits, + session: Session, +} + +struct DbResolver<'a> { + conn: &'a Connection, + source_id: i64, +} + +impl LocalResolver for DbResolver<'_> { + fn local(&self, ty: ObjectType, jmap_id: &str) -> Option { + db::ids::local_for(self.conn, self.source_id, ty, jmap_id) + .ok() + .flatten() + } +} + +struct PrefetchedBlobs<'a> { + conn: &'a Connection, + bytes: &'a HashMap>, +} + +impl BlobIntern for PrefetchedBlobs<'_> { + fn intern(&mut self, jmap_blob_id: &str) -> Result { + let data = self.bytes.get(jmap_blob_id).ok_or_else(|| { + JmapError::malformed(format!("blob {jmap_blob_id} was not prefetched")) + })?; + Ok(db::blobs::intern_blob(self.conn, data)?) + } +} + +fn collect_blob_ids(value: &Value, out: &mut Vec) { + match value { + Value::Object(map) => { + if let Some(Value::String(b)) = map.get("blobId") { + out.push(b.clone()); + } + for v in map.values() { + collect_blob_ids(v, out); + } + } + Value::Array(items) => { + for v in items { + collect_blob_ids(v, out); + } + } + _ => {} + } +} + +fn username_of(auth: &Auth) -> String { + match auth { + Auth::Basic { user, .. } => user.clone(), + Auth::Bearer { .. } => "(bearer)".to_owned(), + } +} + +pub fn run(common: CommonConfig, config: ImportConfig) -> Result { + let logger = common.logger; + let ctx = Context::open(common, &config.connect)?; + let connected = connect::prepare(&ctx, &config.connect)?; + + let work = work_list(&config, &connected); + let key = SourceKey { + kind: "jmap".to_owned(), + session_url: config.connect.url.clone(), + account_id: connected.account_id.clone(), + }; + + if !ctx.dry_run() + && let Some((url, acc)) = + db::sources::conflicting_source(&ctx.conn, "jmap", &key.session_url, &key.account_id) + .map_err(|e| Error::Partial(e.to_string()))? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records JMAP source ({url}, account {acc}); \ + pass --allow-source-change to import a different account" + ))); + } + + let source_id = if ctx.dry_run() { + db::sources::find_source(&ctx.conn, &key).map_err(|e| Error::Partial(e.to_string()))? + } else { + let account_name = match &config.connect.account { + crate::jmap::account::AccountSelector::Name(n) => Some(n.clone()), + crate::jmap::account::AccountSelector::Id(_) => None, + }; + Some( + db::sources::upsert_source( + &ctx.conn, + &key, + account_name.as_deref(), + &username_of(ctx.client.auth()), + ) + .map_err(|e| Error::Partial(e.to_string()))?, + ) + }; + + let net = Net { + client: ctx.client.clone(), + api: connected.session.api_url.clone(), + account: connected.account_id.clone(), + limits: connected.limits, + session: connected.session.clone(), + }; + + let mut summary = Summary::default(); + let mut dry_rows: Vec<(&'static str, u64, u64, u64)> = Vec::new(); + let threads = ctx.common.threads; + + for ty in work { + if logger.enabled(LEVEL_DEFAULT) { + eprintln!("import: {} ...", ty.jmap_name()); + } + let mut counts = TypeCounts::default(); + match reconcile_type( + &ctx, + &net, + ty, + source_id, + threads, + &logger, + &mut counts, + &mut dry_rows, + ) { + Ok(()) => {} + Err(e) => { + logger.warn(&format!( + "type {} aborted: {e}; continuing (run will exit 5)", + ty.jmap_name() + )); + counts.failed += 1; + } + } + summary.per_type.push((ty.jmap_name(), counts)); + } + + if ctx.dry_run() { + print_dry_run(&dry_rows); + return Ok(Summary::default()); + } + + if !summary.any_failed() + && let Err(e) = run_gc(&ctx.conn) + { + logger.warn(&format!("blob GC skipped: {e}")); + } + + summary.retries_observed = ctx.client.retries_observed(); + summary.retry_after_sleeps = ctx.client.retry_after_sleeps(); + Ok(summary) +} + +fn work_list(config: &ImportConfig, connected: &Connected) -> Vec { + let selected = config.objects.as_ref(); + IMPORT_ORDER + .into_iter() + .filter(|ty| connected.supports(*ty)) + .filter(|ty| selected.map(|s| s.contains(ty)).unwrap_or(true)) + .collect() +} + +#[allow(clippy::too_many_arguments)] +fn reconcile_type( + ctx: &Context, + net: &Net, + ty: ObjectType, + source_id: Option, + threads: usize, + logger: &Logger, + counts: &mut TypeCounts, + dry_rows: &mut Vec<(&'static str, u64, u64, u64)>, +) -> Result<(), Error> { + let local_map: HashMap = match source_id { + Some(sid) => { + db::ids::jmap_to_local(&ctx.conn, sid, ty).map_err(|e| Error::Partial(e.to_string()))? + } + None => HashMap::new(), + }; + let local_ids: HashSet = local_map.keys().cloned().collect(); + + let (server_ids, preloaded): (Vec, Option>) = if is_queryless(ty) { + let got = get_all::(&net.client, &net.api, &net.account, ty.jmap_name()) + .map_err(Error::from)?; + let ids = got + .list + .iter() + .filter_map(|v| { + v.get("id") + .and_then(Value::as_str) + .map(|s| JmapId(s.to_owned())) + }) + .collect(); + (ids, Some(got.list)) + } else { + let ids = query_all_ids( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + &net.limits, + ) + .map_err(Error::from)?; + (ids, None) + }; + + let d = diff(&server_ids, &local_ids); + + if ctx.dry_run() { + dry_rows.push(( + ty.jmap_name(), + d.new.len() as u64, + d.vanished.len() as u64, + d.present.len() as u64, + )); + return Ok(()); + } + + let source_id = source_id.expect("source_id present outside dry-run"); + + if !d.new.is_empty() { + let objects = match preloaded { + Some(list) => { + let want: HashSet<&str> = d.new.iter().map(|i| i.0.as_str()).collect(); + list.into_iter() + .filter(|v| { + v.get("id") + .and_then(Value::as_str) + .map(|s| want.contains(s)) + .unwrap_or(false) + }) + .collect() + } + None => fetch_objects(net, ty, &d.new, threads, logger, counts), + }; + insert_objects(ctx, net, ty, source_id, objects, threads, logger, counts)?; + } + + if !d.vanished.is_empty() { + delete_vanished(&ctx.conn, ty, source_id, &d.vanished, &local_map, logger)?; + counts.deleted += d.vanished.len() as u64; + } + + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "import: {} done (fetched={} deleted={} skipped={} failed={})", + ty.jmap_name(), + counts.fetched, + counts.deleted, + counts.skipped, + counts.failed + ); + } + Ok(()) +} + +fn fetch_objects( + net: &Net, + ty: ObjectType, + new_ids: &[JmapId], + threads: usize, + logger: &Logger, + counts: &mut TypeCounts, +) -> Vec { + let chunk = net.limits.max_objects_in_get.max(1) as usize; + let props = get_props(ty); + let workers = effective_workers(threads, &net.limits, false); + let net = Arc::new(net.clone()); + let pool: Pool, GetMsg> = Pool::new(workers, { + let net = net.clone(); + move |ids: Vec| { + let n = ids.len(); + get_objects::( + &net.client, + &net.api, + &net.account, + ty.jmap_name(), + &ids, + props, + &net.limits, + ) + .map(|r| (r.list, n)) + } + }); + + let mut batches = 0u64; + for c in new_ids.chunks(chunk) { + pool.submit(c.to_vec()); + batches += 1; + } + let mut out = Vec::with_capacity(new_ids.len()); + let mut done = 0u64; + for res in pool.finish() { + match res { + Ok((list, _)) => out.extend(list), + Err(e) => { + logger.warn(&format!("{} /get chunk failed: {e}", ty.jmap_name())); + counts.failed += 1; + } + } + done += 1; + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "import: {} fetched {}/{} chunks", + ty.jmap_name(), + done, + batches + ); + } + out +} + +#[allow(clippy::too_many_arguments)] +fn insert_objects( + ctx: &Context, + net: &Net, + ty: ObjectType, + source_id: i64, + objects: Vec, + threads: usize, + logger: &Logger, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let blob_refs = blob_references(ty, &objects); + let blobs = if blob_refs.is_empty() { + HashMap::new() + } else { + download_blobs(net, blob_refs, threads, logger, counts) + }; + + let existing_parents: HashSet = + if matches!(ty, ObjectType::Mailbox | ObjectType::FileNode) { + db::ids::jmap_to_local(&ctx.conn, source_id, ty) + .map(|m| m.into_keys().collect()) + .map_err(|e| Error::Partial(e.to_string()))? + } else { + HashSet::new() + }; + let (ordered, warnings) = order_objects(ty, &objects, &existing_parents); + for w in &warnings { + logger.warn(w); + } + + for batch in ordered.chunks(200) { + let tx = ctx + .conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + for &idx in batch { + let obj = &objects[idx]; + let jmap_id = match obj.get("id").and_then(Value::as_str) { + Some(s) => s.to_owned(), + None => { + counts.failed += 1; + continue; + } + }; + match insert_one(&tx, ty, source_id, obj, &blobs) { + Ok(local_id) => { + db::ids::insert(&tx, source_id, ty, &jmap_id, local_id) + .map_err(|e| Error::Partial(e.to_string()))?; + counts.fetched += 1; + } + Err(e) => { + logger.warn(&format!("{} {jmap_id} skipped: {e}", ty.jmap_name())); + counts.failed += 1; + } + } + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + } + Ok(()) +} + +fn blob_references(ty: ObjectType, objects: &[Value]) -> Vec { + let mut seen: HashSet = HashSet::new(); + let mut refs = Vec::new(); + match ty { + ObjectType::Email => { + for o in objects { + if let Some(b) = o.get("blobId").and_then(Value::as_str) + && seen.insert(b.to_owned()) + { + refs.push(( + b.to_owned(), + "message/rfc822".to_owned(), + "email".to_owned(), + )); + } + } + } + ObjectType::SieveScript => { + for o in objects { + if let Some(b) = o.get("blobId").and_then(Value::as_str) + && seen.insert(b.to_owned()) + { + refs.push(( + b.to_owned(), + "application/sieve".to_owned(), + "script".to_owned(), + )); + } + } + } + ObjectType::FileNode => { + for o in objects { + if let Some(b) = o.get("blobId").and_then(Value::as_str) + && seen.insert(b.to_owned()) + { + let mt = o + .get("type") + .and_then(Value::as_str) + .unwrap_or("application/octet-stream") + .to_owned(); + let name = o + .get("name") + .and_then(Value::as_str) + .unwrap_or("file") + .to_owned(); + refs.push((b.to_owned(), mt, name)); + } + } + } + ObjectType::ContactCard | ObjectType::CalendarEvent => { + for o in objects { + let mut ids = Vec::new(); + collect_blob_ids(o, &mut ids); + for b in ids { + if seen.insert(b.clone()) { + refs.push((b, "application/octet-stream".to_owned(), "blob".to_owned())); + } + } + } + } + _ => {} + } + refs +} + +fn download_blobs( + net: &Net, + refs: Vec, + threads: usize, + logger: &Logger, + counts: &mut TypeCounts, +) -> HashMap> { + let workers = effective_workers(threads, &net.limits, true); + let net_arc = Arc::new(net.clone()); + let pool: Pool = Pool::new(workers, { + let net = net_arc.clone(); + move |(blob_id, type_hint, name): (String, String, String)| { + let r = blobxfer::download_bytes( + &net.client, + &net.session, + &net.account, + &blob_id, + &type_hint, + &name, + ); + (blob_id, r) + } + }); + let total = refs.len(); + for r in refs { + pool.submit(r); + } + let mut out = HashMap::new(); + for (blob_id, res) in pool.finish() { + match res { + Ok(bytes) => { + out.insert(blob_id, bytes); + } + Err(e) => { + logger.warn(&format!("blob {blob_id} download failed: {e}")); + counts.failed += 1; + } + } + } + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("import: downloaded {}/{} blobs", out.len(), total); + } + out +} + +fn order_objects( + ty: ObjectType, + objects: &[Value], + existing_parents: &HashSet, +) -> (Vec, Vec) { + if !matches!(ty, ObjectType::Mailbox | ObjectType::FileNode) { + return ((0..objects.len()).collect(), Vec::new()); + } + let items: Vec<(String, Option)> = objects + .iter() + .map(|o| { + ( + o.get("id") + .and_then(Value::as_str) + .unwrap_or_default() + .to_owned(), + o.get("parentId").and_then(Value::as_str).map(str::to_owned), + ) + }) + .collect(); + let result = topo_order(&items, existing_parents); + let mut warnings = Vec::new(); + for i in &result.orphans { + let (id, parent) = &items[*i]; + warnings.push(format!( + "{} {id}: parent {} not present in batch or archive; inserting with NULL parent", + ty.jmap_name(), + parent.as_deref().unwrap_or(""), + )); + } + for i in &result.cycle_roots { + warnings.push(format!( + "{} {}: parent cycle detected; broken at this node", + ty.jmap_name(), + items[*i].0, + )); + } + (result.order, warnings) +} + +fn insert_one( + conn: &Connection, + ty: ObjectType, + source_id: i64, + obj: &Value, + blobs: &HashMap>, +) -> Result { + let resolver = DbResolver { conn, source_id }; + match ty { + ObjectType::Mailbox => { + let w = serde_json::from_value(obj.clone())?; + mapping::insert_mailbox(conn, &w, &resolver) + } + ObjectType::Identity => { + let w = serde_json::from_value(obj.clone())?; + mapping::insert_identity(conn, &w) + } + ObjectType::AddressBook => { + let w = serde_json::from_value(obj.clone())?; + mapping::insert_address_book(conn, &w) + } + ObjectType::Calendar => { + let w = serde_json::from_value(obj.clone())?; + mapping::insert_calendar(conn, &w) + } + ObjectType::ParticipantIdentity => { + let w = serde_json::from_value(obj.clone())?; + mapping::insert_participant_identity(conn, &w) + } + ObjectType::Email => { + let w: Email = serde_json::from_value(obj.clone())?; + let data = blobs + .get(&w.blob_id.0) + .ok_or_else(|| JmapError::malformed("email blob missing"))?; + let blob_local = db::blobs::intern_blob(conn, data)?; + let mm = crate::sync::keys::index_to_json( + &crate::sync::emailmeta::email_index_from_blob(data), + ); + mapping::insert_email(conn, &w, blob_local, &mm, &resolver) + } + ObjectType::SieveScript => { + let w: SieveScript = serde_json::from_value(obj.clone())?; + let data = blobs + .get(&w.blob_id.0) + .ok_or_else(|| JmapError::malformed("sieve blob missing"))?; + let blob_local = db::blobs::intern_blob(conn, data)?; + mapping::insert_sieve_script(conn, &w, blob_local) + } + ObjectType::FileNode => { + let w: FileNode = serde_json::from_value(obj.clone())?; + let blob_local = match (&w.node_type, &w.blob_id) { + (NodeType::File, Some(b)) => { + let data = blobs + .get(&b.0) + .ok_or_else(|| JmapError::malformed("file blob missing"))?; + Some(db::blobs::intern_blob(conn, data)?) + } + _ => None, + }; + mapping::insert_file_node(conn, &w, blob_local, &resolver) + } + ObjectType::ContactCard => { + let w = serde_json::from_value(obj.clone())?; + let mut bi = PrefetchedBlobs { conn, bytes: blobs }; + mapping::insert_contact_card(conn, &w, &resolver, &mut bi) + } + ObjectType::CalendarEvent => { + let w = serde_json::from_value(obj.clone())?; + let mut bi = PrefetchedBlobs { conn, bytes: blobs }; + mapping::insert_calendar_event(conn, &w, &resolver, &mut bi) + } + } +} + +fn delete_vanished( + conn: &Connection, + ty: ObjectType, + source_id: i64, + vanished: &[JmapId], + local_map: &HashMap, + logger: &Logger, +) -> Result<(), Error> { + let locals: Vec = vanished + .iter() + .filter_map(|j| local_map.get(&j.0).copied()) + .collect(); + if locals.is_empty() { + return Ok(()); + } + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + let vanished_set: HashSet = locals.iter().copied().collect(); + + match ty { + ObjectType::Mailbox | ObjectType::FileNode => { + let table = if ty == ObjectType::Mailbox { + "mailboxes" + } else { + "file_nodes" + }; + let mut stmt = tx + .prepare(&format!("SELECT id, parent_id FROM {table}")) + .map_err(|e| Error::Partial(e.to_string()))?; + let rows: Vec<(i64, Option)> = stmt + .query_map([], |r| Ok((r.get(0)?, r.get(1)?))) + .and_then(|m| m.collect()) + .map_err(|e| Error::Partial(e.to_string()))?; + drop(stmt); + for (id, parent) in &rows { + if let Some(p) = parent + && vanished_set.contains(p) + && !vanished_set.contains(id) + { + tx.execute( + &format!("UPDATE {table} SET parent_id = NULL WHERE id = ?1"), + rusqlite::params![id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + } + let by: HashMap> = rows.iter().copied().collect(); + let depth = |mut id: i64| -> usize { + let mut d = 0; + let mut seen = HashSet::new(); + while let Some(Some(p)) = by.get(&id) { + if !seen.insert(id) || !vanished_set.contains(p) { + break; + } + d += 1; + id = *p; + } + d + }; + let depths: HashMap = locals.iter().map(|id| (*id, depth(*id))).collect(); + let mut ordered = locals.clone(); + ordered.sort_by(|a, b| depths[b].cmp(&depths[a]).then(a.cmp(b))); + for id in ordered { + tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + rusqlite::params![id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + } + _ => { + let table = crate::sync::table_name(ty); + for id in &locals { + tx.execute( + &format!("DELETE FROM {table} WHERE id = ?1"), + rusqlite::params![id], + ) + .map_err(|e| Error::Partial(e.to_string()))?; + } + } + } + + if let Some((table, column)) = cross_ref_target(ty) { + prune_id_arrays(&tx, table, column, &vanished_set) + .map_err(|e| Error::Partial(e.to_string()))?; + } + + for j in vanished { + db::ids::delete(&tx, source_id, ty, &j.0).map_err(|e| Error::Partial(e.to_string()))?; + } + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "import: {} deleted {} vanished", + ty.jmap_name(), + locals.len() + ); + } + Ok(()) +} + +fn cross_ref_target(ty: ObjectType) -> Option<(&'static str, &'static str)> { + match ty { + ObjectType::Mailbox => Some(("emails", "mailbox_ids")), + ObjectType::Calendar => Some(("calendar_events", "calendar_ids")), + ObjectType::AddressBook => Some(("contact_cards", "address_book_ids")), + _ => None, + } +} + +fn prune_id_arrays( + conn: &Connection, + table: &str, + column: &str, + removed: &HashSet, +) -> Result<(), rusqlite::Error> { + let mut stmt = conn.prepare(&format!("SELECT id, {column} FROM {table}"))?; + let rows: Vec<(i64, String)> = stmt + .query_map([], |r| Ok((r.get(0)?, r.get(1)?)))? + .collect::>()?; + drop(stmt); + for (id, json) in rows { + let arr: Vec = serde_json::from_str(&json).unwrap_or_default(); + if arr.iter().any(|v| removed.contains(v)) { + let kept: Vec = arr.into_iter().filter(|v| !removed.contains(v)).collect(); + let new_json = serde_json::to_string(&kept).unwrap_or_else(|_| "[]".to_owned()); + conn.execute( + &format!("UPDATE {table} SET {column} = ?1 WHERE id = ?2"), + rusqlite::params![new_json, id], + )?; + } + } + Ok(()) +} + +fn run_gc(conn: &Connection) -> Result<(), Error> { + let tx = conn + .unchecked_transaction() + .map_err(|e| Error::Partial(e.to_string()))?; + db::blobs::gc_orphan_blobs(&tx).map_err(|e| Error::Partial(e.to_string()))?; + tx.commit().map_err(|e| Error::Partial(e.to_string()))?; + Ok(()) +} + +fn print_dry_run(rows: &[(&'static str, u64, u64, u64)]) { + println!( + "{:<22} {:>8} {:>10} {:>9}", + "TYPE", "NEW", "VANISHED", "PRESENT" + ); + for (ty, new, vanished, present) in rows { + println!("{ty:<22} {new:>8} {vanished:>10} {present:>9}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + use crate::jmap::wire::JmapId; + use crate::logging::Logger; + + fn mem() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "jmap".to_owned(), + session_url: "u".to_owned(), + account_id: "w".to_owned(), + }, + None, + "u", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn vanished_tree_deletes_leaf_first_and_nulls_true_orphan() { + let (c, sid) = mem(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (1,'root',NULL)", + [], + ) + .unwrap(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (2,'mid',1)", + [], + ) + .unwrap(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (3,'leaf',2)", + [], + ) + .unwrap(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (4,'survivor',2)", + [], + ) + .unwrap(); + for (jid, local) in [("R", 1), ("M", 2), ("L", 3), ("S", 4)] { + db::ids::insert(&c, sid, ObjectType::Mailbox, jid, local).unwrap(); + } + let local_map: HashMap = [ + ("R".to_owned(), 1), + ("M".to_owned(), 2), + ("L".to_owned(), 3), + ] + .into_iter() + .collect(); + let vanished = vec![ + JmapId("R".to_owned()), + JmapId("M".to_owned()), + JmapId("L".to_owned()), + ]; + delete_vanished( + &c, + ObjectType::Mailbox, + sid, + &vanished, + &local_map, + &Logger::from_flags(true, 0), + ) + .unwrap(); + let remaining: i64 = c + .query_row("SELECT count(*) FROM mailboxes", [], |r| r.get(0)) + .unwrap(); + assert_eq!(remaining, 1, "only the surviving child remains"); + let parent: Option = c + .query_row("SELECT parent_id FROM mailboxes WHERE id=4", [], |r| { + r.get(0) + }) + .unwrap(); + assert_eq!(parent, None, "true orphan's parent was nulled"); + assert!( + db::ids::local_for(&c, sid, ObjectType::Mailbox, "L") + .unwrap() + .is_none(), + "vanished sync_id rows removed" + ); + } + + #[test] + fn vanished_mailbox_is_pruned_from_email_mailbox_ids() { + let (c, sid) = mem(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (1,'a',NULL)", + [], + ) + .unwrap(); + c.execute( + "INSERT INTO mailboxes (id,name,parent_id) VALUES (2,'b',NULL)", + [], + ) + .unwrap(); + let blob = db::blobs::intern_blob(&c, b"msg").unwrap(); + c.execute( + "INSERT INTO emails (blob_id,received_at,mailbox_ids,keywords) + VALUES (?1,'2020-01-01T00:00:00Z','[1,2]','[]')", + rusqlite::params![blob], + ) + .unwrap(); + db::ids::insert(&c, sid, ObjectType::Mailbox, "A", 1).unwrap(); + db::ids::insert(&c, sid, ObjectType::Mailbox, "B", 2).unwrap(); + let local_map: HashMap = [("A".to_owned(), 1)].into_iter().collect(); + delete_vanished( + &c, + ObjectType::Mailbox, + sid, + &[JmapId("A".to_owned())], + &local_map, + &Logger::from_flags(true, 0), + ) + .unwrap(); + let ids: String = c + .query_row("SELECT mailbox_ids FROM emails", [], |r| r.get(0)) + .unwrap(); + assert_eq!( + ids, "[2]", + "deleted mailbox id removed from referring email" + ); + } + + #[test] + fn order_objects_does_not_warn_when_parent_already_in_archive() { + let objects = vec![serde_json::json!({"id":"child","parentId":"existing"})]; + let existing: HashSet = ["existing".to_owned()].into_iter().collect(); + let (ordered, warnings) = order_objects(ObjectType::Mailbox, &objects, &existing); + assert_eq!(ordered, vec![0]); + assert!( + warnings.is_empty(), + "no orphan warning expected: {warnings:?}" + ); + } + + #[test] + fn order_objects_warns_on_orphan_parent() { + let objects = vec![serde_json::json!({"id":"orphan","parentId":"ghost"})]; + let (ordered, warnings) = order_objects(ObjectType::Mailbox, &objects, &HashSet::new()); + assert_eq!(ordered, vec![0]); + assert_eq!(warnings.len(), 1); + assert!(warnings[0].contains("ghost"), "msg was: {}", warnings[0]); + } + + #[test] + fn order_objects_warns_on_cycle() { + let objects = vec![ + serde_json::json!({"id":"a","parentId":"b"}), + serde_json::json!({"id":"b","parentId":"a"}), + ]; + let (ordered, warnings) = order_objects(ObjectType::Mailbox, &objects, &HashSet::new()); + assert_eq!(ordered.len(), 2); + assert!( + warnings.iter().any(|w| w.contains("cycle")), + "expected a cycle warning, got: {warnings:?}" + ); + } +} diff --git a/src/sync/import_jmap/diff.rs b/src/sync/import_jmap/diff.rs new file mode 100644 index 0000000..5b6dd7a --- /dev/null +++ b/src/sync/import_jmap/diff.rs @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; + +use crate::jmap::wire::JmapId; + +pub struct IdSetDiff { + pub new: Vec, + pub vanished: Vec, + pub present: Vec, +} + +pub fn diff(server_ids: &[JmapId], local_ids: &HashSet) -> IdSetDiff { + let server_set: HashSet<&str> = server_ids.iter().map(|i| i.0.as_str()).collect(); + let mut seen: HashSet<&str> = HashSet::new(); + let mut new = Vec::new(); + let mut present = Vec::new(); + for id in server_ids { + if !seen.insert(id.0.as_str()) { + continue; + } + if local_ids.contains(&id.0) { + present.push(id.clone()); + } else { + new.push(id.clone()); + } + } + let mut vanished = Vec::new(); + for local in local_ids { + if !server_set.contains(local.as_str()) { + vanished.push(JmapId(local.clone())); + } + } + vanished.sort_by(|a, b| a.0.cmp(&b.0)); + IdSetDiff { + new, + vanished, + present, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ids(v: &[&str]) -> Vec { + v.iter().map(|s| JmapId((*s).to_owned())).collect() + } + + fn locals(v: &[&str]) -> HashSet { + v.iter().map(|s| (*s).to_owned()).collect() + } + + #[test] + fn partitions_new_vanished_present() { + let d = diff(&ids(&["a", "b", "c"]), &locals(&["b", "c", "d"])); + assert_eq!(d.new, ids(&["a"])); + assert_eq!(d.present, ids(&["b", "c"])); + assert_eq!(d.vanished, ids(&["d"])); + } + + #[test] + fn empty_local_means_all_new() { + let d = diff(&ids(&["x", "y"]), &locals(&[])); + assert_eq!(d.new, ids(&["x", "y"])); + assert!(d.vanished.is_empty()); + assert!(d.present.is_empty()); + } + + #[test] + fn empty_server_means_all_vanished() { + let d = diff(&ids(&[]), &locals(&["p", "q"])); + assert!(d.new.is_empty()); + assert_eq!(d.vanished, ids(&["p", "q"])); + } + + #[test] + fn duplicate_server_ids_are_collapsed() { + let d = diff(&ids(&["a", "a", "b"]), &locals(&["a"])); + assert_eq!(d.new, ids(&["b"])); + assert_eq!(d.present, ids(&["a"])); + assert!(d.vanished.is_empty()); + } +} diff --git a/src/sync/import_jmap/mapping.rs b/src/sync/import_jmap/mapping.rs new file mode 100644 index 0000000..4902d33 --- /dev/null +++ b/src/sync/import_jmap/mapping.rs @@ -0,0 +1,1020 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use indexmap::IndexMap; +use rusqlite::{Connection, Row, params}; +use serde_json::{Map, Value}; + +use crate::jmap::blob::{export_blob_ids, import_blob_ids}; +use crate::jmap::error::JmapError; +use crate::jmap::wire::JmapId; +use crate::jmap::wire::address_book::AddressBook; +use crate::jmap::wire::calendar::Calendar; +use crate::jmap::wire::calendar_event::CalendarEvent; +use crate::jmap::wire::contact_card::ContactCard; +use crate::jmap::wire::email::Email; +use crate::jmap::wire::file_node::FileNode; +use crate::jmap::wire::identity::Identity; +use crate::jmap::wire::mailbox::Mailbox; +use crate::jmap::wire::participant_identity::ParticipantIdentity; +use crate::jmap::wire::sieve_script::SieveScript; +use crate::types::ObjectType; + +pub trait LocalResolver { + fn local(&self, ty: ObjectType, jmap_id: &str) -> Option; +} + +pub trait TargetResolver { + fn target(&self, ty: ObjectType, local_id: i64) -> Option; +} + +pub trait BlobIntern { + fn intern(&mut self, jmap_blob_id: &str) -> Result; +} + +pub trait BlobUpload { + fn upload(&mut self, local_id: i64) -> Result; +} + +fn translate_in( + ids: &IndexMap, + ty: ObjectType, + resolver: &impl LocalResolver, +) -> Result, JmapError> { + let mut out = Vec::with_capacity(ids.len()); + for (id, _) in ids { + match resolver.local(ty, &id.0) { + Some(local) => out.push(local), + None => { + return Err(JmapError::malformed(format!( + "unresolved {} reference {} on insert", + ty.jmap_name(), + id.0 + ))); + } + } + } + Ok(out) +} + +fn translate_out( + locals: &[i64], + ty: ObjectType, + resolver: &impl TargetResolver, +) -> Result, JmapError> { + let mut out = IndexMap::new(); + for local in locals { + match resolver.target(ty, *local) { + Some(target) => { + out.insert(target, true); + } + None => { + return Err(JmapError::malformed(format!( + "unresolved local {} id {local} on export", + ty.jmap_name() + ))); + } + } + } + Ok(out) +} + +fn id_array_json(ids: &[i64]) -> String { + let vals: Vec = ids.iter().map(|i| Value::from(*i)).collect(); + Value::Array(vals).to_string() +} + +fn parse_local_id_array(text: &str) -> Result, JmapError> { + let v: Value = serde_json::from_str(text)?; + v.as_array() + .ok_or_else(|| JmapError::malformed("id column is not a JSON array"))? + .iter() + .map(|x| { + x.as_i64() + .ok_or_else(|| JmapError::malformed("id array element is not an integer")) + }) + .collect() +} + +fn opt_parent( + resolver: &impl LocalResolver, + ty: ObjectType, + parent: &Option, +) -> Option { + parent.as_ref().and_then(|p| resolver.local(ty, &p.0)) +} + +pub fn insert_mailbox( + conn: &Connection, + wire: &Mailbox, + resolver: &impl LocalResolver, +) -> Result { + let parent = opt_parent(resolver, ObjectType::Mailbox, &wire.parent_id); + conn.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + wire.name, + parent, + wire.role, + wire.sort_order, + wire.is_subscribed as i64 + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const MAILBOX_SELECT: &str = + "SELECT id, name, parent_id, role, sort_order, is_subscribed FROM mailboxes"; + +pub fn row_to_mailbox(row: &Row, resolver: &impl TargetResolver) -> Result { + let parent_local: Option = row.get(2)?; + let parent_id = match parent_local { + Some(p) => Some( + resolver + .target(ObjectType::Mailbox, p) + .ok_or_else(|| JmapError::malformed("unresolved mailbox parent on export"))?, + ), + None => None, + }; + Ok(Mailbox { + id: None, + name: row.get(1)?, + parent_id, + role: row.get(3)?, + sort_order: row.get::<_, i64>(4)? as u32, + is_subscribed: row.get::<_, i64>(5)? != 0, + }) +} + +pub fn insert_email( + conn: &Connection, + wire: &Email, + blob_local_id: i64, + message_match: &str, + resolver: &impl LocalResolver, +) -> Result { + let mailbox_locals = translate_in(&wire.mailbox_ids, ObjectType::Mailbox, resolver)?; + if mailbox_locals.is_empty() { + return Err(JmapError::malformed("email has no resolvable mailbox")); + } + let keywords: Vec = wire.keywords.keys().cloned().collect(); + conn.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + blob_local_id, + format_utc(&wire.received_at)?, + id_array_json(&mailbox_locals), + Value::Array(keywords.iter().map(|k| Value::from(k.as_str())).collect()).to_string(), + message_match + ], + )?; + Ok(conn.last_insert_rowid()) +} + +#[derive(Debug, Clone)] +pub struct EmailRow { + pub blob_local_id: i64, + pub received_at: String, + pub mailbox_locals: Vec, + pub keywords: Vec, + pub message_match: String, +} + +pub const EMAIL_SELECT: &str = + "SELECT id, blob_id, received_at, mailbox_ids, keywords, message_match FROM emails"; + +pub fn row_to_email(row: &Row) -> Result { + let mailbox_text: String = row.get(3)?; + let keyword_text: String = row.get(4)?; + let keywords: Vec = serde_json::from_str(&keyword_text)?; + Ok(EmailRow { + blob_local_id: row.get(1)?, + received_at: row.get(2)?, + mailbox_locals: parse_local_id_array(&mailbox_text)?, + keywords, + message_match: row.get(5)?, + }) +} + +pub fn insert_identity(conn: &Connection, wire: &Identity) -> Result { + conn.execute( + "INSERT INTO identities (name, email, reply_to, bcc, text_signature, html_signature) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![ + wire.name, + wire.email, + opt_json(&wire.reply_to)?, + opt_json(&wire.bcc)?, + wire.text_signature, + wire.html_signature + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const IDENTITY_SELECT: &str = + "SELECT id, name, email, reply_to, bcc, text_signature, html_signature FROM identities"; + +pub fn row_to_identity(row: &Row) -> Result { + Ok(Identity { + id: None, + name: row.get(1)?, + email: row.get(2)?, + reply_to: from_opt_json(row.get::<_, Option>(3)?)?, + bcc: from_opt_json(row.get::<_, Option>(4)?)?, + text_signature: row.get(5)?, + html_signature: row.get(6)?, + }) +} + +pub fn insert_sieve_script( + conn: &Connection, + wire: &SieveScript, + blob_local_id: i64, +) -> Result { + conn.execute( + "INSERT INTO sieve_scripts (name, is_active, blob_id) VALUES (?1, ?2, ?3)", + params![wire.name, wire.is_active as i64, blob_local_id], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const SIEVE_SELECT: &str = "SELECT id, name, is_active, blob_id FROM sieve_scripts"; + +pub fn insert_address_book(conn: &Connection, wire: &AddressBook) -> Result { + conn.execute( + "INSERT INTO address_books (name, description, sort_order, is_default, is_subscribed) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + wire.name, + wire.description, + wire.sort_order, + wire.is_default as i64, + wire.is_subscribed as i64 + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const ADDRESS_BOOK_SELECT: &str = + "SELECT id, name, description, sort_order, is_default, is_subscribed FROM address_books"; + +pub fn insert_calendar(conn: &Connection, wire: &Calendar) -> Result { + conn.execute( + "INSERT INTO calendars (name, description, color, sort_order, is_subscribed, is_visible, + is_default, include_in_availability, default_alerts_with_time, + default_alerts_without_time, time_zone) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", + params![ + wire.name, + wire.description, + wire.color, + wire.sort_order, + wire.is_subscribed as i64, + wire.is_visible as i64, + wire.is_default as i64, + wire.include_in_availability, + opt_json(&wire.default_alerts_with_time)?, + opt_json(&wire.default_alerts_without_time)?, + wire.time_zone + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const CALENDAR_SELECT: &str = "SELECT id, name, description, color, sort_order, is_subscribed, \ + is_visible, is_default, include_in_availability, default_alerts_with_time, \ + default_alerts_without_time, time_zone FROM calendars"; + +pub fn insert_participant_identity( + conn: &Connection, + wire: &ParticipantIdentity, +) -> Result { + conn.execute( + "INSERT INTO participant_identities (name, calendar_address, is_default) + VALUES (?1, ?2, ?3)", + params![wire.name, wire.calendar_address, wire.is_default as i64], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const PARTICIPANT_IDENTITY_SELECT: &str = + "SELECT id, name, calendar_address, is_default FROM participant_identities"; + +pub fn insert_file_node( + conn: &Connection, + wire: &FileNode, + blob_local_id: Option, + resolver: &impl LocalResolver, +) -> Result { + let parent = opt_parent(resolver, ObjectType::FileNode, &wire.parent_id); + let node_type = serde_json::to_value(wire.node_type)? + .as_str() + .unwrap_or("file") + .to_owned(); + let target = match &wire.target { + Some(t) => Some(serde_json::to_string(t)?), + None => None, + }; + conn.execute( + "INSERT INTO file_nodes (parent_id, node_type, blob_id, target, name, media_type, + created, modified, is_subscribed, role) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", + params![ + parent, + node_type, + blob_local_id, + target, + wire.name, + wire.media_type, + format_utc(&wire.created)?, + opt_format_utc(&wire.modified)?, + wire.is_subscribed as i64, + wire.role + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const FILE_NODE_SELECT: &str = "SELECT id, parent_id, node_type, blob_id, target, name, \ + media_type, created, modified, is_subscribed, role FROM file_nodes"; + +pub fn insert_contact_card( + conn: &Connection, + wire: &ContactCard, + resolver: &impl LocalResolver, + blobs: &mut impl BlobIntern, +) -> Result { + let address_books = translate_in(&wire.address_book_ids, ObjectType::AddressBook, resolver)?; + let mut data = Value::Object(value_map(&wire.rest)); + let uid = take_string(&mut data, "uid") + .ok_or_else(|| JmapError::malformed("ContactCard has no uid"))?; + rewrite_blobs_in(&mut data, blobs)?; + conn.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![uid, id_array_json(&address_books), data.to_string()], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const CONTACT_CARD_SELECT: &str = "SELECT id, uid, address_book_ids, data FROM contact_cards"; + +pub fn insert_calendar_event( + conn: &Connection, + wire: &CalendarEvent, + resolver: &impl LocalResolver, + blobs: &mut impl BlobIntern, +) -> Result { + let calendars = translate_in(&wire.calendar_ids, ObjectType::Calendar, resolver)?; + let mut data = Value::Object(value_map(&wire.rest)); + for drop_key in ["method", "utcStart", "utcEnd", "isOrigin", "baseEventId"] { + if let Value::Object(m) = &mut data { + m.remove(drop_key); + } + } + rewrite_blobs_in(&mut data, blobs)?; + conn.execute( + "INSERT INTO calendar_events (calendar_ids, is_draft, use_default_alerts, data) + VALUES (?1, ?2, ?3, ?4)", + params![ + id_array_json(&calendars), + wire.is_draft as i64, + wire.use_default_alerts as i64, + data.to_string() + ], + )?; + Ok(conn.last_insert_rowid()) +} + +pub const CALENDAR_EVENT_SELECT: &str = "SELECT id, calendar_ids, is_draft, use_default_alerts, data FROM calendar_events \ + WHERE data_type = 'Event'"; + +pub fn contact_card_to_wire( + uid: &str, + address_book_ids: &str, + data: &str, + resolver: &impl TargetResolver, + blobs: &mut impl BlobUpload, +) -> Result { + let mut value: Value = serde_json::from_str(data)?; + restore_blobs_in(&mut value, blobs)?; + let locals = parse_local_id_array(address_book_ids)?; + let abids = translate_out(&locals, ObjectType::AddressBook, resolver)?; + if let Value::Object(map) = &mut value { + map.insert("uid".to_owned(), Value::from(uid)); + map.insert("addressBookIds".to_owned(), id_bool_map(&abids)); + } + Ok(value) +} + +pub fn calendar_event_to_wire( + calendar_ids: &str, + is_draft: bool, + use_default_alerts: bool, + data: &str, + resolver: &impl TargetResolver, + blobs: &mut impl BlobUpload, +) -> Result { + let mut value: Value = serde_json::from_str(data)?; + restore_blobs_in(&mut value, blobs)?; + let locals = parse_local_id_array(calendar_ids)?; + let calids = translate_out(&locals, ObjectType::Calendar, resolver)?; + if let Value::Object(map) = &mut value { + map.insert("calendarIds".to_owned(), id_bool_map(&calids)); + map.insert("isDraft".to_owned(), Value::Bool(is_draft)); + map.insert( + "useDefaultAlerts".to_owned(), + Value::Bool(use_default_alerts), + ); + } + Ok(value) +} + +fn id_bool_map(ids: &IndexMap) -> Value { + let mut m = Map::new(); + for (id, _) in ids { + m.insert(id.0.clone(), Value::Bool(true)); + } + Value::Object(m) +} + +fn value_map(rest: &IndexMap) -> Map { + let mut m = Map::new(); + for (k, v) in rest { + m.insert(k.clone(), v.clone()); + } + m +} + +fn take_string(value: &mut Value, key: &str) -> Option { + if let Value::Object(map) = value + && let Some(Value::String(s)) = map.remove(key) + { + return Some(s); + } + None +} + +fn rewrite_blobs_in(data: &mut Value, blobs: &mut impl BlobIntern) -> Result<(), JmapError> { + import_blob_ids(data, |jmap_blob_id| { + blobs + .intern(jmap_blob_id) + .map_err(|e| crate::jmap::blob::BlobWalkError::Resolver(e.to_string())) + }) + .map_err(JmapError::from) +} + +fn restore_blobs_in(data: &mut Value, blobs: &mut impl BlobUpload) -> Result<(), JmapError> { + export_blob_ids(data, |local_id| { + blobs + .upload(local_id) + .map(|id| id.0) + .map_err(|e| crate::jmap::blob::BlobWalkError::Resolver(e.to_string())) + }) + .map_err(JmapError::from) +} + +fn opt_json(value: &Option) -> Result, JmapError> { + match value { + Some(v) => Ok(Some(serde_json::to_string(v)?)), + None => Ok(None), + } +} + +fn from_opt_json( + text: Option, +) -> Result, JmapError> { + match text { + Some(t) => Ok(Some(serde_json::from_str(&t)?)), + None => Ok(None), + } +} + +pub fn row_to_address_book(row: &Row) -> Result { + Ok(AddressBook { + id: None, + name: row.get(1)?, + description: row.get(2)?, + sort_order: row.get::<_, i64>(3)? as u32, + is_default: row.get::<_, i64>(4)? != 0, + is_subscribed: row.get::<_, i64>(5)? != 0, + }) +} + +pub fn row_to_calendar(row: &Row) -> Result { + Ok(Calendar { + id: None, + name: row.get(1)?, + description: row.get(2)?, + color: row.get(3)?, + sort_order: row.get::<_, i64>(4)? as u32, + is_subscribed: row.get::<_, i64>(5)? != 0, + is_visible: row.get::<_, i64>(6)? != 0, + is_default: row.get::<_, i64>(7)? != 0, + include_in_availability: row.get(8)?, + default_alerts_with_time: from_opt_json(row.get::<_, Option>(9)?)?, + default_alerts_without_time: from_opt_json(row.get::<_, Option>(10)?)?, + time_zone: row.get(11)?, + }) +} + +pub fn row_to_participant_identity(row: &Row) -> Result { + Ok(ParticipantIdentity { + id: None, + name: row.get(1)?, + calendar_address: row.get(2)?, + is_default: row.get::<_, i64>(3)? != 0, + }) +} + +#[derive(Debug, Clone)] +pub struct SieveRow { + pub name: Option, + pub is_active: bool, + pub blob_local_id: i64, +} + +pub fn row_to_sieve_script(row: &Row) -> Result { + Ok(SieveRow { + name: row.get(1)?, + is_active: row.get::<_, i64>(2)? != 0, + blob_local_id: row.get(3)?, + }) +} + +#[derive(Debug, Clone)] +pub struct FileNodeRow { + pub wire: FileNode, + pub blob_local_id: Option, +} + +pub fn row_to_file_node( + row: &Row, + resolver: &impl TargetResolver, +) -> Result { + let parent_local: Option = row.get(1)?; + let parent_id = match parent_local { + Some(p) => Some( + resolver + .target(ObjectType::FileNode, p) + .ok_or_else(|| JmapError::malformed("unresolved file_node parent on export"))?, + ), + None => None, + }; + let node_type = serde_json::from_value(Value::from(row.get::<_, String>(2)?))?; + let target: Option> = from_opt_json(row.get::<_, Option>(4)?)?; + let blob_local_id: Option = row.get(3)?; + Ok(FileNodeRow { + wire: FileNode { + id: None, + parent_id, + node_type, + blob_id: None, + target, + name: row.get(5)?, + media_type: row.get(6)?, + created: parse_utc(&row.get::<_, String>(7)?)?, + modified: opt_parse_utc(row.get::<_, Option>(8)?)?, + is_subscribed: row.get::<_, i64>(9)? != 0, + role: row.get(10)?, + }, + blob_local_id, + }) +} + +fn parse_utc(text: &str) -> Result { + time::OffsetDateTime::parse(text, &time::format_description::well_known::Rfc3339) + .map_err(|e| JmapError::malformed(format!("cannot parse date {text}: {e}"))) +} + +fn opt_parse_utc(text: Option) -> Result, JmapError> { + match text { + Some(t) => Ok(Some(parse_utc(&t)?)), + None => Ok(None), + } +} + +fn format_utc(value: &time::OffsetDateTime) -> Result { + value + .format(&time::format_description::well_known::Rfc3339) + .map_err(|e| JmapError::malformed(format!("cannot format date: {e}"))) +} + +fn opt_format_utc(value: &Option) -> Result, JmapError> { + match value { + Some(v) => Ok(Some(format_utc(v)?)), + None => Ok(None), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use std::collections::HashMap; + + struct MapResolver { + to_local: HashMap<(ObjectType, String), i64>, + to_target: HashMap<(ObjectType, i64), String>, + } + impl LocalResolver for MapResolver { + fn local(&self, ty: ObjectType, jmap_id: &str) -> Option { + self.to_local.get(&(ty, jmap_id.to_owned())).copied() + } + } + impl TargetResolver for MapResolver { + fn target(&self, ty: ObjectType, local_id: i64) -> Option { + self.to_target + .get(&(ty, local_id)) + .map(|s| JmapId(s.clone())) + } + } + + struct FakeBlobs; + impl BlobIntern for FakeBlobs { + fn intern(&mut self, _jmap_blob_id: &str) -> Result { + Ok(42) + } + } + impl BlobUpload for FakeBlobs { + fn upload(&mut self, local_id: i64) -> Result { + Ok(JmapId(format!("T{local_id}"))) + } + } + + fn mem() -> Connection { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + c + } + + #[test] + fn mailbox_roundtrips_with_parent_translation() { + let c = mem(); + let root: Mailbox = serde_json::from_value(serde_json::json!({ + "id": "M1", "name": "Root", "role": "archive", "sortOrder": 3 + })) + .unwrap(); + let empty = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + let root_local = insert_mailbox(&c, &root, &empty).unwrap(); + + let child: Mailbox = serde_json::from_value(serde_json::json!({ + "id": "M2", "name": "Child", "parentId": "M1" + })) + .unwrap(); + let mut res = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + res.to_local + .insert((ObjectType::Mailbox, "M1".to_owned()), root_local); + let child_local = insert_mailbox(&c, &child, &res).unwrap(); + + res.to_target + .insert((ObjectType::Mailbox, root_local), "TGT1".to_owned()); + let wire = c + .query_row( + &format!("{MAILBOX_SELECT} WHERE id = ?1"), + params![child_local], + |row| Ok(row_to_mailbox(row, &res)), + ) + .unwrap() + .unwrap(); + assert_eq!(wire.name, "Child"); + assert_eq!(wire.parent_id, Some(JmapId("TGT1".to_owned()))); + } + + #[test] + fn email_keywords_keep_verbatim_case_and_mailbox_translation() { + let c = mem(); + let mut res = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + res.to_local + .insert((ObjectType::Mailbox, "MB".to_owned()), 7); + let email: Email = serde_json::from_value(serde_json::json!({ + "id": "E1", + "blobId": "B1", + "receivedAt": "2021-05-04T10:00:00Z", + "mailboxIds": { "MB": true }, + "keywords": { "$Seen": true, "MyTag": true } + })) + .unwrap(); + let blob_id = crate::db::blobs::intern_blob(&c, b"rfc5322 bytes").unwrap(); + let local = insert_email(&c, &email, blob_id, "{}", &res).unwrap(); + let got = c + .query_row( + &format!("{EMAIL_SELECT} WHERE id = ?1"), + params![local], + |row| Ok(row_to_email(row)), + ) + .unwrap() + .unwrap(); + assert_eq!(got.blob_local_id, blob_id); + assert_eq!(got.mailbox_locals, vec![7]); + assert_eq!(got.keywords, vec!["$Seen".to_owned(), "MyTag".to_owned()]); + } + + #[test] + fn unresolved_foreign_key_on_insert_is_malformed() { + let c = mem(); + let empty = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + let email: Email = serde_json::from_value(serde_json::json!({ + "id": "E1", + "blobId": "B1", + "receivedAt": "2021-05-04T10:00:00Z", + "mailboxIds": { "UNKNOWN": true }, + "keywords": {} + })) + .unwrap(); + let err = insert_email(&c, &email, 1, "{}", &empty).unwrap_err(); + assert!(matches!(err, JmapError::Malformed(_)), "got {err:?}"); + } + + #[test] + fn unresolved_target_on_export_is_malformed() { + let c = mem(); + let root: Mailbox = serde_json::from_value(serde_json::json!({ + "id": "M1", "name": "Root", "parentId": "P9" + })) + .unwrap(); + let mut res = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + res.to_local + .insert((ObjectType::Mailbox, "P9".to_owned()), 1); + let local = insert_mailbox(&c, &root, &res).unwrap(); + let err = c + .query_row( + &format!("{MAILBOX_SELECT} WHERE id = ?1"), + params![local], + |row| Ok(row_to_mailbox(row, &res)), + ) + .unwrap() + .unwrap_err(); + assert!(matches!(err, JmapError::Malformed(_)), "got {err:?}"); + } + + #[test] + fn contact_card_strips_uid_and_rewrites_blob() { + let c = mem(); + let res = MapResolver { + to_local: HashMap::from([((ObjectType::AddressBook, "AB".to_owned()), 1)]), + to_target: HashMap::from([((ObjectType::AddressBook, 1), "TAB".to_owned())]), + }; + let card: ContactCard = serde_json::from_value(serde_json::json!({ + "id": "C1", + "addressBookIds": { "AB": true }, + "uid": "urn:uuid:42", + "name": { "full": "Jane" }, + "photos": { "p1": { "blobId": "PB", "mediaType": "image/png" } } + })) + .unwrap(); + let mut blobs = FakeBlobs; + let local = insert_contact_card(&c, &card, &res, &mut blobs).unwrap(); + let (uid, abids, data): (String, String, String) = c + .query_row( + &format!("{CONTACT_CARD_SELECT} WHERE id = ?1"), + params![local], + |r| Ok((r.get(1)?, r.get(2)?, r.get(3)?)), + ) + .unwrap(); + assert_eq!(uid, "urn:uuid:42"); + let stored: Value = serde_json::from_str(&data).unwrap(); + assert!(stored.get("uid").is_none()); + assert_eq!(stored["photos"]["p1"]["@blob"], Value::from(42)); + + let mut up = FakeBlobs; + let wire = contact_card_to_wire(&uid, &abids, &data, &res, &mut up).unwrap(); + assert_eq!(wire["uid"], Value::from("urn:uuid:42")); + assert_eq!(wire["addressBookIds"]["TAB"], Value::Bool(true)); + assert_eq!(wire["photos"]["p1"]["blobId"], Value::from("T42")); + assert!(wire["photos"]["p1"].get("@blob").is_none()); + } + + #[test] + fn calendar_event_extracts_columns_and_drops_method() { + let c = mem(); + let res = MapResolver { + to_local: HashMap::from([((ObjectType::Calendar, "CAL".to_owned()), 5)]), + to_target: HashMap::from([((ObjectType::Calendar, 5), "TCAL".to_owned())]), + }; + let ev: CalendarEvent = serde_json::from_value(serde_json::json!({ + "id": "EV1", + "calendarIds": { "CAL": true }, + "isDraft": true, + "useDefaultAlerts": false, + "method": "request", + "utcStart": "2020-01-01T00:00:00Z", + "title": "Sprint", + "@type": "Event" + })) + .unwrap(); + let mut blobs = FakeBlobs; + let local = insert_calendar_event(&c, &ev, &res, &mut blobs).unwrap(); + let (cal, dr, ud, data): (String, i64, i64, String) = c + .query_row( + &format!("{CALENDAR_EVENT_SELECT} AND id = ?1"), + params![local], + |r| Ok((r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)), + ) + .unwrap(); + assert_eq!(dr, 1); + assert_eq!(ud, 0); + let stored: Value = serde_json::from_str(&data).unwrap(); + assert!(stored.get("method").is_none()); + assert!(stored.get("utcStart").is_none()); + assert!(stored.get("calendarIds").is_none()); + assert_eq!(stored["title"], Value::from("Sprint")); + + let mut up = FakeBlobs; + let wire = calendar_event_to_wire(&cal, dr != 0, ud != 0, &data, &res, &mut up).unwrap(); + assert_eq!(wire["calendarIds"]["TCAL"], Value::Bool(true)); + assert_eq!(wire["isDraft"], Value::Bool(true)); + assert_eq!(wire["title"], Value::from("Sprint")); + } + + #[test] + fn identity_json_columns_roundtrip() { + let c = mem(); + let id: Identity = serde_json::from_value(serde_json::json!({ + "id": "I1", + "name": "Alice", + "email": "a@x.test", + "replyTo": [ { "name": "A", "email": "a@x.test" } ] + })) + .unwrap(); + let local = insert_identity(&c, &id).unwrap(); + let wire = c + .query_row( + &format!("{IDENTITY_SELECT} WHERE id = ?1"), + params![local], + |row| Ok(row_to_identity(row)), + ) + .unwrap() + .unwrap(); + assert_eq!(wire.email, "a@x.test"); + assert_eq!(wire.reply_to.unwrap()[0].email, "a@x.test"); + assert!(wire.bcc.is_none()); + } + + #[test] + fn file_node_enum_and_target_roundtrip() { + let c = mem(); + let empty = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + let node: FileNode = serde_json::from_value(serde_json::json!({ + "id": "F1", + "nodeType": "symlink", + "target": ["..", "etc", "hosts"], + "name": "link", + "created": "2022-02-02T02:02:02Z" + })) + .unwrap(); + let local = insert_file_node(&c, &node, None, &empty).unwrap(); + let (nt, tgt): (String, Option) = c + .query_row( + &format!("{FILE_NODE_SELECT} WHERE id = ?1"), + params![local], + |r| Ok((r.get(2)?, r.get(4)?)), + ) + .unwrap(); + assert_eq!(nt, "symlink"); + assert_eq!( + serde_json::from_str::>(&tgt.unwrap()).unwrap(), + vec!["..", "etc", "hosts"] + ); + } + + #[test] + fn address_book_and_calendar_row_to_wire_roundtrip() { + let c = mem(); + let ab: AddressBook = serde_json::from_value(serde_json::json!({ + "id": "A1", "name": "Personal", "description": "d", "sortOrder": 2, + "isDefault": true, "isSubscribed": false + })) + .unwrap(); + let abl = insert_address_book(&c, &ab).unwrap(); + let got = c + .query_row( + &format!("{ADDRESS_BOOK_SELECT} WHERE id = ?1"), + params![abl], + |r| Ok(row_to_address_book(r)), + ) + .unwrap() + .unwrap(); + assert_eq!(got.name, "Personal"); + assert!(got.is_default); + assert!(!got.is_subscribed); + + let cal: Calendar = serde_json::from_value(serde_json::json!({ + "id": "C1", "name": "Work", "color": "#ff0000", "timeZone": "Europe/Rome", + "isVisible": false + })) + .unwrap(); + let cl = insert_calendar(&c, &cal).unwrap(); + let gc = c + .query_row( + &format!("{CALENDAR_SELECT} WHERE id = ?1"), + params![cl], + |r| Ok(row_to_calendar(r)), + ) + .unwrap() + .unwrap(); + assert_eq!(gc.name, "Work"); + assert_eq!(gc.color.as_deref(), Some("#ff0000")); + assert_eq!(gc.time_zone.as_deref(), Some("Europe/Rome")); + assert!(!gc.is_visible); + } + + #[test] + fn participant_identity_and_sieve_row_to_wire_roundtrip() { + let c = mem(); + let pi: ParticipantIdentity = serde_json::from_value(serde_json::json!({ + "id": "P1", "name": "Me", "calendarAddress": "mailto:me@x.test", + "isDefault": true + })) + .unwrap(); + let pil = insert_participant_identity(&c, &pi).unwrap(); + let gp = c + .query_row( + &format!("{PARTICIPANT_IDENTITY_SELECT} WHERE id = ?1"), + params![pil], + |r| Ok(row_to_participant_identity(r)), + ) + .unwrap() + .unwrap(); + assert_eq!(gp.calendar_address, "mailto:me@x.test"); + assert!(gp.is_default); + + let ss: SieveScript = serde_json::from_value(serde_json::json!({ + "id": "S1", "name": "main", "isActive": true, "blobId": "B1" + })) + .unwrap(); + let blob = crate::db::blobs::intern_blob(&c, b"keep;").unwrap(); + let ssl = insert_sieve_script(&c, &ss, blob).unwrap(); + let gs = c + .query_row( + &format!("{SIEVE_SELECT} WHERE id = ?1"), + params![ssl], + |r| Ok(row_to_sieve_script(r)), + ) + .unwrap() + .unwrap(); + assert_eq!(gs.name.as_deref(), Some("main")); + assert!(gs.is_active); + assert_eq!(gs.blob_local_id, blob); + } + + #[test] + fn file_node_row_to_wire_translates_parent() { + let c = mem(); + let mut res = MapResolver { + to_local: HashMap::new(), + to_target: HashMap::new(), + }; + let dir: FileNode = serde_json::from_value(serde_json::json!({ + "id": "D1", "nodeType": "directory", "name": "Docs", + "created": "2022-02-02T02:02:02Z" + })) + .unwrap(); + let dirl = insert_file_node(&c, &dir, None, &res).unwrap(); + res.to_local + .insert((ObjectType::FileNode, "D1".to_owned()), dirl); + let file: FileNode = serde_json::from_value(serde_json::json!({ + "id": "F1", "nodeType": "file", "parentId": "D1", "name": "a.bin", + "type": "application/octet-stream", "created": "2022-02-02T02:02:02Z" + })) + .unwrap(); + let blob = crate::db::blobs::intern_blob(&c, b"bytes").unwrap(); + let fl = insert_file_node(&c, &file, Some(blob), &res).unwrap(); + res.to_target + .insert((ObjectType::FileNode, dirl), "TGTD".to_owned()); + let got = c + .query_row( + &format!("{FILE_NODE_SELECT} WHERE id = ?1"), + params![fl], + |r| Ok(row_to_file_node(r, &res)), + ) + .unwrap() + .unwrap(); + assert_eq!(got.wire.name, "a.bin"); + assert_eq!(got.wire.parent_id, Some(JmapId("TGTD".to_owned()))); + assert_eq!(got.blob_local_id, Some(blob)); + } +} diff --git a/src/sync/import_jmap/pool.rs b/src/sync/import_jmap/pool.rs new file mode 100644 index 0000000..ca86bbc --- /dev/null +++ b/src/sync/import_jmap/pool.rs @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::thread::JoinHandle; + +use crossbeam_channel::{Receiver, Sender, unbounded}; + +use crate::jmap::session::Limits; + +pub fn effective_workers(requested: usize, limits: &Limits, upload: bool) -> usize { + let server_cap = if upload { + limits.max_concurrent_upload + } else { + limits.max_concurrent_requests + }; + let cap = server_cap.max(1).min(usize::MAX as u64) as usize; + requested.clamp(1, cap) +} + +pub struct Pool +where + J: Send + 'static, + R: Send + 'static, +{ + job_tx: Option>, + result_rx: Receiver, + workers: Vec>, +} + +impl Pool +where + J: Send + 'static, + R: Send + 'static, +{ + pub fn new(threads: usize, worker: F) -> Pool + where + F: Fn(J) -> R + Send + Sync + 'static, + { + let threads = threads.max(1); + let (job_tx, job_rx) = unbounded::(); + let (result_tx, result_rx) = unbounded::(); + let worker = std::sync::Arc::new(worker); + let mut workers = Vec::with_capacity(threads); + for _ in 0..threads { + let job_rx = job_rx.clone(); + let result_tx = result_tx.clone(); + let worker = worker.clone(); + workers.push(std::thread::spawn(move || { + while let Ok(job) = job_rx.recv() { + if result_tx.send(worker(job)).is_err() { + break; + } + } + })); + } + Pool { + job_tx: Some(job_tx), + result_rx, + workers, + } + } + + pub fn submit(&self, job: J) { + if let Some(tx) = &self.job_tx { + let _ = tx.send(job); + } + } + + pub fn results(&self) -> &Receiver { + &self.result_rx + } + + pub fn finish(mut self) -> Vec { + drop(self.job_tx.take()); + for handle in self.workers.drain(..) { + let _ = handle.join(); + } + self.result_rx.try_iter().collect() + } +} + +impl Drop for Pool +where + J: Send + 'static, + R: Send + 'static, +{ + fn drop(&mut self) { + drop(self.job_tx.take()); + for handle in self.workers.drain(..) { + let _ = handle.join(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn limits(req: u64, up: u64) -> Limits { + Limits { + max_objects_in_get: 500, + max_objects_in_set: 500, + max_calls_in_request: 16, + max_concurrent_requests: req, + max_concurrent_upload: up, + max_size_request: 10_000_000, + max_size_upload: 50_000_000, + } + } + + #[test] + fn worker_count_is_clamped_to_server_limits() { + let l = limits(4, 2); + assert_eq!(effective_workers(16, &l, false), 4); + assert_eq!(effective_workers(16, &l, true), 2); + assert_eq!(effective_workers(1, &l, false), 1); + assert_eq!(effective_workers(0, &l, false), 1); + } + + #[test] + fn pool_processes_all_jobs_across_workers() { + let pool: Pool = Pool::new(4, |n| n * n); + for n in 0..100 { + pool.submit(n); + } + let mut got = pool.finish(); + got.sort_unstable(); + let expected: Vec = (0..100).map(|n| n * n).collect(); + assert_eq!(got, expected); + } +} diff --git a/src/sync/import_jmap/tree.rs b/src/sync/import_jmap/tree.rs new file mode 100644 index 0000000..77cb554 --- /dev/null +++ b/src/sync/import_jmap/tree.rs @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +pub struct TreeOrder { + pub order: Vec, + pub orphans: Vec, + pub cycle_roots: Vec, +} + +pub fn topo_order( + items: &[(String, Option)], + existing_parents: &HashSet, +) -> TreeOrder { + let index: HashMap<&str, usize> = items + .iter() + .enumerate() + .map(|(i, (id, _))| (id.as_str(), i)) + .collect(); + + let mut order = Vec::with_capacity(items.len()); + let mut orphans = Vec::new(); + let mut cycle_roots = Vec::new(); + let mut placed: HashSet = HashSet::new(); + + loop { + let mut progressed = false; + for (i, (_, parent)) in items.iter().enumerate() { + if placed.contains(&i) { + continue; + } + let ready = match parent { + None => true, + Some(p) => match index.get(p.as_str()) { + Some(&pi) => placed.contains(&pi), + None => { + if !existing_parents.contains(p) { + orphans.push(i); + } + true + } + }, + }; + if ready { + order.push(i); + placed.insert(i); + progressed = true; + } + } + if placed.len() == items.len() { + break; + } + if !progressed { + let mut remaining: Vec = + (0..items.len()).filter(|i| !placed.contains(i)).collect(); + remaining.sort_by(|&a, &b| items[a].0.cmp(&items[b].0)); + let root = remaining[0]; + cycle_roots.push(root); + order.push(root); + placed.insert(root); + } + } + + TreeOrder { + order, + orphans, + cycle_roots, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn item(id: &str, parent: Option<&str>) -> (String, Option) { + (id.to_owned(), parent.map(str::to_owned)) + } + + fn empty() -> HashSet { + HashSet::new() + } + + fn pos(order: &[usize], items: &[(String, Option)], id: &str) -> usize { + order + .iter() + .position(|&i| items[i].0 == id) + .expect("id in order") + } + + #[test] + fn parents_precede_children() { + let items = vec![item("c", Some("b")), item("b", Some("a")), item("a", None)]; + let t = topo_order(&items, &empty()); + assert_eq!(t.order.len(), 3); + assert!(pos(&t.order, &items, "a") < pos(&t.order, &items, "b")); + assert!(pos(&t.order, &items, "b") < pos(&t.order, &items, "c")); + assert!(t.orphans.is_empty()); + assert!(t.cycle_roots.is_empty()); + } + + #[test] + fn parent_not_fetched_and_not_existing_is_orphan() { + let items = vec![item("x", Some("missing"))]; + let t = topo_order(&items, &empty()); + assert_eq!(t.order, vec![0]); + assert_eq!(t.orphans, vec![0]); + } + + #[test] + fn parent_already_in_db_is_not_orphan() { + let items = vec![item("x", Some("p"))]; + let existing: HashSet = ["p".to_owned()].into_iter().collect(); + let t = topo_order(&items, &existing); + assert_eq!(t.order, vec![0]); + assert!(t.orphans.is_empty()); + } + + #[test] + fn cycle_is_broken_by_smallest_id() { + let items = vec![item("b", Some("a")), item("a", Some("b"))]; + let t = topo_order(&items, &empty()); + assert_eq!(t.order.len(), 2); + assert_eq!(t.cycle_roots.len(), 1); + assert_eq!(items[t.cycle_roots[0]].0, "a"); + assert!(pos(&t.order, &items, "a") < pos(&t.order, &items, "b")); + } +} diff --git a/src/sync/import_maildir.rs b/src/sync/import_maildir.rs new file mode 100644 index 0000000..8590d50 --- /dev/null +++ b/src/sync/import_maildir.rs @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod coordinator; +pub mod keywords; +pub mod messages; +pub mod tree; + +pub use coordinator::{MaildirImportConfig, run}; diff --git a/src/sync/import_maildir/coordinator.rs b/src/sync/import_maildir/coordinator.rs new file mode 100644 index 0000000..2ff08f0 --- /dev/null +++ b/src/sync/import_maildir/coordinator.rs @@ -0,0 +1,504 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; + +use regex::Regex; +use rusqlite::{Connection, OptionalExtension, params}; +use url::Url; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::messages; +use super::tree; +use super::tree::{DiscoverError, FolderFilters, ResolvedFolder}; + +pub struct MaildirImportConfig { + pub maildir: PathBuf, + pub include: Vec, + pub exclude: Vec, + pub folder: Vec, + pub automap: bool, + pub include_deleted: bool, + pub allow_source_change: bool, +} + +pub fn run(common: CommonConfig, config: MaildirImportConfig) -> Result { + let logger = common.logger; + if common.threads > 1 { + log_at( + logger, + LEVEL_PROGRESS, + "maildir importer is single-threaded; --threads value will be ignored", + ); + } + + let canonical = std::fs::canonicalize(&config.maildir) + .map_err(|e| Error::Usage(format!("--maildir {:?}: {e}", config.maildir)))?; + + warn_if_looks_like_subfolder(&canonical, logger); + + let discovered = match tree::discover(&canonical, config.automap) { + Ok(v) => v, + Err(DiscoverError::NotFound(p)) => { + return Err(Error::Usage(format!("maildir path does not exist: {p:?}"))); + } + Err(DiscoverError::NotADirectory(p)) => { + return Err(Error::Usage(format!("not a directory: {p:?}"))); + } + Err(DiscoverError::NotAMaildir(p)) => { + return Err(Error::Usage(format!( + "not a Maildir: {p:?} (missing cur/ subdirectory)" + ))); + } + Err(DiscoverError::NotMaildirPlus(name)) => { + return Err(Error::Usage(format!( + "only Maildir++ layout is supported; found non-prefixed subfolder {name:?} \ + (subfolders must be named with a leading '.', e.g. '.{name}')" + ))); + } + Err(DiscoverError::Io(p, e)) => { + return Err(Error::Usage(format!("walking {p:?}: {e}"))); + } + }; + + let include_deleted = config.include_deleted; + let filters = FolderFilters { + include: config.include, + exclude: config.exclude, + explicit: config.folder, + }; + if !filters.explicit.is_empty() && (!filters.include.is_empty() || !filters.exclude.is_empty()) + { + return Err(Error::Usage( + "--folder is mutually exclusive with --include/--exclude".to_owned(), + )); + } + let mut resolved = tree::apply_filters(discovered, &filters); + tree::restore_ephemeral_parents(&mut resolved, &canonical); + let run_flags = RunFlags { include_deleted }; + + let mut conn = db::init::open(&common.archive)?; + let session_url = file_url_for(&canonical)?; + let account_id = canonical.to_string_lossy().into_owned(); + let account_name = canonical + .file_name() + .and_then(|n| n.to_str()) + .map(str::to_owned); + + if let Some((existing_url, existing_account)) = + db::sources::conflicting_source(&conn, "maildir", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records maildir source {existing_url} / {existing_account}; \ + re-run with --allow-source-change or use a fresh archive" + ))); + } + let source_key = SourceKey { + kind: "maildir".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + let source_id = if common.dry_run { + db::sources::find_source(&conn, &source_key)?.unwrap_or(-1) + } else { + db::sources::upsert_source(&conn, &source_key, account_name.as_deref(), "")? + }; + + log_at( + logger, + LEVEL_PROGRESS, + &format!("maildir source {session_url}"), + ); + + if common.dry_run { + let summary = build_dry_run_summary(&conn, source_id, &resolved, logger)?; + return Ok(summary); + } + + let mut mailbox_counts = TypeCounts::default(); + let mut email_counts = TypeCounts::default(); + + let local_mailboxes = db::maildir_ids::mailbox_folders(&conn, source_id)?; + let mailbox_local_ids = upsert_mailboxes(&mut conn, source_id, &resolved, &mut mailbox_counts)?; + + let server_names: HashSet<&str> = resolved.iter().map(|f| f.name.as_str()).collect(); + let mut vanished: Vec = local_mailboxes + .keys() + .filter(|n| !server_names.contains(n.as_str())) + .cloned() + .collect(); + tree::vanished_depth_sort(&mut vanished); + if !vanished.is_empty() { + delete_vanished_folders( + &mut conn, + source_id, + &vanished, + &mut mailbox_counts, + &mut email_counts, + logger, + )?; + } + + for folder in &resolved { + if folder.ephemeral { + continue; + } + let mailbox_local = match mailbox_local_ids.get(&folder.name).copied() { + Some(id) => id, + None => { + log_at( + logger, + LEVEL_DEFAULT, + &format!("folder {:?}: missing from mailbox id map", folder.name), + ); + email_counts.failed += 1; + continue; + } + }; + if let Err(e) = reconcile_folder( + &mut conn, + source_id, + folder, + mailbox_local, + &run_flags, + &mut email_counts, + logger, + ) { + log_at( + logger, + LEVEL_DEFAULT, + &format!("folder {:?}: {e}", folder.name), + ); + email_counts.failed += 1; + } + } + + if email_counts.failed == 0 && mailbox_counts.failed == 0 { + let tx = conn.unchecked_transaction()?; + db::blobs::gc_orphan_blobs(&tx)?; + tx.commit()?; + } + + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "maildir: mailbox created={} deleted={} skipped={} failed={}; \ + email created={} fetched={} deleted={} skipped={} failed={}", + mailbox_counts.created, + mailbox_counts.deleted, + mailbox_counts.skipped, + mailbox_counts.failed, + email_counts.created, + email_counts.fetched, + email_counts.deleted, + email_counts.skipped, + email_counts.failed + ), + ); + + Ok(Summary { + per_type: vec![("mailbox", mailbox_counts), ("email", email_counts)], + retries_observed: 0, + retry_after_sleeps: 0, + }) +} + +struct RunFlags { + include_deleted: bool, +} + +fn file_url_for(p: &Path) -> Result { + Url::from_file_path(p).map(|u| u.to_string()).map_err(|_| { + Error::Usage(format!( + "cannot encode maildir path as a file:// URL: {p:?}" + )) + }) +} + +fn warn_if_looks_like_subfolder(canonical: &Path, logger: Logger) { + let Some(basename) = canonical.file_name().and_then(|n| n.to_str()) else { + return; + }; + if !basename.starts_with('.') { + return; + } + let Some(parent) = canonical.parent() else { + return; + }; + if !parent.join("cur").is_dir() { + return; + } + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "warning: {canonical:?} looks like a Maildir++ subfolder of {parent:?} \ + (basename starts with '.' and the parent has its own cur/); proceeding, \ + but you may have meant to import the parent" + ), + ); +} + +fn upsert_mailboxes( + conn: &mut Connection, + source_id: i64, + folders: &[ResolvedFolder], + counts: &mut TypeCounts, +) -> Result, Error> { + let tx = conn.transaction()?; + let mut local_ids: HashMap = HashMap::new(); + for folder in folders { + let parent_local = match folder.parent_path.as_deref() { + Some(p) => local_ids.get(p).copied().or_else(|| { + db::maildir_ids::local_for_mailbox(&tx, source_id, p) + .ok() + .flatten() + }), + None => None, + }; + let is_subscribed: i64 = if folder.ephemeral { 0 } else { 1 }; + let existing = db::maildir_ids::local_for_mailbox(&tx, source_id, &folder.name)?; + let id = if let Some(id) = existing { + tx.execute( + "UPDATE mailboxes SET name = ?1, parent_id = ?2, role = ?3, is_subscribed = ?4 + WHERE id = ?5", + params![folder.leaf, parent_local, folder.role, is_subscribed, id], + )?; + id + } else { + tx.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, 0, ?4)", + params![folder.leaf, parent_local, folder.role, is_subscribed], + )?; + let new_id = tx.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&tx, source_id, &folder.name, new_id)?; + counts.created += 1; + counts.fetched += 1; + new_id + }; + local_ids.insert(folder.name.clone(), id); + } + tx.commit()?; + Ok(local_ids) +} + +fn delete_vanished_folders( + conn: &mut Connection, + source_id: i64, + folders: &[String], + mailbox_counts: &mut TypeCounts, + email_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let tx = conn.transaction()?; + for name in folders { + let local_id = match db::maildir_ids::local_for_mailbox(&tx, source_id, name)? { + Some(id) => id, + None => continue, + }; + let surviving_child: Option = tx + .query_row( + "SELECT id FROM mailboxes WHERE parent_id = ?1 LIMIT 1", + params![local_id], + |row| row.get(0), + ) + .optional()?; + if surviving_child.is_some() { + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "folder {name:?}: vanished from disk but still has child mailboxes in the \ + archive; skipping delete (parent_id RESTRICT)" + ), + ); + mailbox_counts.failed += 1; + continue; + } + let email_ids: Vec = tx + .prepare( + "SELECT local_id FROM sync_id_maildir + WHERE source_id = ?1 AND type_name = 'email' AND folder = ?2", + )? + .query_map(params![source_id, name], |row| row.get(0))? + .collect::, _>>()?; + for eid in &email_ids { + tx.execute("DELETE FROM emails WHERE id = ?1", params![eid])?; + email_counts.deleted += 1; + } + db::maildir_ids::delete_all_emails_in_folder(&tx, source_id, name)?; + tx.execute("DELETE FROM mailboxes WHERE id = ?1", params![local_id])?; + db::maildir_ids::delete_mailbox(&tx, source_id, name)?; + mailbox_counts.deleted += 1; + } + tx.commit()?; + Ok(()) +} + +fn reconcile_folder( + conn: &mut Connection, + source_id: i64, + folder: &ResolvedFolder, + mailbox_local: i64, + flags: &RunFlags, + counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let listing = match messages::list_folder(&folder.path) { + Ok(l) => l, + Err(e) => { + return Err(Error::Partial(format!( + "list {folder:?}: {e}", + folder = folder.path + ))); + } + }; + if listing.io_failures > 0 { + counts.failed += listing.io_failures; + } + let local = db::maildir_ids::email_ids_in_folder(conn, source_id, &folder.name)?; + let diff = messages::diff(listing.entries, &local); + + log_at( + logger, + LEVEL_PROGRESS, + &format!( + "folder {:?}: new={} present={} vanished={}", + folder.name, + diff.new.len(), + diff.present.len(), + diff.vanished.len() + ), + ); + + let ctx = messages::InsertContext { + source_id, + folder: &folder.name, + mailbox_local, + include_deleted: flags.include_deleted, + }; + messages::apply_folder(conn, ctx, diff, counts, logger)?; + Ok(()) +} + +fn build_dry_run_summary( + conn: &Connection, + source_id: i64, + folders: &[ResolvedFolder], + logger: Logger, +) -> Result { + let mut mailbox = TypeCounts::default(); + let mut email = TypeCounts::default(); + let local_mailboxes = if source_id < 0 { + HashMap::new() + } else { + db::maildir_ids::mailbox_folders(conn, source_id)? + }; + let server_set: HashSet<&str> = folders.iter().map(|f| f.name.as_str()).collect(); + let new_folders: Vec<&str> = folders + .iter() + .map(|f| f.name.as_str()) + .filter(|n| !local_mailboxes.contains_key(*n)) + .collect(); + let vanished_folder_names: Vec<&str> = local_mailboxes + .keys() + .filter(|n| !server_set.contains(n.as_str())) + .map(|s| s.as_str()) + .collect(); + mailbox.created += new_folders.len() as u64; + mailbox.fetched += new_folders.len() as u64; + mailbox.deleted += vanished_folder_names.len() as u64; + + for folder in folders { + if folder.ephemeral { + continue; + } + let listing = match messages::list_folder(&folder.path) { + Ok(l) => l, + Err(e) => { + logger.warn(&format!("dry-run list {:?}: {e}", folder.name)); + email.failed += 1; + continue; + } + }; + email.failed += listing.io_failures; + let disk_ids: HashSet = listing + .entries + .iter() + .map(|e| e.unique_id.clone()) + .collect(); + let local = if source_id < 0 { + HashMap::new() + } else { + db::maildir_ids::email_ids_in_folder(conn, source_id, &folder.name)? + }; + let local_keys: HashSet<&String> = local.keys().collect(); + let new_count = disk_ids.iter().filter(|u| !local.contains_key(*u)).count() as u64; + let vanished_count = local_keys + .iter() + .filter(|k| !disk_ids.contains(k.as_str())) + .count() as u64; + let present_count = disk_ids.iter().filter(|u| local.contains_key(*u)).count() as u64; + email.created += new_count; + email.fetched += new_count; + email.deleted += vanished_count; + email.skipped += present_count; + } + for name in &vanished_folder_names { + if source_id >= 0 { + let n = db::maildir_ids::email_ids_in_folder(conn, source_id, name)?.len() as u64; + email.deleted += n; + } + } + + Ok(Summary { + per_type: vec![("mailbox", mailbox), ("email", email)], + retries_observed: 0, + retry_after_sleeps: 0, + }) +} + +fn log_at(logger: Logger, level: u8, msg: &str) { + if logger.enabled(level) { + eprintln!("{msg}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn file_url_for_ascii_path_is_passthrough() { + let p = std::path::Path::new("/home/alice/Maildir"); + let url = file_url_for(p).unwrap(); + assert_eq!(url, "file:///home/alice/Maildir"); + } + + #[test] + fn file_url_for_percent_encodes_spaces_and_unicode() { + let p = std::path::Path::new("/home/alice/My Maildir/Sübfolder"); + let url = file_url_for(p).unwrap(); + assert!(url.starts_with("file:///home/alice/My%20Maildir/")); + assert!(url.contains("S%C3%BCbfolder")); + } + + #[test] + fn file_url_for_rejects_relative_paths() { + let p = std::path::Path::new("./relative"); + let err = file_url_for(p).unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } +} diff --git a/src/sync/import_maildir/keywords.rs b/src/sync/import_maildir/keywords.rs new file mode 100644 index 0000000..c0e74be --- /dev/null +++ b/src/sync/import_maildir/keywords.rs @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use mail_parser::mailbox::maildir::Flag; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Translation { + pub keywords: Vec, + pub has_trashed_flag: bool, +} + +pub fn translate_flags(flags: &[Flag], include_deleted: bool) -> Translation { + let mut out: Vec = Vec::new(); + let mut has_trashed = false; + for f in flags { + match f { + Flag::Passed => push_unique(&mut out, "$forwarded"), + Flag::Replied => push_unique(&mut out, "$answered"), + Flag::Seen => push_unique(&mut out, "$seen"), + Flag::Draft => push_unique(&mut out, "$draft"), + Flag::Flagged => push_unique(&mut out, "$flagged"), + Flag::Trashed => { + has_trashed = true; + if include_deleted { + push_unique(&mut out, "$deleted"); + } + } + } + } + out.sort(); + Translation { + keywords: out, + has_trashed_flag: has_trashed, + } +} + +fn push_unique(out: &mut Vec, k: &str) { + if !out.iter().any(|x| x == k) { + out.push(k.to_owned()); + } +} + +pub fn flags_from_filename(filename: &str) -> Vec { + let Some(part) = filename.rsplit_once("2,").map(|(_, s)| s) else { + return Vec::new(); + }; + let mut out = Vec::new(); + for &ch in part.as_bytes() { + match ch { + b'P' => out.push(Flag::Passed), + b'R' => out.push(Flag::Replied), + b'S' => out.push(Flag::Seen), + b'T' => out.push(Flag::Trashed), + b'D' => out.push(Flag::Draft), + b'F' => out.push(Flag::Flagged), + other => { + if !other.is_ascii_alphanumeric() { + break; + } + } + } + } + out +} + +pub fn unique_id_from_filename(filename: &str) -> &str { + match filename.split_once(':') { + Some((unique, _)) => unique, + None => filename, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_flags_yields_empty_keywords() { + let t = translate_flags(&[], false); + assert!(t.keywords.is_empty()); + assert!(!t.has_trashed_flag); + } + + #[test] + fn each_flag_maps_to_expected_jmap_keyword() { + let t = translate_flags( + &[ + Flag::Passed, + Flag::Replied, + Flag::Seen, + Flag::Draft, + Flag::Flagged, + ], + false, + ); + let mut expected = vec!["$forwarded", "$answered", "$seen", "$draft", "$flagged"]; + expected.sort(); + let expected: Vec = expected.into_iter().map(str::to_owned).collect(); + assert_eq!(t.keywords, expected); + assert!(!t.has_trashed_flag); + } + + #[test] + fn trashed_dropped_by_default_marks_flag() { + let t = translate_flags(&[Flag::Trashed, Flag::Seen], false); + assert!(t.has_trashed_flag); + assert_eq!(t.keywords, vec!["$seen".to_owned()]); + } + + #[test] + fn trashed_with_include_deleted_adds_dollar_deleted() { + let t = translate_flags(&[Flag::Trashed, Flag::Seen], true); + assert!(t.has_trashed_flag); + let mut expected = vec!["$seen", "$deleted"]; + expected.sort(); + let expected: Vec = expected.into_iter().map(str::to_owned).collect(); + assert_eq!(t.keywords, expected); + } + + #[test] + fn order_independent_keywords_are_stable() { + let a = translate_flags(&[Flag::Seen, Flag::Flagged], false); + let b = translate_flags(&[Flag::Flagged, Flag::Seen], false); + assert_eq!(a.keywords, b.keywords); + } + + #[test] + fn duplicate_flags_dedup() { + let t = translate_flags(&[Flag::Seen, Flag::Seen, Flag::Seen], false); + assert_eq!(t.keywords, vec!["$seen".to_owned()]); + } + + #[test] + fn unique_id_strips_at_first_colon() { + assert_eq!( + unique_id_from_filename("1739471123.M001P01234V0.host:2,RS"), + "1739471123.M001P01234V0.host" + ); + } + + #[test] + fn unique_id_no_colon_is_whole_basename() { + assert_eq!( + unique_id_from_filename("1739471123.M001P0.host"), + "1739471123.M001P0.host" + ); + } + + #[test] + fn unique_id_strips_legacy_one_form() { + assert_eq!( + unique_id_from_filename("1234.M5.host:1,experimental"), + "1234.M5.host" + ); + } + + #[test] + fn flags_from_filename_recognises_full_set() { + let mut flags = flags_from_filename("name:2,DFPRST"); + flags.sort(); + let mut want = vec![ + Flag::Draft, + Flag::Flagged, + Flag::Passed, + Flag::Replied, + Flag::Seen, + Flag::Trashed, + ]; + want.sort(); + assert_eq!(flags, want); + } + + #[test] + fn flags_from_filename_no_info_section_yields_empty() { + assert!(flags_from_filename("plain").is_empty()); + assert!(flags_from_filename("name:").is_empty()); + assert!(flags_from_filename("name:1,X").is_empty()); + } + + #[test] + fn flags_from_filename_dovecot_extension_metadata_passes_through() { + + let mut flags = flags_from_filename("uid,S=1234,W=1300:2,RS"); + flags.sort(); + let mut want = vec![Flag::Replied, Flag::Seen]; + want.sort(); + assert_eq!(flags, want); + } + + #[test] + fn flags_from_filename_unknown_alpha_chars_skipped() { + + let mut flags = flags_from_filename("uid:2,SaT"); + flags.sort(); + let mut want = vec![Flag::Seen, Flag::Trashed]; + want.sort(); + assert_eq!(flags, want); + } +} diff --git a/src/sync/import_maildir/messages.rs b/src/sync/import_maildir/messages.rs new file mode 100644 index 0000000..e381169 --- /dev/null +++ b/src/sync/import_maildir/messages.rs @@ -0,0 +1,726 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::time::{Duration, UNIX_EPOCH}; + +use mail_parser::mailbox::maildir::Flag; +use rusqlite::{Connection, params}; +use serde_json::Value; +use time::OffsetDateTime; +use time::format_description::well_known::Rfc3339; + +use crate::db; +use crate::sync::TypeCounts; +use crate::sync::emailmeta::email_index_from_blob; +use crate::sync::keys::index_to_json; + +use super::keywords::{Translation, flags_from_filename, translate_flags, unique_id_from_filename}; + +#[derive(Debug, Clone)] +pub struct DiskEntry { + pub unique_id: String, + pub filename: String, + pub path: PathBuf, + pub flags: Vec, + pub mtime_unix: u64, +} + +#[derive(Debug, Clone, Default)] +pub struct DiskListing { + pub entries: Vec, + pub io_failures: u64, +} + +pub fn list_folder(folder_path: &Path) -> std::io::Result { + let mut out = DiskListing::default(); + let mut seen: std::collections::HashMap = std::collections::HashMap::new(); + for sub in ["cur", "new"] { + let dir = folder_path.join(sub); + if !dir.is_dir() { + continue; + } + let entries = std::fs::read_dir(&dir)?; + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(_) => { + out.io_failures += 1; + continue; + } + }; + let path = entry.path(); + let file_type = match entry.file_type() { + Ok(t) => t, + Err(_) => { + out.io_failures += 1; + continue; + } + }; + if !file_type.is_file() { + continue; + } + let Some(raw_name) = path.file_name() else { + continue; + }; + let filename: String = raw_name.to_string_lossy().into_owned(); + if filename.starts_with('.') { + continue; + } + let unique_id = unique_id_from_filename(&filename).to_owned(); + if unique_id.is_empty() { + continue; + } + if seen.contains_key(&unique_id) { + + continue; + } + let flags = flags_from_filename(&filename); + let mtime_unix = match entry.metadata().and_then(|m| m.modified()).and_then(|t| { + t.duration_since(UNIX_EPOCH).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) + }) + }) { + Ok(d) => d.as_secs(), + Err(_) => 0, + }; + seen.insert(unique_id.clone(), out.entries.len()); + out.entries.push(DiskEntry { + unique_id, + filename, + path, + flags, + mtime_unix, + }); + } + } + Ok(out) +} + +#[derive(Debug, Clone, Default)] +pub struct FolderDiff { + pub new: Vec, + pub present: Vec<(DiskEntry, i64)>, + pub vanished: Vec<(String, i64)>, +} + +pub fn diff(disk: Vec, local: &HashMap) -> FolderDiff { + let mut diff = FolderDiff::default(); + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + for entry in disk { + match local.get(&entry.unique_id) { + None => { + seen.insert(entry.unique_id.clone()); + diff.new.push(entry); + } + Some(&local_id) => { + seen.insert(entry.unique_id.clone()); + diff.present.push((entry, local_id)); + } + } + } + for (uid, local_id) in local { + if !seen.contains(uid) { + diff.vanished.push((uid.clone(), *local_id)); + } + } + diff.vanished.sort_by(|a, b| a.0.cmp(&b.0)); + diff +} + +#[derive(Clone, Copy)] +pub struct InsertContext<'a> { + pub source_id: i64, + pub folder: &'a str, + pub mailbox_local: i64, + pub include_deleted: bool, +} + +pub fn insert_new( + tx: &rusqlite::Transaction<'_>, + ctx: InsertContext<'_>, + entry: &DiskEntry, +) -> Result, InsertError> { + let bytes = std::fs::read(&entry.path).map_err(InsertError::Io)?; + let translation = translate_flags(&entry.flags, ctx.include_deleted); + if translation.has_trashed_flag && !ctx.include_deleted { + return Ok(None); + } + let received_at = format_unix_rfc3339(entry.mtime_unix); + let blob_id = db::blobs::intern_blob(tx, &bytes)?; + let message_match = index_to_json(&email_index_from_blob(&bytes)); + let mailbox_ids = Value::Array(vec![Value::from(ctx.mailbox_local)]); + let keywords = keywords_json(&translation); + tx.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + blob_id, + received_at, + mailbox_ids.to_string(), + keywords, + message_match + ], + )?; + let local_id = tx.last_insert_rowid(); + db::maildir_ids::insert_email(tx, ctx.source_id, ctx.folder, &entry.unique_id, local_id)?; + Ok(Some(local_id)) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PresentOutcome { + + Unchanged, + + KeywordsUpdated, + + DeleteRequested, +} + +pub fn refresh_flags( + tx: &rusqlite::Transaction<'_>, + local_id: i64, + entry: &DiskEntry, + stored_keywords: &str, + include_deleted: bool, +) -> Result { + let translation = translate_flags(&entry.flags, include_deleted); + if translation.has_trashed_flag && !include_deleted { + return Ok(PresentOutcome::DeleteRequested); + } + let expected = keywords_json(&translation); + if stored_keywords == expected { + return Ok(PresentOutcome::Unchanged); + } + tx.execute( + "UPDATE emails SET keywords = ?1 WHERE id = ?2", + params![expected, local_id], + )?; + Ok(PresentOutcome::KeywordsUpdated) +} + +pub fn load_present_keywords( + conn: &Connection, + source_id: i64, + folder: &str, +) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare( + "SELECT e.id, e.keywords FROM emails e + JOIN sync_id_maildir m ON m.local_id = e.id AND m.type_name = 'email' + WHERE m.source_id = ?1 AND m.folder = ?2", + )?; + let rows = stmt.query_map(params![source_id, folder], |row| { + Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)) + })?; + let mut out = HashMap::new(); + for r in rows { + let (id, kw) = r?; + out.insert(id, kw); + } + Ok(out) +} + +pub fn delete_vanished( + tx: &rusqlite::Transaction<'_>, + source_id: i64, + folder: &str, + unique_id: &str, + local_id: i64, +) -> Result<(), rusqlite::Error> { + tx.execute("DELETE FROM emails WHERE id = ?1", params![local_id])?; + db::maildir_ids::delete_email(tx, source_id, folder, unique_id)?; + Ok(()) +} + +const PROGRESS_TICK: u64 = 1000; + +pub fn apply_folder( + conn: &mut Connection, + ctx: InsertContext<'_>, + diff: FolderDiff, + counts: &mut TypeCounts, + logger: crate::logging::Logger, +) -> Result<(), crate::error::Error> { + let stored_keywords = + load_present_keywords(conn, ctx.source_id, ctx.folder).unwrap_or_default(); + let tx = conn.transaction()?; + let total_new = diff.new.len() as u64; + let mut inserted: u64 = 0; + for entry in &diff.new { + match insert_new(&tx, ctx, entry) { + Ok(Some(_)) => { + counts.created += 1; + counts.fetched += 1; + inserted += 1; + if inserted.is_multiple_of(PROGRESS_TICK) + && logger.enabled(crate::logging::LEVEL_PROGRESS) + { + eprintln!( + "folder {folder:?}: inserted {inserted}/{total_new}", + folder = ctx.folder + ); + } + } + Ok(None) => { + counts.skipped += 1; + } + Err(e) => { + logger.warn(&format!( + "maildir {folder:?}/{name}: {e}", + folder = ctx.folder, + name = entry.filename + )); + counts.failed += 1; + } + } + } + for (entry, local_id) in &diff.present { + let stored = stored_keywords + .get(local_id) + .map(String::as_str) + .unwrap_or("[]"); + match refresh_flags(&tx, *local_id, entry, stored, ctx.include_deleted) { + Ok(PresentOutcome::Unchanged) => counts.skipped += 1, + Ok(PresentOutcome::KeywordsUpdated) => counts.fetched += 1, + Ok(PresentOutcome::DeleteRequested) => { + match delete_vanished(&tx, ctx.source_id, ctx.folder, &entry.unique_id, *local_id) { + Ok(()) => counts.deleted += 1, + Err(e) => { + logger.warn(&format!( + "maildir {folder:?}/{name}: T-flag delete failed: {e}", + folder = ctx.folder, + name = entry.filename + )); + counts.failed += 1; + } + } + } + Err(e) => { + logger.warn(&format!( + "maildir {folder:?}/{name}: flag update failed: {e}", + folder = ctx.folder, + name = entry.filename + )); + counts.failed += 1; + } + } + } + for (unique_id, local_id) in &diff.vanished { + if let Err(e) = delete_vanished(&tx, ctx.source_id, ctx.folder, unique_id, *local_id) { + logger.warn(&format!( + "maildir {folder:?}/{unique_id}: delete failed: {e}", + folder = ctx.folder + )); + counts.failed += 1; + } else { + counts.deleted += 1; + } + } + tx.commit()?; + Ok(()) +} + +fn keywords_json(translation: &Translation) -> String { + Value::Array( + translation + .keywords + .iter() + .map(|k| Value::String(k.clone())) + .collect(), + ) + .to_string() +} + +fn format_unix_rfc3339(secs: u64) -> String { + let when = UNIX_EPOCH + Duration::from_secs(secs); + OffsetDateTime::from(when) + .format(&Rfc3339) + .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()) +} + +#[derive(Debug, thiserror::Error)] +pub enum InsertError { + #[error("io: {0}")] + Io(#[from] std::io::Error), + #[error("db: {0}")] + Db(#[from] rusqlite::Error), +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + use std::fs; + + fn fresh_archive() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "maildir".to_owned(), + session_url: "file:///tmp/Maildir".to_owned(), + account_id: "/tmp/Maildir".to_owned(), + }, + Some("Maildir"), + "", + ) + .unwrap(); + (c, sid) + } + + fn write_maildir_message( + folder_path: &Path, + sub: &str, + filename: &str, + body: &[u8], + ) -> PathBuf { + let dir = folder_path.join(sub); + fs::create_dir_all(&dir).unwrap(); + let p = dir.join(filename); + fs::write(&p, body).unwrap(); + p + } + + fn ensure_folder_skel(folder_path: &Path) { + for sub in ["cur", "new", "tmp"] { + fs::create_dir_all(folder_path.join(sub)).unwrap(); + } + } + + #[test] + fn list_folder_returns_cur_and_new_skipping_tmp() { + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", "1.M0.host:2,S", b"a"); + write_maildir_message(td.path(), "new", "2.M0.host", b"b"); + write_maildir_message(td.path(), "tmp", "3.M0.host", b"c"); + let listing = list_folder(td.path()).unwrap(); + assert_eq!(listing.entries.len(), 2); + assert!(listing.entries.iter().any(|e| e.unique_id == "1.M0.host")); + assert!(listing.entries.iter().any(|e| e.unique_id == "2.M0.host")); + } + + #[test] + fn list_folder_skips_dotfiles() { + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", ".uidvalidity", b"meta"); + write_maildir_message(td.path(), "cur", ".dovecot-uidlist", b"meta"); + write_maildir_message(td.path(), "cur", "1.M0.host:2,S", b"a"); + let listing = list_folder(td.path()).unwrap(); + assert_eq!(listing.entries.len(), 1); + assert_eq!(listing.entries[0].unique_id, "1.M0.host"); + } + + #[test] + fn list_folder_parses_dovecot_extension_filename() { + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message( + td.path(), + "cur", + "1739471123.M001P01234V0000I0000abcd.host,S=42,W=44:2,RS", + b"a", + ); + let listing = list_folder(td.path()).unwrap(); + assert_eq!(listing.entries.len(), 1); + let e = &listing.entries[0]; + assert_eq!( + e.unique_id, + "1739471123.M001P01234V0000I0000abcd.host,S=42,W=44" + ); + assert!(e.flags.contains(&Flag::Replied)); + assert!(e.flags.contains(&Flag::Seen)); + } + + #[cfg(target_os = "linux")] + #[test] + fn list_folder_renders_non_utf8_filename_lossy() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + let mut bytes = b"1.M0.host".to_vec(); + bytes.extend_from_slice(&[0xff, 0xfe]); + bytes.extend_from_slice(b":2,S"); + let bad_name = OsStr::from_bytes(&bytes); + let p = td.path().join("cur").join(bad_name); + fs::write(&p, b"body").unwrap(); + let listing = list_folder(td.path()).unwrap(); + assert_eq!(listing.entries.len(), 1); + let e = &listing.entries[0]; + assert!( + e.unique_id.contains('\u{FFFD}'), + "lossy substitution: {:?}", + e.unique_id + ); + assert!(e.unique_id.starts_with("1.M0.host")); + assert!(e.flags.contains(&Flag::Seen)); + } + + #[test] + fn diff_partitions_new_present_vanished() { + let entries = vec![ + DiskEntry { + unique_id: "a".into(), + filename: "a".into(), + path: PathBuf::new(), + flags: vec![], + mtime_unix: 0, + }, + DiskEntry { + unique_id: "b".into(), + filename: "b".into(), + path: PathBuf::new(), + flags: vec![], + mtime_unix: 0, + }, + ]; + let mut local = HashMap::new(); + local.insert("b".to_owned(), 10); + local.insert("c".to_owned(), 11); + let d = diff(entries, &local); + assert_eq!(d.new.len(), 1); + assert_eq!(d.new[0].unique_id, "a"); + assert_eq!(d.present.len(), 1); + assert_eq!(d.present[0].0.unique_id, "b"); + assert_eq!(d.present[0].1, 10); + assert_eq!(d.vanished, vec![("c".to_owned(), 11)]); + } + + #[test] + fn insert_new_writes_blob_email_and_sync_row() { + let (mut c, sid) = fresh_archive(); + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES ('INBOX', NULL, 'inbox', 0, 1)", + [], + ) + .unwrap(); + let mailbox_local: i64 = c.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&c, sid, "INBOX", mailbox_local).unwrap(); + + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + let body = b"From: a@x\r\nSubject: hi\r\nMessage-ID: \r\n\r\nhi"; + write_maildir_message(td.path(), "cur", "uid1.M0.host:2,S", body); + let listing = list_folder(td.path()).unwrap(); + let entry = listing.entries.into_iter().next().unwrap(); + + let tx = c.transaction().unwrap(); + let inserted = insert_new( + &tx, + InsertContext { + source_id: sid, + folder: "INBOX", + mailbox_local, + include_deleted: false, + }, + &entry, + ) + .unwrap(); + tx.commit().unwrap(); + assert!(inserted.is_some()); + + let email_count: i64 = c + .query_row("SELECT count(*) FROM emails", [], |r| r.get(0)) + .unwrap(); + assert_eq!(email_count, 1); + let local = db::maildir_ids::local_for_email(&c, sid, "INBOX", "uid1.M0.host") + .unwrap() + .unwrap(); + assert_eq!(local, inserted.unwrap()); + let kws: String = c + .query_row( + "SELECT keywords FROM emails WHERE id = ?1", + params![local], + |r| r.get(0), + ) + .unwrap(); + assert!(kws.contains("$seen")); + } + + #[test] + fn insert_new_skips_trashed_without_include_deleted() { + let (mut c, sid) = fresh_archive(); + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES ('INBOX', NULL, 'inbox', 0, 1)", + [], + ) + .unwrap(); + let mailbox_local: i64 = c.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&c, sid, "INBOX", mailbox_local).unwrap(); + + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", "uid.host:2,T", b"x"); + let listing = list_folder(td.path()).unwrap(); + let entry = listing.entries.into_iter().next().unwrap(); + let tx = c.transaction().unwrap(); + let inserted = insert_new( + &tx, + InsertContext { + source_id: sid, + folder: "INBOX", + mailbox_local, + include_deleted: false, + }, + &entry, + ) + .unwrap(); + tx.commit().unwrap(); + assert!(inserted.is_none()); + let email_count: i64 = c + .query_row("SELECT count(*) FROM emails", [], |r| r.get(0)) + .unwrap(); + assert_eq!(email_count, 0); + } + + #[test] + fn refresh_flags_updates_only_when_changed() { + let (mut c, sid) = fresh_archive(); + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES ('INBOX', NULL, 'inbox', 0, 1)", + [], + ) + .unwrap(); + let mailbox_local = c.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&c, sid, "INBOX", mailbox_local).unwrap(); + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", "u.host:2,", b"hi"); + let entry = list_folder(td.path()).unwrap().entries.remove(0); + let tx = c.transaction().unwrap(); + let local = insert_new( + &tx, + InsertContext { + source_id: sid, + folder: "INBOX", + mailbox_local, + include_deleted: false, + }, + &entry, + ) + .unwrap() + .unwrap(); + tx.commit().unwrap(); + + let stored = load_present_keywords(&c, sid, "INBOX").unwrap(); + let stored_for_local = stored + .get(&local) + .cloned() + .unwrap_or_else(|| "[]".to_owned()); + + let tx = c.transaction().unwrap(); + assert_eq!( + refresh_flags(&tx, local, &entry, &stored_for_local, false).unwrap(), + PresentOutcome::Unchanged + ); + tx.commit().unwrap(); + + fs::rename(&entry.path, entry.path.with_file_name("u.host:2,S")).unwrap(); + let entry2 = list_folder(td.path()).unwrap().entries.remove(0); + let tx = c.transaction().unwrap(); + assert_eq!( + refresh_flags(&tx, local, &entry2, &stored_for_local, false).unwrap(), + PresentOutcome::KeywordsUpdated + ); + tx.commit().unwrap(); + let kws: String = c + .query_row( + "SELECT keywords FROM emails WHERE id = ?1", + params![local], + |r| r.get(0), + ) + .unwrap(); + assert!(kws.contains("$seen")); + } + + #[test] + fn refresh_flags_returns_delete_when_trashed_added_and_include_deleted_off() { + let (mut c, sid) = fresh_archive(); + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES ('INBOX', NULL, 'inbox', 0, 1)", + [], + ) + .unwrap(); + let mailbox_local = c.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&c, sid, "INBOX", mailbox_local).unwrap(); + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", "u.host:2,", b"hi"); + let entry = list_folder(td.path()).unwrap().entries.remove(0); + let tx = c.transaction().unwrap(); + let local = insert_new( + &tx, + InsertContext { + source_id: sid, + folder: "INBOX", + mailbox_local, + include_deleted: false, + }, + &entry, + ) + .unwrap() + .unwrap(); + tx.commit().unwrap(); + + fs::rename(&entry.path, entry.path.with_file_name("u.host:2,T")).unwrap(); + let entry2 = list_folder(td.path()).unwrap().entries.remove(0); + let tx = c.transaction().unwrap(); + assert_eq!( + refresh_flags(&tx, local, &entry2, "[]", false).unwrap(), + PresentOutcome::DeleteRequested + ); + tx.commit().unwrap(); + } + + #[test] + fn refresh_flags_keeps_trashed_with_include_deleted_on() { + let (mut c, sid) = fresh_archive(); + c.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES ('INBOX', NULL, 'inbox', 0, 1)", + [], + ) + .unwrap(); + let mailbox_local = c.last_insert_rowid(); + db::maildir_ids::insert_mailbox(&c, sid, "INBOX", mailbox_local).unwrap(); + let td = tempfile::tempdir().unwrap(); + ensure_folder_skel(td.path()); + write_maildir_message(td.path(), "cur", "u.host:2,T", b"hi"); + let entry = list_folder(td.path()).unwrap().entries.remove(0); + let tx = c.transaction().unwrap(); + let local = insert_new( + &tx, + InsertContext { + source_id: sid, + folder: "INBOX", + mailbox_local, + include_deleted: true, + }, + &entry, + ) + .unwrap() + .unwrap(); + tx.commit().unwrap(); + + let stored = load_present_keywords(&c, sid, "INBOX").unwrap(); + let stored_for_local = stored.get(&local).cloned().unwrap(); + let tx = c.transaction().unwrap(); + assert_eq!( + refresh_flags(&tx, local, &entry, &stored_for_local, true).unwrap(), + PresentOutcome::Unchanged + ); + tx.commit().unwrap(); + } +} diff --git a/src/sync/import_maildir/tree.rs b/src/sync/import_maildir/tree.rs new file mode 100644 index 0000000..68c3d51 --- /dev/null +++ b/src/sync/import_maildir/tree.rs @@ -0,0 +1,495 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::path::{Path, PathBuf}; + +use regex::Regex; + +use crate::imap::automap; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedFolder { + pub name: String, + pub leaf: String, + pub parent_path: Option, + pub path: PathBuf, + pub role: Option<&'static str>, + pub ephemeral: bool, +} + +pub struct FolderFilters { + pub include: Vec, + pub exclude: Vec, + pub explicit: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum DiscoverError { + #[error("path does not exist: {0}")] + NotFound(PathBuf), + #[error("path is not a directory: {0}")] + NotADirectory(PathBuf), + #[error("not a Maildir: {0} (missing cur/ subdirectory)")] + NotAMaildir(PathBuf), + #[error( + "only Maildir++ layout is supported: found directory {0:?} that looks like a non-prefixed \ + subfolder (subfolders must be named with a leading '.', e.g. '.Sent')" + )] + NotMaildirPlus(String), + #[error("io error walking {0}: {1}")] + Io(PathBuf, std::io::Error), +} + +pub fn discover(root: &Path, automap_enabled: bool) -> Result, DiscoverError> { + if !root.exists() { + return Err(DiscoverError::NotFound(root.to_path_buf())); + } + let meta = std::fs::metadata(root).map_err(|e| DiscoverError::Io(root.to_path_buf(), e))?; + if !meta.is_dir() { + return Err(DiscoverError::NotADirectory(root.to_path_buf())); + } + if !root.join("cur").is_dir() { + return Err(DiscoverError::NotAMaildir(root.to_path_buf())); + } + let mut folders = vec![ResolvedFolder { + name: "INBOX".to_owned(), + leaf: "INBOX".to_owned(), + parent_path: None, + path: root.to_path_buf(), + role: Some("inbox"), + ephemeral: false, + }]; + + let mut subfolders: Vec<(String, PathBuf)> = Vec::new(); + let entries = std::fs::read_dir(root).map_err(|e| DiscoverError::Io(root.to_path_buf(), e))?; + for entry in entries { + let entry = entry.map_err(|e| DiscoverError::Io(root.to_path_buf(), e))?; + let path = entry.path(); + if !path.is_dir() { + continue; + } + let Some(raw_name) = path.file_name() else { + continue; + }; + let name: String = raw_name.to_string_lossy().into_owned(); + if matches!(name.as_str(), "cur" | "new" | "tmp") { + continue; + } + let has_cur = path.join("cur").is_dir(); + if let Some(stripped) = name.strip_prefix('.') { + if has_cur && !stripped.is_empty() { + subfolders.push((stripped.to_owned(), path)); + } + } else if has_cur { + return Err(DiscoverError::NotMaildirPlus(name)); + } + } + + subfolders.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut known: Vec = vec!["INBOX".to_owned()]; + for (canonical, path) in subfolders { + let parent_path = split_parent(&canonical); + if let Some(p) = parent_path.as_deref() { + ensure_ephemeral_parents(p, &mut known, &mut folders, root); + } + let leaf = leaf_of(&canonical); + let role = if automap_enabled { + automap::role_for_folder(&canonical, &[], "", true) + } else { + None + }; + folders.push(ResolvedFolder { + name: canonical.clone(), + leaf: leaf.to_owned(), + parent_path, + path, + role, + ephemeral: false, + }); + known.push(canonical); + } + Ok(folders) +} + +fn ensure_ephemeral_parents( + parent_path: &str, + known: &mut Vec, + folders: &mut Vec, + root: &Path, +) { + for ancestor in ancestor_chain(parent_path) { + if known.iter().any(|n| n == &ancestor) { + continue; + } + let leaf = leaf_of(&ancestor).to_owned(); + let parent_path = split_parent(&ancestor); + folders.push(ResolvedFolder { + name: ancestor.clone(), + leaf, + parent_path, + path: root.to_path_buf(), + role: None, + ephemeral: true, + }); + known.push(ancestor); + } +} + +fn ancestor_chain(name: &str) -> Vec { + let mut out = Vec::new(); + let mut acc = String::new(); + for part in name.split('.') { + if !acc.is_empty() { + acc.push('.'); + } + acc.push_str(part); + out.push(acc.clone()); + } + out +} + +fn split_parent(name: &str) -> Option { + name.rfind('.').map(|i| name[..i].to_owned()) +} + +fn leaf_of(name: &str) -> &str { + match name.rfind('.') { + Some(i) => &name[i + 1..], + None => name, + } +} + +pub fn apply_filters(folders: Vec, filters: &FolderFilters) -> Vec { + folders + .into_iter() + .filter(|f| match_filters(&f.name, filters)) + .collect() +} + +pub fn restore_ephemeral_parents(folders: &mut Vec, root: &Path) { + let known: std::collections::HashSet = folders.iter().map(|f| f.name.clone()).collect(); + let mut to_add: Vec = Vec::new(); + for f in folders.iter() { + if let Some(parent) = f.parent_path.as_deref() { + for ancestor in ancestor_chain(parent) { + if known.contains(&ancestor) || to_add.iter().any(|x| x.name == ancestor) { + continue; + } + let leaf = leaf_of(&ancestor).to_owned(); + let parent_path = split_parent(&ancestor); + to_add.push(ResolvedFolder { + name: ancestor, + leaf, + parent_path, + path: root.to_path_buf(), + role: None, + ephemeral: true, + }); + } + } + } + if to_add.is_empty() { + return; + } + folders.extend(to_add); + folders.sort_by_key(|f| f.name.matches('.').count()); +} + +fn match_filters(name: &str, filters: &FolderFilters) -> bool { + if !filters.explicit.is_empty() { + return filters.explicit.iter().any(|e| e == name); + } + if !filters.include.is_empty() && !filters.include.iter().any(|r| r.is_match(name)) { + return false; + } + if filters.exclude.iter().any(|r| r.is_match(name)) { + return false; + } + true +} + +pub fn vanished_folders<'a, I>(local: I, server: &[ResolvedFolder]) -> Vec +where + I: IntoIterator, +{ + let server_set: std::collections::HashSet<&str> = + server.iter().map(|f| f.name.as_str()).collect(); + local + .into_iter() + .filter(|n| !server_set.contains(*n)) + .map(str::to_owned) + .collect() +} + +pub fn vanished_depth_sort(names: &mut [String]) { + names.sort_by(|a, b| { + let da = a.matches('.').count(); + let db = b.matches('.').count(); + db.cmp(&da).then_with(|| a.cmp(b)) + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + fn make_maildir(root: &Path, folders: &[&str]) { + for sub in ["cur", "new", "tmp"] { + fs::create_dir_all(root.join(sub)).unwrap(); + } + for name in folders { + let p = root.join(name); + for sub in ["cur", "new", "tmp"] { + fs::create_dir_all(p.join(sub)).unwrap(); + } + } + } + + #[test] + fn discover_just_inbox_when_root_alone() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[]); + let folders = discover(td.path(), true).unwrap(); + assert_eq!(folders.len(), 1); + assert_eq!(folders[0].name, "INBOX"); + assert_eq!(folders[0].role, Some("inbox")); + assert!(folders[0].parent_path.is_none()); + } + + #[test] + fn discover_dovecot_style_subfolders() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[".Sent", ".Drafts", ".Trash", ".Junk"]); + let folders = discover(td.path(), true).unwrap(); + let names: Vec<&str> = folders.iter().map(|f| f.name.as_str()).collect(); + assert!(names.contains(&"INBOX")); + assert!(names.contains(&"Sent")); + assert!(names.contains(&"Drafts")); + let sent = folders.iter().find(|f| f.name == "Sent").unwrap(); + assert_eq!(sent.role, Some("sent")); + let drafts = folders.iter().find(|f| f.name == "Drafts").unwrap(); + assert_eq!(drafts.role, Some("drafts")); + let trash = folders.iter().find(|f| f.name == "Trash").unwrap(); + assert_eq!(trash.role, Some("trash")); + let junk = folders.iter().find(|f| f.name == "Junk").unwrap(); + assert_eq!(junk.role, Some("junk")); + } + + #[test] + fn discover_courier_maildir_plus_deep_hierarchy() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[".Archive", ".Archive.2024", ".Archive.2025"]); + let folders = discover(td.path(), true).unwrap(); + let names: Vec<&str> = folders.iter().map(|f| f.name.as_str()).collect(); + assert!(names.contains(&"Archive")); + assert!(names.contains(&"Archive.2024")); + assert!(names.contains(&"Archive.2025")); + let a2025 = folders.iter().find(|f| f.name == "Archive.2025").unwrap(); + assert_eq!(a2025.leaf, "2025"); + assert_eq!(a2025.parent_path.as_deref(), Some("Archive")); + let archive = folders.iter().find(|f| f.name == "Archive").unwrap(); + assert_eq!(archive.role, Some("archive")); + + assert!(a2025.role.is_none()); + } + + #[test] + fn discover_inserts_ephemeral_parent_for_orphan_branch() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[".Lists.maildir-dev"]); + let folders = discover(td.path(), true).unwrap(); + let lists = folders.iter().find(|f| f.name == "Lists").unwrap(); + assert!(lists.ephemeral); + assert!(lists.role.is_none()); + assert!(lists.parent_path.is_none()); + let leaf = folders + .iter() + .find(|f| f.name == "Lists.maildir-dev") + .unwrap(); + assert!(!leaf.ephemeral); + assert_eq!(leaf.parent_path.as_deref(), Some("Lists")); + assert_eq!(leaf.leaf, "maildir-dev"); + } + + #[test] + fn discover_skips_non_dot_subfolders_silently_only_if_no_cur() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[".Sent"]); + + fs::create_dir(td.path().join(".dovecot.imap")).unwrap(); + fs::write(td.path().join("dovecot.index.log"), "").unwrap(); + let folders = discover(td.path(), true).unwrap(); + assert_eq!(folders.len(), 2); + assert!(folders.iter().any(|f| f.name == "INBOX")); + assert!(folders.iter().any(|f| f.name == "Sent")); + } + + #[test] + fn discover_rejects_dovecot_layout_fs_tree() { + + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &["Sent"]); + let err = discover(td.path(), true).unwrap_err(); + assert!(matches!(err, DiscoverError::NotMaildirPlus(_))); + } + + #[test] + fn discover_rejects_path_without_cur_subdir() { + let td = tempfile::tempdir().unwrap(); + fs::create_dir_all(td.path().join("new")).unwrap(); + let err = discover(td.path(), true).unwrap_err(); + assert!(matches!(err, DiscoverError::NotAMaildir(_))); + } + + #[test] + fn discover_rejects_nonexistent_path() { + let err = discover(Path::new("/definitely/not/a/maildir/at/all"), true).unwrap_err(); + assert!(matches!(err, DiscoverError::NotFound(_))); + } + + #[test] + fn discover_returns_parents_before_children() { + let td = tempfile::tempdir().unwrap(); + make_maildir( + td.path(), + &[".Archive.2025", ".Archive", ".Lists.maildir-dev"], + ); + let folders = discover(td.path(), true).unwrap(); + let index = |name: &str| folders.iter().position(|f| f.name == name).unwrap(); + assert!(index("Archive") < index("Archive.2025")); + assert!(index("Lists") < index("Lists.maildir-dev")); + assert_eq!(index("INBOX"), 0); + } + + #[test] + fn ancestor_chain_walks_dot_segments() { + assert_eq!(ancestor_chain("A"), vec!["A".to_owned()]); + assert_eq!( + ancestor_chain("A.B.C"), + vec!["A".to_owned(), "A.B".to_owned(), "A.B.C".to_owned()] + ); + } + + #[test] + fn leaf_and_split_handle_root_and_nested() { + assert_eq!(leaf_of("INBOX"), "INBOX"); + assert_eq!(leaf_of("Archive.2025"), "2025"); + assert_eq!(split_parent("INBOX"), None); + assert_eq!(split_parent("Archive.2025"), Some("Archive".to_owned())); + } + + fn folders_for(names: &[&str]) -> Vec { + names + .iter() + .map(|n| ResolvedFolder { + name: (*n).to_owned(), + leaf: (*n).to_owned(), + parent_path: None, + path: PathBuf::new(), + role: None, + ephemeral: false, + }) + .collect() + } + + #[test] + fn include_filter_keeps_only_matches() { + let folders = folders_for(&["INBOX", "Sent", "Trash"]); + let filters = FolderFilters { + include: vec![Regex::new("^(INBOX|Sent)$").unwrap()], + exclude: Vec::new(), + explicit: Vec::new(), + }; + let kept = apply_filters(folders, &filters); + assert_eq!(kept.len(), 2); + assert!(kept.iter().any(|f| f.name == "INBOX")); + assert!(kept.iter().any(|f| f.name == "Sent")); + } + + #[test] + fn exclude_filter_drops_matches() { + let folders = folders_for(&["INBOX", "Trash"]); + let filters = FolderFilters { + include: Vec::new(), + exclude: vec![Regex::new("^Trash$").unwrap()], + explicit: Vec::new(), + }; + let kept = apply_filters(folders, &filters); + assert_eq!(kept.len(), 1); + assert_eq!(kept[0].name, "INBOX"); + } + + #[test] + fn explicit_folder_overrides_include_exclude() { + let folders = folders_for(&["INBOX", "Sent", "Trash"]); + let filters = FolderFilters { + include: Vec::new(), + exclude: Vec::new(), + explicit: vec!["Sent".to_owned()], + }; + let kept = apply_filters(folders, &filters); + assert_eq!(kept.len(), 1); + assert_eq!(kept[0].name, "Sent"); + } + + #[test] + fn discover_with_automap_off_clears_roles_except_inbox() { + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[".Sent", ".Drafts"]); + let folders = discover(td.path(), false).unwrap(); + let inbox = folders.iter().find(|f| f.name == "INBOX").unwrap(); + assert_eq!(inbox.role, Some("inbox")); + let sent = folders.iter().find(|f| f.name == "Sent").unwrap(); + assert_eq!(sent.role, None); + let drafts = folders.iter().find(|f| f.name == "Drafts").unwrap(); + assert_eq!(drafts.role, None); + } + + #[cfg(target_os = "linux")] + #[test] + fn discover_renders_non_utf8_folder_name_lossy() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + let td = tempfile::tempdir().unwrap(); + make_maildir(td.path(), &[]); + let mut bytes = b".Foo".to_vec(); + bytes.extend_from_slice(&[0xff, 0xfe]); + let dotted = OsStr::from_bytes(&bytes); + let p = td.path().join(dotted); + for sub in ["cur", "new", "tmp"] { + fs::create_dir_all(p.join(sub)).unwrap(); + } + let folders = discover(td.path(), true).unwrap(); + assert_eq!(folders.len(), 2); + let sub = folders.iter().find(|f| f.name != "INBOX").unwrap(); + assert!( + sub.name.contains('\u{FFFD}'), + "lossy substitution: {:?}", + sub.name + ); + assert!(sub.name.starts_with("Foo")); + } + + #[test] + fn vanished_depth_sort_deepest_first() { + let mut names = vec![ + "Archive".to_owned(), + "Archive.2025.January".to_owned(), + "Archive.2025".to_owned(), + ]; + vanished_depth_sort(&mut names); + assert_eq!( + names, + vec![ + "Archive.2025.January".to_owned(), + "Archive.2025".to_owned(), + "Archive".to_owned(), + ] + ); + } +} diff --git a/src/sync/import_managesieve.rs b/src/sync/import_managesieve.rs new file mode 100644 index 0000000..b51e01d --- /dev/null +++ b/src/sync/import_managesieve.rs @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod coordinator; +pub mod reconcile; + +pub use coordinator::{ManageSieveAuth, ManageSieveImportConfig, run}; diff --git a/src/sync/import_managesieve/coordinator.rs b/src/sync/import_managesieve/coordinator.rs new file mode 100644 index 0000000..fa683cb --- /dev/null +++ b/src/sync/import_managesieve/coordinator.rs @@ -0,0 +1,638 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Arc; + +use url::Url; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::error::Error; +use crate::imap::retry::BackoffState; +use crate::imap::transport::Connector; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::managesieve::error::SieveError; +use crate::managesieve::response::parse_listscripts; +use crate::managesieve::retry::{Disposition, classify, is_negotiation_failure}; +use crate::managesieve::{ConnectMode, SieveClient, parse_getscript}; +use crate::sync::keys::blake3_bytes; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::reconcile::{ + Action, PresentOutcome, apply_active_assignment, apply_content_update, apply_delete, apply_new, + load_local_state, plan, +}; + +pub struct ManageSieveImportConfig { + pub url: String, + pub auth: ManageSieveAuth, + pub allow_cleartext: bool, + pub allow_source_change: bool, +} + +#[derive(Debug, Clone)] +pub enum ManageSieveAuth { + Basic { user: String, password: String }, + Bearer { user: String, token: String }, +} + +#[derive(Debug, Clone)] +struct Endpoint { + host: String, + port: u16, + implicit_tls: bool, +} + +struct ControlCtx { + connector: Arc, + host: String, + port: u16, + mode: ConnectMode, + allow_cleartext: bool, + auth: ManageSieveAuth, + backoff: BackoffState, + max_retries: u32, + logger: Logger, +} + +fn reconnect(client: &mut SieveClient, ctx: &ControlCtx) -> Result<(), SieveError> { + let new = SieveClient::connect( + &ctx.connector, + &ctx.host, + ctx.port, + ctx.mode, + ctx.allow_cleartext, + )?; + *client = new; + match do_authenticate(client, &ctx.auth) { + Ok(()) => {} + Err(SieveAuthError::Wire(e)) => return Err(e), + Err(SieveAuthError::TerminallyRefused(text)) => { + return Err(SieveError::Protocol(format!( + "auth refused on reconnect: {text}" + ))); + } + Err(SieveAuthError::NoUsableMechanism(text)) => { + return Err(SieveError::Unsupported(text)); + } + } + if !client.had_fresh_post_auth_caps() + && let Err(e) = client.refresh_capabilities() + { + ctx.logger.warn(&format!( + "post-reconnect CAPABILITY refresh failed: {e}; continuing" + )); + } + Ok(()) +} + +pub fn run(common: CommonConfig, config: ManageSieveImportConfig) -> Result { + let logger = common.logger; + if common.threads > 1 { + log_at( + logger, + LEVEL_PROGRESS, + "managesieve importer is single-threaded; --threads value will be ignored", + ); + } + + let mut conn = db::init::open(&common.archive)?; + + let endpoint = parse_endpoint(&config.url)?; + let session_url = format!( + "{}://{}:{}", + if endpoint.implicit_tls { + "sieves" + } else { + "sieve" + }, + endpoint.host, + endpoint.port + ); + + let connector = Arc::new( + Connector::new(common.allow_invalid_certs).map_err(|e| Error::Connection(e.to_string()))?, + ); + let mode = if endpoint.implicit_tls { + ConnectMode::ImplicitTls + } else { + ConnectMode::StartTls + }; + + let mut client = SieveClient::connect( + &connector, + &endpoint.host, + endpoint.port, + mode, + config.allow_cleartext, + ) + .map_err(|e| Error::Connection(e.to_string()))?; + + let account_id = authenticate(&mut client, &config.auth)?; + log_at( + logger, + LEVEL_PROGRESS, + &format!("authenticated as {account_id} on {session_url}"), + ); + if !client.had_fresh_post_auth_caps() + && let Err(e) = client.refresh_capabilities() + { + logger.warn(&format!( + "post-auth CAPABILITY refresh failed: {e}; continuing" + )); + } + + let source_key = SourceKey { + kind: "managesieve".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + if let Some((existing_url, existing_account)) = + db::sources::conflicting_source(&conn, "managesieve", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records managesieve source {existing_url} / {existing_account}; \ + re-run with --allow-source-change or use a fresh archive" + ))); + } + let source_id = if common.dry_run { + db::sources::find_source(&conn, &source_key)?.unwrap_or(-1) + } else { + db::sources::upsert_source(&conn, &source_key, Some(&account_id), &account_id)? + }; + + let backoff = BackoffState::new(); + let control_ctx = ControlCtx { + connector: connector.clone(), + host: endpoint.host.clone(), + port: endpoint.port, + mode, + allow_cleartext: config.allow_cleartext, + auth: config.auth.clone(), + backoff: backoff.clone(), + max_retries: common.max_retries, + logger, + }; + let server_listing = listscripts_with_retry(&mut client, &control_ctx)?; + + let mut server_with_bytes: Vec<(String, bool, [u8; 32], Vec)> = + Vec::with_capacity(server_listing.len()); + let mut counts = TypeCounts::default(); + for (name, active) in &server_listing { + match getscript_with_retry(&mut client, &control_ctx, name) { + Ok(bytes) => { + let hash = blake3_bytes(&bytes); + server_with_bytes.push((name.clone(), *active, hash, bytes)); + } + Err(e) => match classify(&e) { + Disposition::Permanent + | Disposition::PerScriptRecoverable + | Disposition::Transient => { + logger.warn(&format!("GETSCRIPT {name:?} failed, skipping: {e}")); + counts.failed += 1; + } + Disposition::Referral => { + return Err(Error::Connection(format!( + "ManageSieve referral on GETSCRIPT {name:?}: {e}; vandelay does not follow referrals" + ))); + } + Disposition::TransportDrop => { + return Err(Error::Connection(format!( + "GETSCRIPT {name:?} transport drop exhausted retries: {e}" + ))); + } + Disposition::FeatureNegotiation => { + return Err(Error::Connection(format!("GETSCRIPT {name:?}: {e}"))); + } + }, + } + } + + if common.dry_run { + let summary = + build_dry_run_summary(&conn, source_id, &server_with_bytes, counts, &backoff)?; + let _ = client.logout(); + return Ok(summary); + } + + let local_map = db::managesieve_ids::all_names(&conn, source_id)?; + let (local_hashes, local_active) = load_local_state(&conn, source_id)?; + let server_keyed: Vec<(String, bool, [u8; 32])> = server_with_bytes + .iter() + .map(|(n, a, h, _)| (n.clone(), *a, *h)) + .collect(); + let plan = plan(&server_keyed, &local_map, &local_hashes, &local_active); + + let bytes_by_name: std::collections::HashMap> = server_with_bytes + .iter() + .map(|(n, _, _, b)| (n.clone(), b)) + .collect(); + + let mut active_local: Option = None; + + for action in &plan.actions { + match action { + Action::New { name, active } => { + let bytes = bytes_by_name.get(name).cloned().unwrap(); + let tx = conn.transaction()?; + match apply_new(&tx, source_id, name, bytes) { + Ok(local_id) => { + tx.commit()?; + if *active { + active_local = Some(local_id); + } + counts.created += 1; + counts.fetched += 1; + } + Err(e) => { + let _ = tx.rollback(); + logger.warn(&format!("INSERT sieve_scripts {name:?} failed: {e}")); + counts.failed += 1; + } + } + } + Action::Present { + name, + local_id, + outcome, + active, + } => { + match outcome { + PresentOutcome::Unchanged => { + counts.skipped += 1; + } + PresentOutcome::ActiveOnly => { + counts.skipped += 1; + } + PresentOutcome::ContentUpdated => { + let bytes = bytes_by_name.get(name).cloned().unwrap(); + let tx = conn.transaction()?; + match apply_content_update(&tx, *local_id, bytes) { + Ok(()) => { + tx.commit()?; + counts.fetched += 1; + } + Err(e) => { + let _ = tx.rollback(); + logger.warn(&format!("UPDATE sieve_scripts {name:?} failed: {e}")); + counts.failed += 1; + } + } + } + } + if *active { + active_local = Some(*local_id); + } + } + Action::Vanished { name, local_id } => { + let tx = conn.transaction()?; + match apply_delete(&tx, source_id, name, *local_id) { + Ok(()) => { + tx.commit()?; + counts.deleted += 1; + } + Err(e) => { + let _ = tx.rollback(); + logger.warn(&format!("DELETE sieve_scripts {name:?} failed: {e}")); + counts.failed += 1; + } + } + } + } + } + + apply_active_assignment(&mut conn, active_local)?; + + let _ = client.logout(); + + if counts.failed == 0 { + let tx = conn.unchecked_transaction()?; + db::blobs::gc_orphan_blobs(&tx)?; + tx.commit()?; + } + + log_at( + logger, + LEVEL_DEFAULT, + &format!( + "managesieve: created={} fetched={} deleted={} skipped={} failed={}", + counts.created, counts.fetched, counts.deleted, counts.skipped, counts.failed + ), + ); + + Ok(Summary { + per_type: vec![("sievescript", counts)], + retries_observed: backoff.total_retries(), + retry_after_sleeps: backoff.transient_retries() as u64, + }) +} + +fn build_dry_run_summary( + conn: &rusqlite::Connection, + source_id: i64, + server: &[(String, bool, [u8; 32], Vec)], + mut counts: TypeCounts, + backoff: &BackoffState, +) -> Result { + let server_keyed: Vec<(String, bool, [u8; 32])> = server + .iter() + .map(|(n, a, h, _)| (n.clone(), *a, *h)) + .collect(); + let (local_map, local_hashes, local_active) = if source_id < 0 { + ( + std::collections::HashMap::new(), + std::collections::HashMap::new(), + std::collections::HashMap::new(), + ) + } else { + let local_map = db::managesieve_ids::all_names(conn, source_id)?; + let (h, a) = load_local_state(conn, source_id)?; + (local_map, h, a) + }; + let plan = plan(&server_keyed, &local_map, &local_hashes, &local_active); + for action in &plan.actions { + match action { + Action::New { .. } => { + counts.created += 1; + counts.fetched += 1; + } + Action::Present { + outcome: PresentOutcome::ContentUpdated, + .. + } => counts.fetched += 1, + Action::Present { .. } => counts.skipped += 1, + Action::Vanished { .. } => counts.deleted += 1, + } + } + Ok(Summary { + per_type: vec![("sievescript", counts)], + retries_observed: backoff.total_retries(), + retry_after_sleeps: backoff.transient_retries() as u64, + }) +} + +fn parse_endpoint(url: &str) -> Result { + let parsed = + Url::parse(url).map_err(|e| Error::Usage(format!("invalid --url {url:?}: {e}")))?; + let scheme = parsed.scheme(); + let implicit_tls = match scheme { + "sieves" => true, + "sieve" => false, + other => { + return Err(Error::Usage(format!( + "--url scheme must be sieve or sieves, got {other}" + ))); + } + }; + let host = parsed + .host_str() + .ok_or_else(|| Error::Usage(format!("--url missing host: {url}")))? + .to_owned(); + let port = parsed.port().unwrap_or(4190); + Ok(Endpoint { + host, + port, + implicit_tls, + }) +} + +fn authenticate(client: &mut SieveClient, auth: &ManageSieveAuth) -> Result { + do_authenticate(client, auth).map_err(|e| match e { + SieveAuthError::TerminallyRefused(text) => { + Error::Connection(format!("auth failed: {text}")) + } + SieveAuthError::NoUsableMechanism(text) => Error::Connection(text), + SieveAuthError::Wire(e) => Error::Connection(e.to_string()), + })?; + Ok(account_id_for(auth)) +} + +fn account_id_for(auth: &ManageSieveAuth) -> String { + match auth { + ManageSieveAuth::Basic { user, .. } => user.clone(), + ManageSieveAuth::Bearer { user, .. } => user.clone(), + } +} + +#[derive(Debug)] +enum SieveAuthError { + + TerminallyRefused(String), + + NoUsableMechanism(String), + + Wire(SieveError), +} + +fn do_authenticate(client: &mut SieveClient, auth: &ManageSieveAuth) -> Result<(), SieveAuthError> { + match auth { + ManageSieveAuth::Basic { user, password } => { + let caps = client.capabilities().clone(); + let mut last_plain_error: Option = None; + if caps.has_sasl("PLAIN") { + match client.authenticate_plain(user, password) { + Ok(()) => return Ok(()), + Err(e) if is_negotiation_failure(&e) => { + last_plain_error = Some(e.to_string()); + } + Err(e) => return Err(SieveAuthError::Wire(e)), + } + } + if caps.has_sasl("LOGIN") { + match client.authenticate_login(user, password) { + Ok(()) => return Ok(()), + Err(e) if is_negotiation_failure(&e) => { + let combined = match last_plain_error { + Some(p) => format!("LOGIN: {e}; PLAIN: {p}"), + None => format!("LOGIN: {e}"), + }; + return Err(SieveAuthError::TerminallyRefused(combined)); + } + Err(e) => return Err(SieveAuthError::Wire(e)), + } + } + if let Some(p) = last_plain_error { + return Err(SieveAuthError::TerminallyRefused(format!("PLAIN: {p}"))); + } + Err(SieveAuthError::NoUsableMechanism(format!( + "server SASL list lacks PLAIN and LOGIN (advertised: {:?})", + caps.sasl + ))) + } + ManageSieveAuth::Bearer { user, token } => { + if !client.capabilities().has_sasl("OAUTHBEARER") { + return Err(SieveAuthError::NoUsableMechanism(format!( + "server does not advertise OAUTHBEARER (advertised: {:?})", + client.capabilities().sasl + ))); + } + match client.authenticate_oauthbearer(user, token) { + Ok(()) => Ok(()), + Err(e) if is_negotiation_failure(&e) => Err(SieveAuthError::TerminallyRefused( + format!("OAUTHBEARER: {e}"), + )), + Err(e) => Err(SieveAuthError::Wire(e)), + } + } + } +} + +fn listscripts_with_retry( + client: &mut SieveClient, + ctx: &ControlCtx, +) -> Result, Error> { + let mut transient_attempts: u32 = 0; + let mut transport_attempts: u32 = 0; + loop { + match client.listscripts() { + Ok(block) => { + ctx.backoff.reset(); + let listed = parse_listscripts(&block.data) + .map_err(|e| Error::Connection(format!("LISTSCRIPTS parse: {e}")))?; + return Ok(listed.into_iter().map(|s| (s.name, s.active)).collect()); + } + Err(e) => match classify(&e) { + Disposition::Transient => { + if transient_attempts >= ctx.max_retries { + return Err(Error::Connection(format!( + "LISTSCRIPTS retries exhausted: {e}" + ))); + } + transient_attempts += 1; + log_at( + ctx.logger, + LEVEL_PROGRESS, + &format!("LISTSCRIPTS transient: {e}; retrying"), + ); + std::thread::sleep(ctx.backoff.next_shared_delay()); + } + Disposition::TransportDrop => { + if transport_attempts >= ctx.max_retries { + return Err(Error::Connection(format!( + "LISTSCRIPTS transport drop, retries exhausted: {e}" + ))); + } + transport_attempts += 1; + std::thread::sleep(ctx.backoff.transport_delay(transport_attempts)); + match reconnect(client, ctx) { + Ok(()) => { + log_at( + ctx.logger, + LEVEL_DEFAULT, + &format!("managesieve reconnected after transport drop ({e})"), + ); + } + Err(e2) => { + return Err(Error::Connection(format!( + "LISTSCRIPTS reconnect failed: {e2}" + ))); + } + } + } + Disposition::Referral => { + return Err(Error::Connection(format!( + "LISTSCRIPTS referral: {e}; vandelay does not follow referrals" + ))); + } + _ => return Err(Error::Connection(format!("LISTSCRIPTS failed: {e}"))), + }, + } + } +} + +fn getscript_with_retry( + client: &mut SieveClient, + ctx: &ControlCtx, + name: &str, +) -> Result, SieveError> { + let mut transient_attempts: u32 = 0; + let mut transport_attempts: u32 = 0; + loop { + match client.getscript(name) { + Ok(block) => { + ctx.backoff.reset(); + return parse_getscript(&block.data); + } + Err(e) => match classify(&e) { + Disposition::Transient => { + if transient_attempts >= ctx.max_retries { + return Err(e); + } + transient_attempts += 1; + log_at( + ctx.logger, + LEVEL_PROGRESS, + &format!("GETSCRIPT {name:?} transient: {e}; retrying"), + ); + std::thread::sleep(ctx.backoff.next_shared_delay()); + } + Disposition::TransportDrop => { + if transport_attempts >= ctx.max_retries { + return Err(e); + } + transport_attempts += 1; + std::thread::sleep(ctx.backoff.transport_delay(transport_attempts)); + reconnect(client, ctx)?; + log_at( + ctx.logger, + LEVEL_DEFAULT, + &format!( + "managesieve reconnected mid-GETSCRIPT {name:?} after transport drop" + ), + ); + } + _ => return Err(e), + }, + } + } +} + +fn log_at(logger: Logger, level: u8, msg: &str) { + if logger.enabled(level) { + eprintln!("{msg}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_endpoint_sieves_defaults_to_4190() { + let e = parse_endpoint("sieves://mail.example.com").unwrap(); + assert_eq!(e.host, "mail.example.com"); + assert_eq!(e.port, 4190); + assert!(e.implicit_tls); + } + + #[test] + fn parse_endpoint_sieve_defaults_to_4190() { + let e = parse_endpoint("sieve://mail.example.com").unwrap(); + assert_eq!(e.host, "mail.example.com"); + assert_eq!(e.port, 4190); + assert!(!e.implicit_tls); + } + + #[test] + fn parse_endpoint_explicit_port() { + let e = parse_endpoint("sieve://mail.example.com:14190").unwrap(); + assert_eq!(e.port, 14190); + assert!(!e.implicit_tls); + } + + #[test] + fn parse_endpoint_rejects_other_schemes() { + let err = parse_endpoint("imap://example.com").unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } + + #[test] + fn parse_endpoint_rejects_missing_host() { + let err = parse_endpoint("sieve:///path").unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } +} diff --git a/src/sync/import_managesieve/reconcile.rs b/src/sync/import_managesieve/reconcile.rs new file mode 100644 index 0000000..184b603 --- /dev/null +++ b/src/sync/import_managesieve/reconcile.rs @@ -0,0 +1,480 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +use rusqlite::{Connection, OptionalExtension, params}; + +use crate::db; +use crate::error::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PresentOutcome { + Unchanged, + ActiveOnly, + ContentUpdated, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Action { + New { + name: String, + active: bool, + }, + Present { + name: String, + local_id: i64, + outcome: PresentOutcome, + active: bool, + }, + Vanished { + name: String, + local_id: i64, + }, +} + +pub struct Plan { + pub actions: Vec, +} + +pub fn plan( + server: &[(String, bool, [u8; 32])], + local: &HashMap, + local_hashes: &HashMap, + local_active: &HashMap, +) -> Plan { + let mut actions = Vec::new(); + let mut server_names: HashSet<&str> = HashSet::new(); + for (name, active, hash) in server { + server_names.insert(name.as_str()); + match local.get(name) { + None => actions.push(Action::New { + name: name.clone(), + active: *active, + }), + Some(local_id) => { + let cur_hash = local_hashes.get(local_id); + let cur_active = local_active.get(local_id).copied().unwrap_or(false); + let outcome = if cur_hash != Some(hash) { + PresentOutcome::ContentUpdated + } else if cur_active != *active { + PresentOutcome::ActiveOnly + } else { + PresentOutcome::Unchanged + }; + actions.push(Action::Present { + name: name.clone(), + local_id: *local_id, + outcome, + active: *active, + }); + } + } + } + for (name, local_id) in local { + if !server_names.contains(name.as_str()) { + actions.push(Action::Vanished { + name: name.clone(), + local_id: *local_id, + }); + } + } + Plan { actions } +} + +pub type LocalState = (HashMap, HashMap); + +pub fn load_local_state(conn: &Connection, source_id: i64) -> Result { + let mut stmt = conn.prepare( + "SELECT s.id, b.hash, s.is_active + FROM sieve_scripts s JOIN blobs b ON b.id = s.blob_id + JOIN sync_id_managesieve m ON m.local_id = s.id + WHERE m.source_id = ?1", + )?; + let rows = stmt.query_map(params![source_id], |row| { + let id: i64 = row.get(0)?; + let hash: Vec = row.get(1)?; + let active: i64 = row.get(2)?; + Ok((id, hash, active != 0)) + })?; + let mut hashes = HashMap::new(); + let mut active = HashMap::new(); + for r in rows { + let (id, hash, a) = r?; + if hash.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&hash); + hashes.insert(id, arr); + } + active.insert(id, a); + } + Ok((hashes, active)) +} + +pub fn apply_new( + tx: &rusqlite::Transaction<'_>, + source_id: i64, + name: &str, + bytes: &[u8], +) -> Result { + let blob_id = db::blobs::intern_blob(tx, bytes)?; + tx.execute( + "INSERT INTO sieve_scripts (name, is_active, blob_id) VALUES (?1, 0, ?2)", + params![name, blob_id], + )?; + let local_id = tx.last_insert_rowid(); + db::managesieve_ids::insert(tx, source_id, name, local_id)?; + Ok(local_id) +} + +pub fn apply_content_update( + tx: &rusqlite::Transaction<'_>, + local_id: i64, + bytes: &[u8], +) -> Result<(), Error> { + let blob_id = db::blobs::intern_blob(tx, bytes)?; + tx.execute( + "UPDATE sieve_scripts SET blob_id = ?1 WHERE id = ?2", + params![blob_id, local_id], + )?; + Ok(()) +} + +pub fn apply_delete( + tx: &rusqlite::Transaction<'_>, + source_id: i64, + name: &str, + local_id: i64, +) -> Result<(), Error> { + tx.execute("DELETE FROM sieve_scripts WHERE id = ?1", params![local_id])?; + db::managesieve_ids::delete(tx, source_id, name)?; + Ok(()) +} + +pub fn apply_active_assignment( + conn: &mut Connection, + new_active_local: Option, +) -> Result<(), Error> { + let tx = conn.transaction()?; + let existing_active: Option = tx + .query_row( + "SELECT id FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .optional()?; + if existing_active != new_active_local { + if existing_active.is_some() { + tx.execute( + "UPDATE sieve_scripts SET is_active = 0 WHERE is_active = 1", + [], + )?; + } + if let Some(id) = new_active_local { + tx.execute( + "UPDATE sieve_scripts SET is_active = 1 WHERE id = ?1", + params![id], + )?; + } + } + tx.commit()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::init; + use crate::db::sources::{SourceKey, upsert_source}; + use blake3::Hasher; + + fn hash(b: &[u8]) -> [u8; 32] { + let mut h = Hasher::new(); + h.update(b); + h.finalize().into() + } + + fn mem() -> (Connection, i64) { + let c = Connection::open_in_memory().unwrap(); + init::apply_schema(&c).unwrap(); + let sid = upsert_source( + &c, + &SourceKey { + kind: "managesieve".to_owned(), + session_url: "sieve://h:4190".to_owned(), + account_id: "a".to_owned(), + }, + None, + "a", + ) + .unwrap(); + (c, sid) + } + + #[test] + fn plan_classifies_new_present_unchanged_and_vanished() { + let h_v = hash(b"v"); + let h_w = hash(b"w"); + let server: Vec<(String, bool, [u8; 32])> = vec![ + ("a".into(), false, h_v), + ("b".into(), true, h_w), + ("c".into(), false, h_v), + ]; + let mut local: HashMap = HashMap::new(); + local.insert("b".into(), 2); + local.insert("c".into(), 3); + local.insert("d".into(), 4); + let mut lh: HashMap = HashMap::new(); + lh.insert(2, h_w); + lh.insert(3, h_v); + lh.insert(4, h_v); + let mut la: HashMap = HashMap::new(); + la.insert(2, true); + la.insert(3, false); + la.insert(4, false); + let p = plan(&server, &local, &lh, &la); + let mut new_count = 0; + let mut unchanged = 0; + let mut content_updated = 0; + let mut active_only = 0; + let mut vanished = 0; + for a in p.actions { + match a { + Action::New { .. } => new_count += 1, + Action::Present { + outcome: PresentOutcome::Unchanged, + .. + } => unchanged += 1, + Action::Present { + outcome: PresentOutcome::ContentUpdated, + .. + } => content_updated += 1, + Action::Present { + outcome: PresentOutcome::ActiveOnly, + .. + } => active_only += 1, + Action::Vanished { .. } => vanished += 1, + } + } + assert_eq!(new_count, 1); + assert_eq!(unchanged, 2); + assert_eq!(content_updated, 0); + assert_eq!(active_only, 0); + assert_eq!(vanished, 1); + } + + #[test] + fn plan_flags_content_update_when_hash_differs() { + let h_v = hash(b"v"); + let h_w = hash(b"w"); + let server: Vec<(String, bool, [u8; 32])> = vec![("a".into(), false, h_w)]; + let mut local: HashMap = HashMap::new(); + local.insert("a".into(), 1); + let mut lh = HashMap::new(); + lh.insert(1, h_v); + let mut la = HashMap::new(); + la.insert(1, false); + let p = plan(&server, &local, &lh, &la); + assert!(matches!( + p.actions[0], + Action::Present { + outcome: PresentOutcome::ContentUpdated, + .. + } + )); + } + + #[test] + fn plan_flags_active_only_when_only_flag_differs() { + let h = hash(b"same"); + let server: Vec<(String, bool, [u8; 32])> = vec![("a".into(), true, h)]; + let mut local: HashMap = HashMap::new(); + local.insert("a".into(), 1); + let mut lh = HashMap::new(); + lh.insert(1, h); + let mut la = HashMap::new(); + la.insert(1, false); + let p = plan(&server, &local, &lh, &la); + assert!(matches!( + p.actions[0], + Action::Present { + outcome: PresentOutcome::ActiveOnly, + .. + } + )); + } + + #[test] + fn apply_new_inserts_script_and_id_mapping() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id = apply_new(&tx, sid, "vacation", b"require[\"x\"];").unwrap(); + tx.commit().unwrap(); + assert!(id > 0); + let local = db::managesieve_ids::local_for(&c, sid, "vacation") + .unwrap() + .unwrap(); + assert_eq!(local, id); + let count: i64 = c + .query_row("SELECT count(*) FROM sieve_scripts", [], |r| r.get(0)) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn apply_content_update_swaps_blob_pointer() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id = apply_new(&tx, sid, "vacation", b"v1").unwrap(); + tx.commit().unwrap(); + let tx = c.transaction().unwrap(); + apply_content_update(&tx, id, b"v2").unwrap(); + tx.commit().unwrap(); + let blob: Vec = c + .query_row( + "SELECT b.data FROM sieve_scripts s JOIN blobs b ON b.id=s.blob_id WHERE s.id=?1", + params![id], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(blob, b"v2"); + } + + #[test] + fn apply_active_assignment_swaps_active_in_one_transaction() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id_a = apply_new(&tx, sid, "a", b"x").unwrap(); + let id_b = apply_new(&tx, sid, "b", b"y").unwrap(); + tx.commit().unwrap(); + apply_active_assignment(&mut c, Some(id_a)).unwrap(); + let cur: Option = c + .query_row( + "SELECT id FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .optional() + .unwrap(); + assert_eq!(cur, Some(id_a)); + apply_active_assignment(&mut c, Some(id_b)).unwrap(); + let cur: Option = c + .query_row( + "SELECT id FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .optional() + .unwrap(); + assert_eq!(cur, Some(id_b)); + apply_active_assignment(&mut c, None).unwrap(); + let cur: Option = c + .query_row( + "SELECT id FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .optional() + .unwrap(); + assert_eq!(cur, None); + } + + #[test] + fn apply_delete_clears_active_before_destroy() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id = apply_new(&tx, sid, "a", b"x").unwrap(); + tx.commit().unwrap(); + apply_active_assignment(&mut c, Some(id)).unwrap(); + let tx = c.transaction().unwrap(); + apply_delete(&tx, sid, "a", id).unwrap(); + tx.commit().unwrap(); + let count: i64 = c + .query_row("SELECT count(*) FROM sieve_scripts", [], |r| r.get(0)) + .unwrap(); + assert_eq!(count, 0); + assert!( + db::managesieve_ids::local_for(&c, sid, "a") + .unwrap() + .is_none() + ); + } + + #[test] + fn apply_new_dedups_identical_script_bytes_at_blob_level() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id_a = apply_new(&tx, sid, "a", b"require [\"fileinto\"];\n").unwrap(); + let id_b = apply_new(&tx, sid, "b", b"require [\"fileinto\"];\n").unwrap(); + tx.commit().unwrap(); + let blob_a: i64 = c + .query_row( + "SELECT blob_id FROM sieve_scripts WHERE id = ?1", + params![id_a], + |r| r.get(0), + ) + .unwrap(); + let blob_b: i64 = c + .query_row( + "SELECT blob_id FROM sieve_scripts WHERE id = ?1", + params![id_b], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(blob_a, blob_b, "identical bytes must share a blob row"); + let blob_count: i64 = c + .query_row("SELECT count(*) FROM blobs", [], |r| r.get(0)) + .unwrap(); + assert_eq!(blob_count, 1, "blob table must contain a single row"); + } + + #[test] + fn apply_content_update_orphans_old_blob_for_gc() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id = apply_new(&tx, sid, "a", b"v1").unwrap(); + tx.commit().unwrap(); + let tx = c.transaction().unwrap(); + apply_content_update(&tx, id, b"v2").unwrap(); + tx.commit().unwrap(); + let blob_count_before_gc: i64 = c + .query_row("SELECT count(*) FROM blobs", [], |r| r.get(0)) + .unwrap(); + assert_eq!(blob_count_before_gc, 2, "old blob still present pre-GC"); + let tx = c.unchecked_transaction().unwrap(); + crate::db::blobs::gc_orphan_blobs(&tx).unwrap(); + tx.commit().unwrap(); + let blob_count_after_gc: i64 = c + .query_row("SELECT count(*) FROM blobs", [], |r| r.get(0)) + .unwrap(); + assert_eq!(blob_count_after_gc, 1, "orphan blob reclaimed by GC"); + } + + #[test] + fn partial_unique_index_rejects_two_active_rows() { + let (mut c, sid) = mem(); + let tx = c.transaction().unwrap(); + let id_a = apply_new(&tx, sid, "a", b"x").unwrap(); + let id_b = apply_new(&tx, sid, "b", b"y").unwrap(); + tx.commit().unwrap(); + c.execute( + "UPDATE sieve_scripts SET is_active = 1 WHERE id = ?1", + params![id_a], + ) + .unwrap(); + let err = c + .execute( + "UPDATE sieve_scripts SET is_active = 1 WHERE id = ?1", + params![id_b], + ) + .unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("UNIQUE") || msg.contains("unique"), + "expected unique-constraint failure, got {msg}" + ); + } +} diff --git a/src/sync/import_takeout.rs b/src/sync/import_takeout.rs new file mode 100644 index 0000000..26655b0 --- /dev/null +++ b/src/sync/import_takeout.rs @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod calendar; +pub mod contacts; +pub mod coordinator; +pub mod labels; +pub mod mail; +pub mod mbox; +pub mod tree; +pub mod walk; + +pub use coordinator::{TakeoutImportConfig, run}; diff --git a/src/sync/import_takeout/calendar.rs b/src/sync/import_takeout/calendar.rs new file mode 100644 index 0000000..bb6dd95 --- /dev/null +++ b/src/sync/import_takeout/calendar.rs @@ -0,0 +1,309 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::path::Path; + +use rusqlite::{Connection, OptionalExtension, Transaction, params}; +use serde_json::Value; + +use crate::db::takeout_ids; +use crate::error::Error; +use crate::logging::Logger; +use crate::sync::TypeCounts; +use crate::sync::import_dav::calcard; + +const DEFAULT_CALENDAR: &str = "Imported"; + +pub fn process_file( + conn: &mut Connection, + file_path: &Path, + source_id: i64, + calendar_cache: &mut HashMap, + calendar_counts: &mut TypeCounts, + event_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let text = match std::fs::read_to_string(file_path) { + Ok(s) => s, + Err(e) => { + logger.warn(&format!("{file_path:?}: read: {e}")); + event_counts.failed += 1; + return Ok(()); + } + }; + + let entries = match calcard::ical_to_jscalendar_entries(&text) { + Ok(v) => v, + Err(calcard::CalcardError::NoEntries) => { + logger.warn(&format!("{file_path:?}: iCalendar contains no events")); + return Ok(()); + } + Err(e) => { + logger.warn(&format!("{file_path:?}: iCalendar parse: {e}")); + event_counts.failed += 1; + return Ok(()); + } + }; + + let metadata = parse_calendar_metadata(&text); + let container_name = metadata + .x_wr_calname + .clone() + .unwrap_or_else(|| DEFAULT_CALENDAR.to_owned()); + + let tx = conn.transaction()?; + let calendar_local = ensure_calendar( + &tx, + source_id, + &container_name, + &metadata, + calendar_cache, + calendar_counts, + )?; + + for (ordinal, mut entry) in entries.into_iter().enumerate() { + match insert_event( + &tx, + source_id, + calendar_local, + file_path, + ordinal, + &mut entry, + event_counts, + ) { + Ok(()) => {} + Err(e) => { + logger.warn(&format!("{file_path:?}#{ordinal}: event insert: {e}")); + event_counts.failed += 1; + } + } + } + tx.commit()?; + Ok(()) +} + +#[derive(Debug, Default, Clone)] +struct CalendarMetadata { + x_wr_calname: Option, + x_wr_caldesc: Option, + x_wr_timezone: Option, +} + +fn ensure_calendar( + tx: &Transaction<'_>, + source_id: i64, + name: &str, + metadata: &CalendarMetadata, + cache: &mut HashMap, + counts: &mut TypeCounts, +) -> Result { + if let Some(&id) = cache.get(name) { + return Ok(id); + } + if let Some(id) = takeout_ids::local_for(tx, source_id, takeout_ids::CALENDAR, name)? { + cache.insert(name.to_owned(), id); + return Ok(id); + } + tx.execute( + "INSERT INTO calendars + (name, description, time_zone, sort_order, is_subscribed, is_visible, is_default) + VALUES (?1, ?2, ?3, 0, 1, 1, 0)", + params![name, metadata.x_wr_caldesc, metadata.x_wr_timezone], + )?; + let id = tx.last_insert_rowid(); + takeout_ids::insert(tx, source_id, takeout_ids::CALENDAR, name, id)?; + cache.insert(name.to_owned(), id); + counts.created += 1; + counts.fetched += 1; + Ok(id) +} + +fn insert_event( + tx: &Transaction<'_>, + source_id: i64, + calendar_local: i64, + file_path: &Path, + ordinal: usize, + entry: &mut calcard::JsCalendarEntry, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let (is_draft, use_default_alerts, uid_field) = + calcard::strip_extracted_fields_from_event(&mut entry.data); + let uid = match uid_field + .as_deref() + .map(str::trim) + .filter(|s| !s.is_empty()) + { + Some(u) => u.to_owned(), + None => calcard::synthesise_uid(&format!("{}#{ordinal}", file_path.to_string_lossy())), + }; + let data_type = entry.data_type.as_column(); + let data_json = entry.data.to_string(); + + let existing = takeout_ids::local_for(tx, source_id, takeout_ids::CALENDAR_EVENT, &uid)?; + + if let Some(local) = existing { + let calendar_ids = + merge_container_id(tx, "calendar_events", "calendar_ids", local, calendar_local)?; + let current: (String, i64, i64, String, String) = tx.query_row( + "SELECT calendar_ids, is_draft, use_default_alerts, data, data_type + FROM calendar_events WHERE id = ?1", + params![local], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)), + )?; + let want = ( + calendar_ids.clone(), + is_draft as i64, + use_default_alerts as i64, + data_json.clone(), + data_type.to_owned(), + ); + if current == want { + counts.skipped += 1; + return Ok(()); + } + tx.execute( + "UPDATE calendar_events SET calendar_ids = ?1, is_draft = ?2, + use_default_alerts = ?3, data = ?4, data_type = ?5 + WHERE id = ?6", + params![ + calendar_ids, + is_draft as i64, + use_default_alerts as i64, + data_json, + data_type, + local, + ], + )?; + counts.fetched += 1; + return Ok(()); + } + + let calendar_ids = format!("[{calendar_local}]"); + tx.execute( + "INSERT INTO calendar_events + (calendar_ids, is_draft, use_default_alerts, data, data_type) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + calendar_ids, + is_draft as i64, + use_default_alerts as i64, + data_json, + data_type, + ], + )?; + let local_id = tx.last_insert_rowid(); + takeout_ids::insert(tx, source_id, takeout_ids::CALENDAR_EVENT, &uid, local_id)?; + counts.created += 1; + counts.fetched += 1; + Ok(()) +} + +fn merge_container_id( + tx: &Transaction<'_>, + table: &str, + column: &str, + local_id: i64, + new_id: i64, +) -> Result { + let current: String = tx + .query_row( + &format!("SELECT {column} FROM {table} WHERE id = ?1"), + params![local_id], + |r| r.get(0), + ) + .optional()? + .unwrap_or_else(|| "[]".to_owned()); + let mut ids: Vec = match serde_json::from_str::(¤t) { + Ok(Value::Array(arr)) => arr.into_iter().filter_map(|v| v.as_i64()).collect(), + _ => Vec::new(), + }; + if !ids.contains(&new_id) { + ids.push(new_id); + } + ids.sort_unstable(); + ids.dedup(); + Ok(Value::Array(ids.into_iter().map(Value::from).collect()).to_string()) +} + +fn parse_calendar_metadata(text: &str) -> CalendarMetadata { + let mut out = CalendarMetadata::default(); + let mut inside_vevent = false; + for raw_line in text.lines() { + let line = raw_line.trim_end_matches('\r'); + if line.starts_with("BEGIN:VEVENT") + || line.starts_with("BEGIN:VTODO") + || line.starts_with("BEGIN:VJOURNAL") + { + inside_vevent = true; + continue; + } + if line.starts_with("END:VEVENT") + || line.starts_with("END:VTODO") + || line.starts_with("END:VJOURNAL") + { + inside_vevent = false; + continue; + } + if inside_vevent { + continue; + } + if let Some(rest) = line.strip_prefix("X-WR-CALNAME:") { + out.x_wr_calname = Some(rest.trim().to_owned()); + } else if let Some(rest) = line.strip_prefix("X-WR-CALDESC:") { + out.x_wr_caldesc = Some(rest.trim().to_owned()); + } else if let Some(rest) = line.strip_prefix("X-WR-TIMEZONE:") { + out.x_wr_timezone = Some(rest.trim().to_owned()); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_metadata_finds_xwr_fields() { + let ical = "BEGIN:VCALENDAR\r\n\ + PRODID:-//Google Inc//Google Calendar 70.9054//EN\r\n\ + X-WR-CALNAME:My Calendar\r\n\ + X-WR-CALDESC:Some description\r\n\ + X-WR-TIMEZONE:Europe/Madrid\r\n\ + BEGIN:VEVENT\r\nUID:e1@google.com\r\nDTSTAMP:20250101T000000Z\r\n\ + END:VEVENT\r\nEND:VCALENDAR\r\n"; + let m = parse_calendar_metadata(ical); + assert_eq!(m.x_wr_calname.as_deref(), Some("My Calendar")); + assert_eq!(m.x_wr_caldesc.as_deref(), Some("Some description")); + assert_eq!(m.x_wr_timezone.as_deref(), Some("Europe/Madrid")); + } + + #[test] + fn parse_metadata_ignores_xwr_inside_vevent() { + let ical = "BEGIN:VCALENDAR\r\n\ + BEGIN:VEVENT\r\nUID:e1\r\nX-WR-CALNAME:NOT THIS\r\nEND:VEVENT\r\n\ + END:VCALENDAR\r\n"; + let m = parse_calendar_metadata(ical); + assert!(m.x_wr_calname.is_none()); + } + + #[test] + fn parse_metadata_empty_when_missing() { + let ical = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"; + let m = parse_calendar_metadata(ical); + assert!(m.x_wr_calname.is_none()); + assert!(m.x_wr_caldesc.is_none()); + assert!(m.x_wr_timezone.is_none()); + } + + #[test] + fn parse_metadata_handles_lf_only_line_endings() { + let ical = "BEGIN:VCALENDAR\nX-WR-CALNAME:Foo\nEND:VCALENDAR\n"; + let m = parse_calendar_metadata(ical); + assert_eq!(m.x_wr_calname.as_deref(), Some("Foo")); + } +} diff --git a/src/sync/import_takeout/contacts.rs b/src/sync/import_takeout/contacts.rs new file mode 100644 index 0000000..4d13149 --- /dev/null +++ b/src/sync/import_takeout/contacts.rs @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::path::Path; + +use rusqlite::{Connection, Transaction, params}; + +use crate::db::takeout_ids; +use crate::error::Error; +use crate::logging::Logger; +use crate::sync::TypeCounts; +use crate::sync::import_dav::calcard; + +const DEFAULT_ADDRESS_BOOK: &str = "Imported"; + +pub fn process_file( + conn: &mut Connection, + file_path: &Path, + source_id: i64, + address_book_local: &mut Option, + book_counts: &mut TypeCounts, + card_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let text = match std::fs::read_to_string(file_path) { + Ok(s) => s, + Err(e) => { + logger.warn(&format!("{file_path:?}: read: {e}")); + card_counts.failed += 1; + return Ok(()); + } + }; + + let chunks = split_vcards(&text); + if chunks.is_empty() { + return Ok(()); + } + + let tx = conn.transaction()?; + let book_local = match *address_book_local { + Some(id) => id, + None => { + let id = ensure_default_address_book(&tx, source_id, book_counts)?; + *address_book_local = Some(id); + id + } + }; + + for (ordinal, chunk) in chunks.into_iter().enumerate() { + match insert_card( + &tx, + source_id, + book_local, + file_path, + ordinal, + &chunk, + card_counts, + ) { + Ok(()) => {} + Err(e) => { + logger.warn(&format!("{file_path:?}#{ordinal}: vCard: {e}")); + card_counts.failed += 1; + } + } + } + tx.commit()?; + Ok(()) +} + +fn ensure_default_address_book( + tx: &Transaction<'_>, + source_id: i64, + counts: &mut TypeCounts, +) -> Result { + if let Some(id) = takeout_ids::local_for( + tx, + source_id, + takeout_ids::ADDRESS_BOOK, + DEFAULT_ADDRESS_BOOK, + )? { + return Ok(id); + } + tx.execute( + "INSERT INTO address_books + (name, description, sort_order, is_default, is_subscribed) + VALUES (?1, NULL, 0, 0, 1)", + params![DEFAULT_ADDRESS_BOOK], + )?; + let id = tx.last_insert_rowid(); + takeout_ids::insert( + tx, + source_id, + takeout_ids::ADDRESS_BOOK, + DEFAULT_ADDRESS_BOOK, + id, + )?; + counts.created += 1; + counts.fetched += 1; + Ok(id) +} + +fn insert_card( + tx: &Transaction<'_>, + source_id: i64, + book_local: i64, + file_path: &Path, + ordinal: usize, + chunk: &str, + counts: &mut TypeCounts, +) -> Result<(), Error> { + let synthetic_href = format!("{}#{ordinal}", file_path.to_string_lossy()); + let card = calcard::vcard_to_jscontact(chunk, &synthetic_href) + .map_err(|e| Error::Partial(format!("vCard parse: {e}")))?; + let data_json = card.data.to_string(); + let address_book_ids = format!("[{book_local}]"); + + let existing = takeout_ids::local_for(tx, source_id, takeout_ids::CONTACT_CARD, &card.uid)?; + + if let Some(local) = existing { + let current: (String, String, String) = tx.query_row( + "SELECT uid, address_book_ids, data FROM contact_cards WHERE id = ?1", + params![local], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)), + )?; + let want = ( + card.uid.clone(), + address_book_ids.clone(), + data_json.clone(), + ); + if current == want { + counts.skipped += 1; + return Ok(()); + } + tx.execute( + "UPDATE contact_cards SET uid = ?1, address_book_ids = ?2, data = ?3 + WHERE id = ?4", + params![card.uid, address_book_ids, data_json, local], + )?; + counts.fetched += 1; + return Ok(()); + } + + tx.execute( + "INSERT INTO contact_cards (uid, address_book_ids, data) VALUES (?1, ?2, ?3)", + params![card.uid, address_book_ids, data_json], + )?; + let local_id = tx.last_insert_rowid(); + takeout_ids::insert( + tx, + source_id, + takeout_ids::CONTACT_CARD, + &card.uid, + local_id, + )?; + counts.created += 1; + counts.fetched += 1; + Ok(()) +} + +fn split_vcards(text: &str) -> Vec { + let mut chunks: Vec = Vec::new(); + let mut current: Option = None; + for raw_line in text.lines() { + let line = raw_line.trim_end_matches('\r'); + let bytes = line.as_bytes(); + if starts_with_ignore_ascii_case(bytes, b"BEGIN:VCARD") { + current = Some(String::new()); + } + if let Some(buf) = current.as_mut() { + buf.push_str(line); + buf.push('\n'); + } + if starts_with_ignore_ascii_case(bytes, b"END:VCARD") + && let Some(buf) = current.take() + { + chunks.push(buf); + } + } + chunks +} + +fn starts_with_ignore_ascii_case(haystack: &[u8], needle: &[u8]) -> bool { + haystack.len() >= needle.len() && haystack[..needle.len()].eq_ignore_ascii_case(needle) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn splits_single_card() { + let text = "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:A\r\nEND:VCARD\r\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 1); + assert!(chunks[0].contains("FN:A")); + } + + #[test] + fn splits_multiple_cards() { + let text = "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:A\r\nEND:VCARD\r\n\ + BEGIN:VCARD\r\nVERSION:3.0\r\nFN:B\r\nEND:VCARD\r\n\ + BEGIN:VCARD\r\nVERSION:3.0\r\nFN:C\r\nEND:VCARD\r\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 3); + assert!(chunks[0].contains("FN:A")); + assert!(chunks[1].contains("FN:B")); + assert!(chunks[2].contains("FN:C")); + } + + #[test] + fn skips_lines_outside_any_card() { + let text = "Some preamble garbage\r\n\ + BEGIN:VCARD\r\nVERSION:3.0\r\nFN:A\r\nEND:VCARD\r\n\ + garbage between cards\r\n\ + BEGIN:VCARD\r\nVERSION:3.0\r\nFN:B\r\nEND:VCARD\r\n\ + trailing garbage\r\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 2); + assert!(!chunks[0].contains("garbage")); + } + + #[test] + fn preserves_folded_continuation_lines_inside_a_card() { + let text = "BEGIN:VCARD\r\nVERSION:3.0\r\n\ + ADR:;;1 Infinite Loop\\nCupertino\\, CA 95014\\nUnited States\r\n \ + ;;;;;\r\nEND:VCARD\r\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 1); + assert!(chunks[0].contains("ADR:")); + assert!(chunks[0].contains(";;;;;")); + } + + #[test] + fn handles_lf_only_line_endings() { + let text = "BEGIN:VCARD\nVERSION:3.0\nFN:LF\nEND:VCARD\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 1); + assert!(chunks[0].contains("FN:LF")); + } + + #[test] + fn empty_input_yields_no_chunks() { + assert!(split_vcards("").is_empty()); + assert!(split_vcards("not a vcard\n").is_empty()); + } + + #[test] + fn unterminated_card_is_dropped() { + let text = "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:A\r\n"; + let chunks = split_vcards(text); + assert!(chunks.is_empty()); + } + + #[test] + fn case_insensitive_boundary_match() { + let text = "begin:vcard\r\nFN:CaseTest\r\nend:vcard\r\n"; + let chunks = split_vcards(text); + assert_eq!(chunks.len(), 1); + assert!(chunks[0].contains("FN:CaseTest")); + } +} diff --git a/src/sync/import_takeout/coordinator.rs b/src/sync/import_takeout/coordinator.rs new file mode 100644 index 0000000..53b2b77 --- /dev/null +++ b/src/sync/import_takeout/coordinator.rs @@ -0,0 +1,382 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use rusqlite::{Connection, params}; +use url::Url; + +use crate::db; +use crate::db::sources::SourceKey; +use crate::db::takeout_ids; +use crate::error::Error; +use crate::logging::{LEVEL_DEFAULT, LEVEL_PROGRESS, Logger}; +use crate::sync::{CommonConfig, Summary, TypeCounts}; + +use super::calendar; +use super::contacts; +use super::labels::MappingOptions; +use super::mail; +use super::walk::{self, FileKind}; + +#[derive(Debug)] +pub struct TakeoutImportConfig { + pub takeout_root: PathBuf, + pub allow_source_change: bool, + pub automap: bool, +} + +pub fn run(common: CommonConfig, config: TakeoutImportConfig) -> Result { + let logger = common.logger; + if common.threads > 1 && logger.enabled(LEVEL_PROGRESS) { + eprintln!("takeout importer is single-threaded; --threads value will be ignored"); + } + + let canonical = std::fs::canonicalize(&config.takeout_root) + .map_err(|e| Error::Usage(format!("--path {:?}: {e}", config.takeout_root)))?; + if !canonical.is_dir() { + return Err(Error::Usage(format!("not a directory: {canonical:?}"))); + } + + let walk_result = walk::walk_with_logger(&canonical, logger) + .map_err(|e| Error::Usage(format!("walking {canonical:?}: {e}")))?; + if walk_result.is_empty() { + return Err(Error::Usage(format!( + "{canonical:?}: no .mbox / .ics / .vcf files found under this path" + ))); + } + + if logger.enabled(LEVEL_PROGRESS) { + let mbox_n = walk_result.by_kind(FileKind::Mbox).count(); + let ics_n = walk_result.by_kind(FileKind::Ics).count(); + let vcf_n = walk_result.by_kind(FileKind::Vcf).count(); + eprintln!( + "takeout discovery: {} .mbox, {} .ics, {} .vcf (io failures: {}, symlink cycles: {})", + mbox_n, ics_n, vcf_n, walk_result.io_failures, walk_result.symlink_cycles + ); + } + + let mut conn = db::init::open(&common.archive)?; + let session_url = file_url_for(&canonical)?; + let account_id = canonical.to_string_lossy().into_owned(); + let account_name = canonical + .file_name() + .and_then(|n| n.to_str()) + .map(str::to_owned); + + if let Some((existing_url, existing_account)) = + db::sources::conflicting_source(&conn, "takeout", &session_url, &account_id)? + && !config.allow_source_change + { + return Err(Error::SourceChange(format!( + "archive already records takeout source {existing_url} / {existing_account}; \ + re-run with --allow-source-change or use a fresh archive" + ))); + } + + let source_key = SourceKey { + kind: "takeout".to_owned(), + session_url: session_url.clone(), + account_id: account_id.clone(), + }; + let source_id = if common.dry_run { + db::sources::find_source(&conn, &source_key)?.unwrap_or(-1) + } else { + db::sources::upsert_source(&conn, &source_key, account_name.as_deref(), "")? + }; + + if common.dry_run { + return Ok(dry_run_summary(&conn, source_id, &walk_result, logger)); + } + + let options = MappingOptions { + automap: config.automap, + }; + + let mut mailbox_counts = TypeCounts::default(); + let mut email_counts = TypeCounts::default(); + let mut calendar_counts = TypeCounts::default(); + let mut calendar_event_counts = TypeCounts::default(); + let mut book_counts = TypeCounts::default(); + let mut card_counts = TypeCounts::default(); + + let mut mailbox_cache: HashMap = + takeout_ids::all_for_type(&conn, source_id, takeout_ids::MAILBOX)?; + + process_mbox_files( + &mut conn, + source_id, + &walk_result, + options, + &mut mailbox_cache, + &mut mailbox_counts, + &mut email_counts, + logger, + )?; + + process_ics_files( + &mut conn, + source_id, + &walk_result, + &mut calendar_counts, + &mut calendar_event_counts, + logger, + )?; + + process_vcf_files( + &mut conn, + source_id, + &walk_result, + &mut book_counts, + &mut card_counts, + logger, + )?; + + let no_failures = mailbox_counts.failed == 0 + && email_counts.failed == 0 + && calendar_counts.failed == 0 + && calendar_event_counts.failed == 0 + && book_counts.failed == 0 + && card_counts.failed == 0; + if no_failures { + let tx = conn.unchecked_transaction()?; + db::blobs::gc_orphan_blobs(&tx)?; + tx.commit()?; + } + + if logger.enabled(LEVEL_DEFAULT) { + eprintln!( + "takeout: mailbox c={} f={} | email c={} u={} f={} | calendar c={} f={} | \ + event c={} u={} f={} | addressbook c={} | card c={} u={} f={}", + mailbox_counts.created, + mailbox_counts.failed, + email_counts.created, + email_counts.fetched.saturating_sub(email_counts.created), + email_counts.failed, + calendar_counts.created, + calendar_counts.failed, + calendar_event_counts.created, + calendar_event_counts + .fetched + .saturating_sub(calendar_event_counts.created), + calendar_event_counts.failed, + book_counts.created, + card_counts.created, + card_counts.fetched.saturating_sub(card_counts.created), + card_counts.failed, + ); + } + + Ok(Summary { + per_type: vec![ + ("mailbox", mailbox_counts), + ("email", email_counts), + ("calendar", calendar_counts), + ("calendarevent", calendar_event_counts), + ("addressbook", book_counts), + ("contactcard", card_counts), + ], + retries_observed: 0, + retry_after_sleeps: 0, + }) +} + +#[allow(clippy::too_many_arguments)] +fn process_mbox_files( + conn: &mut Connection, + source_id: i64, + walk_result: &walk::WalkResult, + options: MappingOptions, + mailbox_cache: &mut HashMap, + mailbox_counts: &mut TypeCounts, + email_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + for file in walk_result.by_kind(FileKind::Mbox) { + let fallback = mailbox_name_from_filename(&file.path); + if logger.enabled(LEVEL_PROGRESS) { + eprintln!( + "mbox: processing {:?} (fallback mailbox: {:?})", + file.path, fallback + ); + } + let ctx = mail::InsertContext { + source_id, + fallback_mailbox: &fallback, + options, + mailbox_cache, + }; + match mail::process_file(conn, &file.path, ctx, mailbox_counts, email_counts, logger) { + Ok(()) => {} + Err(e) => { + logger.warn(&format!("mbox {:?}: {e}", file.path)); + email_counts.failed += 1; + } + } + } + Ok(()) +} + +fn process_ics_files( + conn: &mut Connection, + source_id: i64, + walk_result: &walk::WalkResult, + calendar_counts: &mut TypeCounts, + event_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let mut cache: HashMap = + takeout_ids::all_for_type(conn, source_id, takeout_ids::CALENDAR)?; + for file in walk_result.by_kind(FileKind::Ics) { + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("ics: processing {:?}", file.path); + } + if let Err(e) = calendar::process_file( + conn, + &file.path, + source_id, + &mut cache, + calendar_counts, + event_counts, + logger, + ) { + logger.warn(&format!("ics {:?}: {e}", file.path)); + event_counts.failed += 1; + } + } + Ok(()) +} + +fn process_vcf_files( + conn: &mut Connection, + source_id: i64, + walk_result: &walk::WalkResult, + book_counts: &mut TypeCounts, + card_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let mut book_local: Option = + takeout_ids::local_for(conn, source_id, takeout_ids::ADDRESS_BOOK, "Imported")?; + for file in walk_result.by_kind(FileKind::Vcf) { + if logger.enabled(LEVEL_PROGRESS) { + eprintln!("vcf: processing {:?}", file.path); + } + if let Err(e) = contacts::process_file( + conn, + &file.path, + source_id, + &mut book_local, + book_counts, + card_counts, + logger, + ) { + logger.warn(&format!("vcf {:?}: {e}", file.path)); + card_counts.failed += 1; + } + } + Ok(()) +} + +fn mailbox_name_from_filename(path: &Path) -> String { + path.file_stem() + .and_then(|s| s.to_str()) + .map(str::to_owned) + .unwrap_or_else(|| "Imported".to_owned()) +} + +fn file_url_for(p: &Path) -> Result { + Url::from_file_path(p) + .map(|u| u.to_string()) + .map_err(|_| Error::Usage(format!("cannot encode takeout path as file:// URL: {p:?}"))) +} + +fn dry_run_summary( + conn: &Connection, + source_id: i64, + walk_result: &walk::WalkResult, + logger: Logger, +) -> Summary { + let mbox_n = walk_result.by_kind(FileKind::Mbox).count(); + let ics_n = walk_result.by_kind(FileKind::Ics).count(); + let vcf_n = walk_result.by_kind(FileKind::Vcf).count(); + let local_emails = if source_id < 0 { + 0 + } else { + count_rows(conn, source_id, takeout_ids::EMAIL) + }; + let local_events = if source_id < 0 { + 0 + } else { + count_rows(conn, source_id, takeout_ids::CALENDAR_EVENT) + }; + let local_cards = if source_id < 0 { + 0 + } else { + count_rows(conn, source_id, takeout_ids::CONTACT_CARD) + }; + if logger.enabled(LEVEL_DEFAULT) { + eprintln!("dry-run: mbox files={mbox_n} ics files={ics_n} vcf files={vcf_n}"); + eprintln!( + " existing in archive: emails={local_emails} events={local_events} cards={local_cards}" + ); + } + Summary { + per_type: vec![ + ("mailbox", TypeCounts::default()), + ("email", TypeCounts::default()), + ("calendar", TypeCounts::default()), + ("calendarevent", TypeCounts::default()), + ("addressbook", TypeCounts::default()), + ("contactcard", TypeCounts::default()), + ], + retries_observed: 0, + retry_after_sleeps: 0, + } +} + +fn count_rows(conn: &Connection, source_id: i64, type_name: &str) -> u64 { + conn.query_row( + "SELECT COUNT(*) FROM sync_id_takeout WHERE source_id = ?1 AND type_name = ?2", + params![source_id, type_name], + |row| row.get::<_, i64>(0), + ) + .unwrap_or(0) as u64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn file_url_encodes_unicode_path() { + let p = Path::new("/tmp/Año"); + let u = file_url_for(p).unwrap(); + assert!(u.starts_with("file://")); + assert!(u.contains("A%C3%B1o")); + } + + #[test] + fn file_url_rejects_relative_path() { + let err = file_url_for(Path::new("./relative")).unwrap_err(); + assert!(matches!(err, Error::Usage(_))); + } + + #[test] + fn mailbox_name_from_filename_strips_extension() { + let p = Path::new("/some/dir/Github.mbox"); + assert_eq!(mailbox_name_from_filename(p), "Github"); + } + + #[test] + fn mailbox_name_from_filename_handles_dotted_basename() { + let p = Path::new("/x/All mail Including Spam and Trash.mbox"); + assert_eq!( + mailbox_name_from_filename(p), + "All mail Including Spam and Trash" + ); + } +} diff --git a/src/sync/import_takeout/labels.rs b/src/sync/import_takeout/labels.rs new file mode 100644 index 0000000..1a51446 --- /dev/null +++ b/src/sync/import_takeout/labels.rs @@ -0,0 +1,302 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::BTreeSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LabelClassification { + pub mailboxes: Vec, + pub keywords: BTreeSet, + pub had_explicit_seen_signal: bool, + pub had_unread_signal: bool, + pub opened_won_over_unread: bool, +} + +impl LabelClassification { + pub fn keywords_sorted(&self) -> Vec { + self.keywords.iter().cloned().collect() + } + + pub fn is_empty(&self) -> bool { + self.mailboxes.is_empty() && self.keywords.is_empty() + } +} + +#[derive(Debug, Clone, Copy)] +pub struct MappingOptions { + pub automap: bool, +} + +impl Default for MappingOptions { + fn default() -> Self { + Self { automap: true } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MailboxAssignment { + pub canonical_path: String, + pub role: Option<&'static str>, +} + +pub fn parse_header(value: &str) -> Vec { + value + .split(',') + .map(str::trim) + .filter(|t| !t.is_empty()) + .map(str::to_owned) + .collect() +} + +pub fn classify(tokens: &[String]) -> LabelClassification { + let mut mailboxes: Vec = Vec::new(); + let mut keywords: BTreeSet = BTreeSet::new(); + let mut had_opened = false; + let mut had_unread = false; + + for tok in tokens { + if is_category_token(tok) { + continue; + } + match tok.as_str() { + "Starred" => { + keywords.insert("$flagged".to_owned()); + } + "Important" => { + keywords.insert("$important".to_owned()); + } + "Opened" => { + had_opened = true; + keywords.insert("$seen".to_owned()); + } + "Unread" => { + had_unread = true; + } + other => { + let canonical = canonical_label(other); + if !mailboxes.iter().any(|m| m == &canonical) { + mailboxes.push(canonical); + } + } + } + } + + let opened_won_over_unread = had_opened && had_unread; + LabelClassification { + mailboxes, + keywords, + had_explicit_seen_signal: had_opened, + had_unread_signal: had_unread, + opened_won_over_unread, + } +} + +pub fn role_for_mailbox(canonical_path: &str, opts: MappingOptions) -> Option<&'static str> { + if !opts.automap { + return None; + } + if canonical_path.contains('/') { + return None; + } + match canonical_path { + "Inbox" => Some("inbox"), + "Sent" => Some("sent"), + "Drafts" => Some("drafts"), + "Trash" => Some("trash"), + "Spam" => Some("junk"), + "Archive" => Some("archive"), + _ => None, + } +} + +pub fn assignments_for( + classification: &LabelClassification, + opts: MappingOptions, +) -> Vec { + classification + .mailboxes + .iter() + .map(|p| MailboxAssignment { + canonical_path: p.clone(), + role: role_for_mailbox(p, opts), + }) + .collect() +} + +fn is_category_token(t: &str) -> bool { + matches!( + t, + "Category Personal" + | "Category Promotions" + | "Category Social" + | "Category Updates" + | "Category Forums" + ) +} + +fn canonical_label(raw: &str) -> String { + match raw { + "Archived" => "Archive".to_owned(), + other => other.to_owned(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn classify_str(s: &str) -> LabelClassification { + classify(&parse_header(s)) + } + + #[test] + fn parse_header_splits_on_comma_and_trims() { + let v = parse_header("Inbox, Opened ,Starred,,Important"); + assert_eq!(v, vec!["Inbox", "Opened", "Starred", "Important"]); + } + + #[test] + fn parse_header_empty_yields_empty() { + assert!(parse_header("").is_empty()); + assert!(parse_header(",, ,, ").is_empty()); + } + + #[test] + fn inbox_token_becomes_inbox_mailbox_with_role() { + let c = classify_str("Inbox"); + assert_eq!(c.mailboxes, vec!["Inbox".to_owned()]); + let a = assignments_for(&c, MappingOptions::default()); + assert_eq!(a[0].role, Some("inbox")); + } + + #[test] + fn sent_drafts_trash_spam_archive_map_to_roles() { + for (token, role) in [ + ("Sent", "sent"), + ("Drafts", "drafts"), + ("Trash", "trash"), + ("Spam", "junk"), + ("Archived", "archive"), + ] { + let c = classify_str(token); + let a = assignments_for(&c, MappingOptions::default()); + assert_eq!(a[0].role, Some(role), "token {token}"); + } + let c = classify_str("Archived"); + assert_eq!(c.mailboxes, vec!["Archive".to_owned()]); + } + + #[test] + fn starred_important_become_keywords_not_mailboxes() { + let c = classify_str("Starred,Important"); + assert!(c.mailboxes.is_empty()); + assert!(c.keywords.contains("$flagged")); + assert!(c.keywords.contains("$important")); + } + + #[test] + fn opened_yields_seen_keyword() { + let c = classify_str("Inbox,Opened"); + assert!(c.keywords.contains("$seen")); + } + + #[test] + fn unread_alone_means_no_seen() { + let c = classify_str("Inbox,Unread"); + assert!(!c.keywords.contains("$seen")); + assert!(c.had_unread_signal); + } + + #[test] + fn opened_plus_unread_marks_collision_and_keeps_seen() { + let c = classify_str("Inbox,Opened,Unread"); + assert!(c.keywords.contains("$seen")); + assert!(c.opened_won_over_unread); + } + + #[test] + fn category_tokens_are_dropped_silently() { + let c = classify_str("Inbox,Category Promotions,Category Updates,Opened"); + assert_eq!(c.mailboxes, vec!["Inbox".to_owned()]); + assert!(c.keywords.contains("$seen")); + } + + #[test] + fn nested_labels_preserve_full_path() { + let c = classify_str("Inbox,Label_001/Label_002_under_Label_001"); + assert!( + c.mailboxes + .contains(&"Label_001/Label_002_under_Label_001".to_owned()) + ); + let a = assignments_for(&c, MappingOptions::default()); + let nested = a + .iter() + .find(|m| m.canonical_path == "Label_001/Label_002_under_Label_001") + .unwrap(); + assert_eq!(nested.role, None); + } + + #[test] + fn custom_labels_get_no_role() { + let c = classify_str("Inbox,Github,Newsletter"); + let a = assignments_for(&c, MappingOptions::default()); + let github = a.iter().find(|m| m.canonical_path == "Github").unwrap(); + assert_eq!(github.role, None); + } + + #[test] + fn realistic_fixture_label_sets_classify_as_expected() { + let c = classify_str("Archived,Important,Opened,Category Social,Github"); + assert!(c.mailboxes.contains(&"Archive".to_owned())); + assert!(c.mailboxes.contains(&"Github".to_owned())); + assert!(c.keywords.contains("$important")); + assert!(c.keywords.contains("$seen")); + assert!(!c.mailboxes.iter().any(|m| m.contains("Category"))); + + let c = classify_str("Important,Trash,Category Social,Unread,Github"); + assert!(c.mailboxes.contains(&"Trash".to_owned())); + assert!(c.mailboxes.contains(&"Github".to_owned())); + assert!(c.keywords.contains("$important")); + assert!(!c.keywords.contains("$seen")); + assert!(c.had_unread_signal); + } + + #[test] + fn noautomap_suppresses_roles_but_keeps_names() { + let c = classify_str("Inbox,Sent,Github"); + let a = assignments_for(&c, MappingOptions { automap: false }); + assert!(a.iter().all(|m| m.role.is_none())); + let names: Vec<&str> = a.iter().map(|m| m.canonical_path.as_str()).collect(); + assert!(names.contains(&"Inbox")); + assert!(names.contains(&"Sent")); + assert!(names.contains(&"Github")); + } + + #[test] + fn keywords_sorted_returns_deterministic_order() { + let c = classify_str("Starred,Important,Opened"); + let k = c.keywords_sorted(); + assert_eq!( + k, + vec![ + "$flagged".to_owned(), + "$important".to_owned(), + "$seen".to_owned() + ] + ); + } + + #[test] + fn duplicate_mailbox_token_dedups() { + let c = classify_str("Github,Github,Inbox"); + let count = c + .mailboxes + .iter() + .filter(|m| m.as_str() == "Github") + .count(); + assert_eq!(count, 1); + } +} diff --git a/src/sync/import_takeout/mail.rs b/src/sync/import_takeout/mail.rs new file mode 100644 index 0000000..e34321b --- /dev/null +++ b/src/sync/import_takeout/mail.rs @@ -0,0 +1,416 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use std::time::{Duration, UNIX_EPOCH}; + +use rusqlite::{Connection, OptionalExtension, Transaction, params}; +use serde_json::Value; +use time::OffsetDateTime; +use time::format_description::well_known::Rfc3339; + +use crate::db; +use crate::db::takeout_ids; +use crate::error::Error; +use crate::logging::{LEVEL_PROGRESS, Logger}; +use crate::sync::TypeCounts; +use crate::sync::emailmeta::email_meta_from_blob; +use crate::sync::keys::index_to_json; + +use super::labels::{self, MappingOptions}; +use super::mbox::{Message, MessageIterator}; + +pub struct InsertContext<'a> { + pub source_id: i64, + pub fallback_mailbox: &'a str, + pub options: MappingOptions, + pub mailbox_cache: &'a mut HashMap, +} + +pub fn process_file( + conn: &mut Connection, + file_path: &Path, + ctx: InsertContext<'_>, + mailbox_counts: &mut TypeCounts, + email_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let file = + File::open(file_path).map_err(|e| Error::Partial(format!("open {file_path:?}: {e}")))?; + let reader = BufReader::new(file); + let mut iter = MessageIterator::new(reader); + + let InsertContext { + source_id, + fallback_mailbox, + options, + mailbox_cache, + } = ctx; + let tx = conn.transaction()?; + let mut commit_pending: u64 = 0; + + loop { + match iter.next() { + None => break, + Some(Err(e)) => { + logger.warn(&format!("{file_path:?}: mbox io: {e}")); + email_counts.failed += 1; + break; + } + Some(Ok(msg)) => { + match process_message( + &tx, + source_id, + fallback_mailbox, + options, + &msg, + mailbox_cache, + mailbox_counts, + email_counts, + logger, + ) { + Ok(()) => { + commit_pending += 1; + if commit_pending.is_multiple_of(PROGRESS_TICK) + && logger.enabled(LEVEL_PROGRESS) + { + eprintln!("{file_path:?}: processed {commit_pending} messages"); + } + } + Err(e) => { + logger.warn(&format!("{file_path:?}: message: {e}")); + email_counts.failed += 1; + } + } + } + } + } + tx.commit()?; + Ok(()) +} + +const PROGRESS_TICK: u64 = 256; + +#[allow(clippy::too_many_arguments)] +fn process_message( + tx: &Transaction<'_>, + source_id: i64, + fallback_mailbox: &str, + options: MappingOptions, + msg: &Message, + cache: &mut HashMap, + mailbox_counts: &mut TypeCounts, + email_counts: &mut TypeCounts, + logger: Logger, +) -> Result<(), Error> { + let bytes = msg.contents(); + let msg_hash = blake3::hash(bytes).to_hex().to_string(); + + let raw_header = extract_x_gmail_labels(bytes); + let had_labels_header = raw_header.is_some(); + let tokens = match &raw_header { + Some(s) => labels::parse_header(s), + None => Vec::new(), + }; + let mut classification = labels::classify(&tokens); + if classification.mailboxes.is_empty() { + let fallback = fallback_mailbox.trim(); + if fallback.is_empty() { + classification.mailboxes.push("Imported".to_owned()); + } else { + classification.mailboxes.push(fallback.to_owned()); + } + if had_labels_header { + logger.warn(&format!( + "message {msg_hash}: X-Gmail-Labels present but empty after parse; \ + placing in fallback mailbox {:?}", + classification.mailboxes[0] + )); + } else { + logger.warn(&format!( + "message {msg_hash}: no X-Gmail-Labels header; placing in fallback mailbox {:?}", + classification.mailboxes[0] + )); + } + } + if classification.opened_won_over_unread { + logger.warn(&format!( + "message {msg_hash}: X-Gmail-Labels carried both 'Opened' and 'Unread'; \ + treating as $seen (Opened wins)" + )); + } + + let mut mailbox_ids: Vec = Vec::with_capacity(classification.mailboxes.len()); + for path in &classification.mailboxes { + let id = ensure_mailbox(tx, source_id, path, options, cache, mailbox_counts)?; + mailbox_ids.push(id); + } + mailbox_ids.sort_unstable(); + mailbox_ids.dedup(); + + let keywords_json = json_string_array(&classification.keywords_sorted()); + let mailbox_ids_json = json_int_array(&mailbox_ids); + + if let Some(local_id) = takeout_ids::local_for(tx, source_id, takeout_ids::EMAIL, &msg_hash)? { + let current: (String, String) = tx + .query_row( + "SELECT mailbox_ids, keywords FROM emails WHERE id = ?1", + params![local_id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .optional()? + .unwrap_or_default(); + if current.0 != mailbox_ids_json || current.1 != keywords_json { + tx.execute( + "UPDATE emails SET mailbox_ids = ?1, keywords = ?2 WHERE id = ?3", + params![mailbox_ids_json, keywords_json, local_id], + )?; + email_counts.fetched += 1; + } else { + email_counts.skipped += 1; + } + return Ok(()); + } + + let blob_id = db::blobs::intern_blob(tx, bytes)?; + let (index, date_rfc3339) = email_meta_from_blob(bytes); + let message_match = index_to_json(&index); + let received_at = pick_received_at(msg.internal_date(), date_rfc3339.as_deref()); + + tx.execute( + "INSERT INTO emails (blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (?1, ?2, ?3, ?4, ?5)", + params![ + blob_id, + received_at, + mailbox_ids_json, + keywords_json, + message_match + ], + )?; + let local_id = tx.last_insert_rowid(); + takeout_ids::insert(tx, source_id, takeout_ids::EMAIL, &msg_hash, local_id)?; + email_counts.created += 1; + email_counts.fetched += 1; + Ok(()) +} + +fn ensure_mailbox( + tx: &Transaction<'_>, + source_id: i64, + path: &str, + options: MappingOptions, + cache: &mut HashMap, + counts: &mut TypeCounts, +) -> Result { + if let Some(&id) = cache.get(path) { + return Ok(id); + } + if let Some(id) = takeout_ids::local_for(tx, source_id, takeout_ids::MAILBOX, path)? { + cache.insert(path.to_owned(), id); + return Ok(id); + } + let (parent_id, leaf) = match path.rsplit_once('/') { + Some((parent, leaf)) => ( + Some(ensure_mailbox( + tx, source_id, parent, options, cache, counts, + )?), + leaf, + ), + None => (None, path), + }; + let role = labels::role_for_mailbox(path, options); + tx.execute( + "INSERT INTO mailboxes (name, parent_id, role, sort_order, is_subscribed) + VALUES (?1, ?2, ?3, 0, 1)", + params![leaf, parent_id, role], + )?; + let id = tx.last_insert_rowid(); + takeout_ids::insert(tx, source_id, takeout_ids::MAILBOX, path, id)?; + cache.insert(path.to_owned(), id); + counts.created += 1; + counts.fetched += 1; + Ok(id) +} + +fn json_string_array(values: &[String]) -> String { + Value::Array(values.iter().map(|s| Value::String(s.clone())).collect()).to_string() +} + +fn json_int_array(values: &[i64]) -> String { + Value::Array(values.iter().map(|&i| Value::from(i)).collect()).to_string() +} + +fn pick_received_at(internal_date: u64, date_rfc3339: Option<&str>) -> String { + if internal_date > 0 { + return format_unix_rfc3339(internal_date); + } + if let Some(d) = date_rfc3339 { + return d.to_owned(); + } + OffsetDateTime::now_utc() + .format(&Rfc3339) + .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()) +} + +fn format_unix_rfc3339(secs: u64) -> String { + let when = UNIX_EPOCH + Duration::from_secs(secs); + OffsetDateTime::from(when) + .format(&Rfc3339) + .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()) +} + +fn extract_x_gmail_labels(bytes: &[u8]) -> Option { + let mut values: Vec = Vec::new(); + let mut current: Option = None; + let mut i = 0; + while i < bytes.len() { + let nl = bytes[i..].iter().position(|&b| b == b'\n'); + let (line, advance) = match nl { + Some(p) => (&bytes[i..i + p], p + 1), + None => (&bytes[i..], bytes.len() - i), + }; + let line = line.strip_suffix(b"\r").unwrap_or(line); + if line.is_empty() { + break; + } + let is_continuation = line + .first() + .map(|b| *b == b' ' || *b == b'\t') + .unwrap_or(false); + if is_continuation { + if let Some(buf) = current.as_mut() { + let extra = std::str::from_utf8(line).unwrap_or("").trim_start(); + if !extra.is_empty() { + buf.push(' '); + buf.push_str(extra); + } + } + } else { + if let Some(buf) = current.take() { + values.push(buf); + } + if let Some(colon) = line.iter().position(|&b| b == b':') { + let name = &line[..colon]; + if name.eq_ignore_ascii_case(b"X-Gmail-Labels") { + let value = &line[colon + 1..]; + let value = std::str::from_utf8(value).unwrap_or("").trim(); + current = Some(value.to_owned()); + } + } + } + i += advance; + } + if let Some(buf) = current.take() { + values.push(buf); + } + if values.is_empty() { + None + } else { + Some(values.join(",")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extract_labels_from_simple_message() { + let bytes = b"From: a@b\r\n\ + X-Gmail-Labels: Inbox,Opened\r\n\ + Subject: hi\r\n\ + \r\n\ + body\r\n"; + assert_eq!( + extract_x_gmail_labels(bytes), + Some("Inbox,Opened".to_owned()) + ); + } + + #[test] + fn extract_labels_handles_lf_only_line_endings() { + let bytes = b"From: a@b\nX-Gmail-Labels: Inbox\n\nbody\n"; + assert_eq!(extract_x_gmail_labels(bytes), Some("Inbox".to_owned())); + } + + #[test] + fn extract_labels_handles_folded_continuation() { + let bytes = b"From: a@b\r\n\ + X-Gmail-Labels: Inbox,\r\n\ + \tImportant,Github\r\n\ + Subject: hi\r\n\ + \r\n\ + body\r\n"; + assert_eq!( + extract_x_gmail_labels(bytes), + Some("Inbox, Important,Github".to_owned()) + ); + } + + #[test] + fn extract_labels_returns_none_when_header_absent() { + let bytes = b"From: a@b\r\nSubject: hi\r\n\r\nbody\r\n"; + assert_eq!(extract_x_gmail_labels(bytes), None); + } + + #[test] + fn extract_labels_case_insensitive_name_match() { + let bytes = b"x-gmail-labels: Sent\r\n\r\n"; + assert_eq!(extract_x_gmail_labels(bytes), Some("Sent".to_owned())); + } + + #[test] + fn extract_labels_concatenates_multiple_header_occurrences() { + let bytes = b"X-Gmail-Labels: Inbox\r\nX-Gmail-Labels: Github\r\n\r\nbody"; + assert_eq!( + extract_x_gmail_labels(bytes), + Some("Inbox,Github".to_owned()) + ); + } + + #[test] + fn extract_labels_stops_at_blank_line() { + let bytes = b"X-Gmail-Labels: Inbox\r\n\r\nX-Gmail-Labels: NOT-A-HEADER\r\n"; + assert_eq!(extract_x_gmail_labels(bytes), Some("Inbox".to_owned())); + } + + #[test] + fn json_int_array_emits_compact_form() { + assert_eq!(json_int_array(&[1, 2, 3]), "[1,2,3]"); + assert_eq!(json_int_array(&[]), "[]"); + } + + #[test] + fn json_string_array_quotes_each_value() { + assert_eq!( + json_string_array(&["$seen".to_owned(), "$flagged".to_owned()]), + "[\"$seen\",\"$flagged\"]" + ); + } + + #[test] + fn pick_received_at_prefers_envelope_internal_date() { + let got = pick_received_at(1763065233, Some("2025-05-12T10:00:00+02:00")); + assert_eq!(got, "2025-11-13T20:20:33Z"); + } + + #[test] + fn pick_received_at_falls_back_to_date_header() { + let got = pick_received_at(0, Some("2025-05-12T10:00:00+02:00")); + assert_eq!(got, "2025-05-12T10:00:00+02:00"); + } + + #[test] + fn pick_received_at_last_resort_is_now_in_rfc3339() { + let got = pick_received_at(0, None); + assert!(got.ends_with("Z") || got.contains("+") || got.contains("-")); + assert!(got.contains("T")); + } +} diff --git a/src/sync/import_takeout/mbox.rs b/src/sync/import_takeout/mbox.rs new file mode 100644 index 0000000..33548e7 --- /dev/null +++ b/src/sync/import_takeout/mbox.rs @@ -0,0 +1,278 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::BufRead; + +use time::{Date, Month, OffsetDateTime, Time, UtcOffset}; + +pub struct MessageIterator { + reader: T, + message: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Message { + internal_date: u64, + envelope_from: String, + contents: Vec, +} + +impl MessageIterator +where + T: BufRead, +{ + pub fn new(reader: T) -> MessageIterator { + MessageIterator { + reader, + message: None, + } + } +} + +impl Iterator for MessageIterator +where + T: BufRead, +{ + type Item = std::io::Result; + + fn next(&mut self) -> Option { + let mut line = Vec::with_capacity(80); + + loop { + match self.reader.read_until(b'\n', &mut line) { + Ok(0) => return self.message.take().map(Ok), + Ok(_) => {} + Err(e) => return Some(Err(e)), + } + + if line.starts_with(b"From ") { + let finished = self.message.take().map(Ok); + let header = std::str::from_utf8(&line).unwrap_or(""); + self.message = Some(Message::from_envelope(header)); + if finished.is_some() { + return finished; + } + line.clear(); + continue; + } + + if let Some(message) = &mut self.message { + if line.first() != Some(&b'>') { + message.contents.extend_from_slice(&line); + line.clear(); + continue; + } + let non_gt = line.iter().position(|&c| c != b'>').unwrap_or(line.len()); + if line[non_gt..].starts_with(b"From ") { + message.contents.extend_from_slice(&line[1..]); + } else { + message.contents.extend_from_slice(&line); + } + } + line.clear(); + } + } +} + +impl Message { + fn from_envelope(header: &str) -> Self { + let trimmed = header.strip_prefix("From ").unwrap_or(header); + let (envelope_from, date_str) = match trimmed.split_once(' ') { + Some((from, rest)) => (from.trim().to_owned(), rest), + None => (String::new(), ""), + }; + let internal_date = parse_envelope_date(date_str).unwrap_or(0); + Self { + internal_date, + envelope_from, + contents: Vec::with_capacity(1024), + } + } + + pub fn internal_date(&self) -> u64 { + self.internal_date + } + + pub fn envelope_from(&self) -> &str { + &self.envelope_from + } + + pub fn contents(&self) -> &[u8] { + &self.contents + } + + pub fn unwrap_contents(self) -> Vec { + self.contents + } +} + +fn parse_envelope_date(s: &str) -> Option { + let parts: Vec<&str> = s.split_whitespace().collect(); + let (month_pos, day_pos, time_pos, offset_pos, year_pos) = match parts.len() { + 5 => (1, 2, 3, None, 4), + 6 => (1, 2, 3, Some(4), 5), + _ => return None, + }; + + let month = parse_month(parts[month_pos])?; + let day: u8 = parts[day_pos].parse().ok()?; + let (hour, minute, second) = parse_hms(parts[time_pos])?; + let year: i32 = parts[year_pos].parse().ok()?; + let offset = match offset_pos { + Some(p) => parse_offset(parts[p])?, + None => UtcOffset::UTC, + }; + + let date = Date::from_calendar_date(year, month, day).ok()?; + let time = Time::from_hms(hour, minute, second).ok()?; + let ts = OffsetDateTime::new_in_offset(date, time, offset).unix_timestamp(); + u64::try_from(ts).ok() +} + +fn parse_month(s: &str) -> Option { + Some(match s { + x if x.eq_ignore_ascii_case("jan") => Month::January, + x if x.eq_ignore_ascii_case("feb") => Month::February, + x if x.eq_ignore_ascii_case("mar") => Month::March, + x if x.eq_ignore_ascii_case("apr") => Month::April, + x if x.eq_ignore_ascii_case("may") => Month::May, + x if x.eq_ignore_ascii_case("jun") => Month::June, + x if x.eq_ignore_ascii_case("jul") => Month::July, + x if x.eq_ignore_ascii_case("aug") => Month::August, + x if x.eq_ignore_ascii_case("sep") => Month::September, + x if x.eq_ignore_ascii_case("oct") => Month::October, + x if x.eq_ignore_ascii_case("nov") => Month::November, + x if x.eq_ignore_ascii_case("dec") => Month::December, + _ => return None, + }) +} + +fn parse_hms(s: &str) -> Option<(u8, u8, u8)> { + let mut it = s.split(':'); + let h: u8 = it.next()?.parse().ok()?; + let m: u8 = it.next()?.parse().ok()?; + let sec: u8 = it.next()?.parse().ok()?; + if it.next().is_some() { + return None; + } + Some((h, m, sec)) +} + +fn parse_offset(s: &str) -> Option { + let bytes = s.as_bytes(); + if bytes.len() != 5 { + return None; + } + let sign: i8 = match bytes[0] { + b'+' => 1, + b'-' => -1, + _ => return None, + }; + let hh: i8 = std::str::from_utf8(&bytes[1..3]).ok()?.parse().ok()?; + let mm: i8 = std::str::from_utf8(&bytes[3..5]).ok()?.parse().ok()?; + UtcOffset::from_hms(sign * hh, sign * mm, 0).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn classic_ctime_envelope_parses() { + let raw = b"From god@heaven.af.mil Sat Jan 3 01:05:34 1996\n\ + Message 1\n\ + \n\ + From cras@irccrew.org Tue Jul 23 19:39:23 2002\n\ + Message 2\n\ + \n"; + let mut it = MessageIterator::new(&raw[..]); + let m1 = it.next().unwrap().unwrap(); + assert_eq!(m1.envelope_from(), "god@heaven.af.mil"); + assert_eq!(m1.internal_date(), 820631134); + assert_eq!(m1.contents(), b"Message 1\n\n"); + let m2 = it.next().unwrap().unwrap(); + assert_eq!(m2.envelope_from(), "cras@irccrew.org"); + assert_eq!(m2.internal_date(), 1027453163); + assert!(it.next().is_none()); + } + + #[test] + fn mboxrd_unescape_recovers_lines() { + let raw = b"From a@b Tue Aug 6 13:34:34 2002\n\ + Message 3\n\ + >From hello\n\ + >>From world\n\ + >>>From test\n\ + \n"; + let m = MessageIterator::new(&raw[..]).next().unwrap().unwrap(); + assert_eq!( + m.contents(), + b"Message 3\nFrom hello\n>From world\n>>From test\n\n" + ); + } + + #[test] + fn mboxrd_leaves_non_from_lines_starting_with_gt_alone() { + let raw = b"From a@b Mon Jan 15 15:30:00 2018\n\ + Message 4\n\ + > From\n\ + >F\n"; + let m = MessageIterator::new(&raw[..]).next().unwrap().unwrap(); + assert_eq!(m.contents(), b"Message 4\n> From\n>F\n"); + } + + #[test] + fn takeout_envelope_with_utc_offset_parses() { + let raw = b"From 1848707889910060830@xxx Thu Nov 13 20:20:33 +0000 2025\n\ + X-GM-THRID: 1848707889910060830\n\ + X-Gmail-Labels: Inbox,Opened\n\ + Subject: hi\n\ + \n\ + body\n"; + let m = MessageIterator::new(&raw[..]).next().unwrap().unwrap(); + assert_eq!(m.envelope_from(), "1848707889910060830@xxx"); + assert_eq!(m.internal_date(), 1763065233); + assert!(m.contents().starts_with(b"X-GM-THRID:")); + } + + #[test] + fn takeout_envelope_with_negative_offset_applies_offset() { + let raw = b"From x@y Thu Nov 13 12:20:33 -0800 2025\n\ + body\n"; + let m = MessageIterator::new(&raw[..]).next().unwrap().unwrap(); + assert_eq!(m.internal_date(), 1763065233); + } + + #[test] + fn malformed_envelope_date_yields_zero_not_error() { + let raw = b"From who knows what\nbody\n"; + let m = MessageIterator::new(&raw[..]).next().unwrap().unwrap(); + assert_eq!(m.internal_date(), 0); + } + + #[test] + fn streams_multiple_takeout_format_messages() { + let raw = b"From 1@xxx Thu Nov 13 20:20:33 +0000 2025\n\ + X-GM-THRID: 1\n\ + X-Gmail-Labels: Inbox\n\ + Subject: A\n\ + \n\ + body a\n\ + From 2@xxx Thu Nov 13 21:00:00 +0000 2025\n\ + X-GM-THRID: 2\n\ + X-Gmail-Labels: Sent,Archived\n\ + Subject: B\n\ + \n\ + body b\n"; + let parsed: Vec<_> = MessageIterator::new(&raw[..]) + .collect::, _>>() + .unwrap(); + assert_eq!(parsed.len(), 2); + assert_eq!(parsed[0].envelope_from(), "1@xxx"); + assert_eq!(parsed[1].envelope_from(), "2@xxx"); + assert!(parsed[0].contents().starts_with(b"X-GM-THRID: 1")); + } +} diff --git a/src/sync/import_takeout/tree.rs b/src/sync/import_takeout/tree.rs new file mode 100644 index 0000000..fdba6b8 --- /dev/null +++ b/src/sync/import_takeout/tree.rs @@ -0,0 +1,210 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::BTreeSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PlannedMailbox { + pub canonical_path: String, + pub leaf: String, + pub parent_path: Option, + pub role: Option<&'static str>, + pub ephemeral: bool, +} + +pub fn assemble(input: &[String], role_for: F) -> Vec +where + F: Fn(&str) -> Option<&'static str>, +{ + let explicit: BTreeSet = input.iter().cloned().collect(); + let mut all_paths: BTreeSet = BTreeSet::new(); + for p in &explicit { + for prefix in prefixes(p) { + all_paths.insert(prefix); + } + } + + let mut planned: Vec = Vec::with_capacity(all_paths.len()); + for path in all_paths { + let (parent_path, leaf) = split_path(&path); + let ephemeral = !explicit.contains(&path); + let role = role_for(&path); + planned.push(PlannedMailbox { + canonical_path: path, + leaf, + parent_path, + role, + ephemeral, + }); + } + planned +} + +pub fn vanished_depth_sort(paths: &mut [String]) { + paths.sort_by(|a, b| depth_of(b).cmp(&depth_of(a)).then_with(|| a.cmp(b))); +} + +fn prefixes(path: &str) -> Vec { + let parts: Vec<&str> = path.split('/').collect(); + let mut out = Vec::with_capacity(parts.len()); + for i in 1..=parts.len() { + out.push(parts[..i].join("/")); + } + out +} + +fn split_path(path: &str) -> (Option, String) { + match path.rsplit_once('/') { + Some((parent, leaf)) => (Some(parent.to_owned()), leaf.to_owned()), + None => (None, path.to_owned()), + } +} + +fn depth_of(path: &str) -> usize { + path.bytes().filter(|b| *b == b'/').count() + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + fn no_roles(_: &str) -> Option<&'static str> { + None + } + + fn inbox_role(p: &str) -> Option<&'static str> { + if p == "Inbox" { Some("inbox") } else { None } + } + + #[test] + fn flat_paths_pass_through_unchanged() { + let r = assemble( + &["Inbox".to_owned(), "Sent".to_owned(), "Github".to_owned()], + no_roles, + ); + let names: Vec<&str> = r.iter().map(|m| m.canonical_path.as_str()).collect(); + assert_eq!(names, vec!["Github", "Inbox", "Sent"]); + assert!(r.iter().all(|m| !m.ephemeral)); + assert!(r.iter().all(|m| m.parent_path.is_none())); + } + + #[test] + fn nested_path_creates_ephemeral_parent() { + let r = assemble( + &["Label_001/Label_002_under_Label_001".to_owned()], + no_roles, + ); + assert_eq!(r.len(), 2); + assert_eq!(r[0].canonical_path, "Label_001"); + assert!(r[0].ephemeral); + assert_eq!(r[0].parent_path, None); + assert_eq!(r[1].canonical_path, "Label_001/Label_002_under_Label_001"); + assert!(!r[1].ephemeral); + assert_eq!(r[1].parent_path, Some("Label_001".to_owned())); + assert_eq!(r[1].leaf, "Label_002_under_Label_001"); + } + + #[test] + fn explicit_parent_is_not_marked_ephemeral() { + let r = assemble( + &[ + "Label_001".to_owned(), + "Label_001/Sub".to_owned(), + "Label_001/Other".to_owned(), + ], + no_roles, + ); + let parent = r.iter().find(|m| m.canonical_path == "Label_001").unwrap(); + assert!(!parent.ephemeral); + } + + #[test] + fn three_level_deep_path_creates_all_missing_ancestors() { + let r = assemble(&["A/B/C".to_owned()], no_roles); + let names: Vec<&str> = r.iter().map(|m| m.canonical_path.as_str()).collect(); + assert_eq!(names, vec!["A", "A/B", "A/B/C"]); + assert!(r[0].ephemeral); + assert!(r[1].ephemeral); + assert!(!r[2].ephemeral); + assert_eq!(r[2].parent_path, Some("A/B".to_owned())); + } + + #[test] + fn role_closure_consulted_for_ephemeral_parents_too() { + let r = assemble(&["Inbox/Sub".to_owned()], inbox_role); + let inbox = r.iter().find(|m| m.canonical_path == "Inbox").unwrap(); + assert!(inbox.ephemeral, "auto-created from the nested label"); + assert_eq!( + inbox.role, + Some("inbox"), + "an auto-created Inbox is still the inbox" + ); + } + + #[test] + fn role_closure_applies_to_explicit_top_level() { + let r = assemble(&["Inbox".to_owned()], inbox_role); + assert_eq!(r[0].role, Some("inbox")); + } + + #[test] + fn order_is_parents_before_children() { + let r = assemble( + &[ + "Zeta/Alpha".to_owned(), + "Alpha".to_owned(), + "Beta/Gamma/Delta".to_owned(), + ], + no_roles, + ); + let names: Vec<&str> = r.iter().map(|m| m.canonical_path.as_str()).collect(); + assert_eq!( + names, + vec![ + "Alpha", + "Beta", + "Beta/Gamma", + "Beta/Gamma/Delta", + "Zeta", + "Zeta/Alpha", + ] + ); + } + + #[test] + fn duplicate_inputs_dedup() { + let r = assemble( + &["Inbox".to_owned(), "Inbox".to_owned(), "Inbox".to_owned()], + no_roles, + ); + assert_eq!(r.len(), 1); + } + + #[test] + fn vanished_depth_sort_orders_leaves_first() { + let mut v = vec![ + "Foo".to_owned(), + "Foo/Bar/Baz".to_owned(), + "Other".to_owned(), + "Foo/Bar".to_owned(), + ]; + vanished_depth_sort(&mut v); + assert_eq!(v, vec!["Foo/Bar/Baz", "Foo/Bar", "Foo", "Other",]); + } + + #[test] + fn vanished_depth_sort_handles_ties_lexicographically() { + let mut v = vec!["B".to_owned(), "A".to_owned(), "C".to_owned()]; + vanished_depth_sort(&mut v); + assert_eq!(v, vec!["A", "B", "C"]); + } + + #[test] + fn empty_input_yields_empty_output() { + let r = assemble(&[], no_roles); + assert!(r.is_empty()); + } +} diff --git a/src/sync/import_takeout/walk.rs b/src/sync/import_takeout/walk.rs new file mode 100644 index 0000000..44fb4cb --- /dev/null +++ b/src/sync/import_takeout/walk.rs @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; +use std::fs; +use std::io; +use std::os::unix::fs::MetadataExt; +use std::path::{Path, PathBuf}; + +use crate::logging::Logger; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FileKind { + Mbox, + Ics, + Vcf, +} + +impl FileKind { + fn from_extension(ext: &str) -> Option { + let lower = ext.to_ascii_lowercase(); + match lower.as_str() { + "mbox" => Some(FileKind::Mbox), + "ics" => Some(FileKind::Ics), + "vcf" => Some(FileKind::Vcf), + _ => None, + } + } +} + +#[derive(Debug, Clone)] +pub struct DiscoveredFile { + pub path: PathBuf, + pub kind: FileKind, +} + +#[derive(Debug, Default)] +pub struct WalkResult { + pub files: Vec, + pub symlink_cycles: u64, + pub io_failures: u64, +} + +impl WalkResult { + pub fn by_kind(&self, kind: FileKind) -> impl Iterator { + self.files.iter().filter(move |f| f.kind == kind) + } + + pub fn is_empty(&self) -> bool { + self.files.is_empty() + } +} + +pub fn walk(root: &Path) -> io::Result { + walk_with_logger(root, Logger::from_flags(true, 0)) +} + +pub fn walk_with_logger(root: &Path, logger: Logger) -> io::Result { + let mut result = WalkResult::default(); + let mut visited: HashSet<(u64, u64)> = HashSet::new(); + walk_dir(root, &mut result, &mut visited, logger)?; + result.files.sort_by(|a, b| a.path.cmp(&b.path)); + Ok(result) +} + +fn walk_dir( + dir: &Path, + result: &mut WalkResult, + visited: &mut HashSet<(u64, u64)>, + logger: Logger, +) -> io::Result<()> { + let metadata = fs::metadata(dir)?; + let key = (metadata.dev(), metadata.ino()); + if !visited.insert(key) { + result.symlink_cycles += 1; + logger.warn(&format!( + "takeout walk: symlink cycle broken at {dir:?} (target already visited)" + )); + return Ok(()); + } + + let entries = match fs::read_dir(dir) { + Ok(it) => it, + Err(e) => { + result.io_failures += 1; + logger.warn(&format!("takeout walk: read_dir {dir:?}: {e}")); + return Ok(()); + } + }; + + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(e) => { + result.io_failures += 1; + logger.warn(&format!("takeout walk: entry in {dir:?}: {e}")); + continue; + } + }; + let path = entry.path(); + let file_type = match entry.file_type() { + Ok(t) => t, + Err(e) => { + result.io_failures += 1; + logger.warn(&format!("takeout walk: file_type {path:?}: {e}")); + continue; + } + }; + if file_type.is_dir() { + walk_dir(&path, result, visited, logger)?; + continue; + } + if file_type.is_symlink() { + let target_meta = match fs::metadata(&path) { + Ok(m) => m, + Err(e) => { + result.io_failures += 1; + logger.warn(&format!("takeout walk: symlink target {path:?}: {e}")); + continue; + } + }; + if target_meta.is_dir() { + walk_dir(&path, result, visited, logger)?; + continue; + } + if !target_meta.is_file() { + continue; + } + } else if !file_type.is_file() { + continue; + } + if let Some(kind) = classify(&path) { + result.files.push(DiscoveredFile { path, kind }); + } + } + Ok(()) +} + +fn classify(path: &Path) -> Option { + let ext = path.extension()?.to_str()?; + FileKind::from_extension(ext) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::os::unix::fs::symlink; + use tempfile::TempDir; + + fn touch(path: &Path) { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(path, b"").unwrap(); + } + + #[test] + fn collects_only_matching_extensions() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("Mail/All.mbox")); + touch(&root.join("Calendar/cal.ics")); + touch(&root.join("Contacts/c.vcf")); + touch(&root.join("Calendar/meet_settings.json")); + touch(&root.join("archive_browser.html")); + touch(&root.join("Contacts/photo.jpg")); + let r = walk(root).unwrap(); + assert_eq!(r.files.len(), 3); + let mut kinds: Vec = r.files.iter().map(|f| f.kind).collect(); + kinds.sort_by_key(|k| format!("{k:?}")); + assert_eq!(kinds, vec![FileKind::Ics, FileKind::Mbox, FileKind::Vcf]); + } + + #[test] + fn extension_match_is_case_insensitive() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("Inbox.MBOX")); + touch(&root.join("Cal.Ics")); + touch(&root.join("All.Vcf")); + let r = walk(root).unwrap(); + assert_eq!(r.files.len(), 3); + } + + #[test] + fn recurses_into_nested_directories() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("Takeout/Mail/a.mbox")); + touch(&root.join("Takeout/Calendar/deep/b.ics")); + touch(&root.join("Takeout/Contacts/All Contacts/c.vcf")); + let r = walk(root).unwrap(); + assert_eq!(r.files.len(), 3); + } + + #[test] + fn empty_or_non_matching_tree_returns_empty() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("readme.txt")); + touch(&root.join("nested/photo.jpg")); + let r = walk(root).unwrap(); + assert!(r.is_empty()); + } + + #[test] + fn results_are_sorted_for_determinism() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("z.mbox")); + touch(&root.join("a.mbox")); + touch(&root.join("m.ics")); + let r = walk(root).unwrap(); + let names: Vec<&str> = r + .files + .iter() + .map(|f| f.path.file_name().unwrap().to_str().unwrap()) + .collect(); + assert_eq!(names, vec!["a.mbox", "m.ics", "z.mbox"]); + } + + #[test] + fn by_kind_filters_returned_iterator() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("a.mbox")); + touch(&root.join("b.mbox")); + touch(&root.join("c.ics")); + let r = walk(root).unwrap(); + assert_eq!(r.by_kind(FileKind::Mbox).count(), 2); + assert_eq!(r.by_kind(FileKind::Ics).count(), 1); + assert_eq!(r.by_kind(FileKind::Vcf).count(), 0); + } + + #[test] + fn symlink_to_directory_is_followed_once_not_infinitely() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join("real/a.mbox")); + symlink(root.join("real"), root.join("link-to-real")).unwrap(); + let r = walk(root).unwrap(); + assert_eq!(r.files.len(), 1); + } + + #[test] + fn symlink_cycle_is_broken() { + let td = TempDir::new().unwrap(); + let root = td.path(); + fs::create_dir_all(root.join("a/b")).unwrap(); + symlink(root.join("a"), root.join("a/b/loop")).unwrap(); + let r = walk(root).unwrap(); + assert!(r.symlink_cycles >= 1, "cycle counter incremented"); + } + + #[test] + fn dotted_directory_basenames_traverse_fine() { + let td = TempDir::new().unwrap(); + let root = td.path(); + touch(&root.join(".hidden/m.mbox")); + let r = walk(root).unwrap(); + assert_eq!(r.files.len(), 1); + } +} diff --git a/src/sync/keys.rs b/src/sync/keys.rs new file mode 100644 index 0000000..046f667 --- /dev/null +++ b/src/sync/keys.rs @@ -0,0 +1,269 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use serde_json::{Value, json}; + +pub fn normalize_message_id(raw: &str) -> String { + let t = raw.trim(); + let t = t.strip_prefix('<').unwrap_or(t); + let t = t.strip_suffix('>').unwrap_or(t); + t.trim().to_lowercase() +} + +pub fn fold_name(name: &str) -> String { + name.trim().to_lowercase() +} + +pub fn blake3_fields(fields: &[&str]) -> [u8; 32] { + let mut hasher = blake3::Hasher::new(); + for (i, f) in fields.iter().enumerate() { + if i > 0 { + hasher.update(b"\x1f"); + } + hasher.update(f.as_bytes()); + } + *hasher.finalize().as_bytes() +} + +pub fn blake3_bytes(bytes: &[u8]) -> [u8; 32] { + *blake3::hash(bytes).as_bytes() +} + +pub fn identity_key(name: &str, email: &str) -> [u8; 32] { + blake3_fields(&[name, &email.to_lowercase()]) +} + +pub fn participant_identity_key(calendar_address: &str, name: &str) -> [u8; 32] { + blake3_fields(&[&calendar_address.to_lowercase(), name]) +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum EmailKey { + MessageId(String), + Fallback([u8; 32]), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EmailIndex { + + pub mids: Vec, + + pub fb: [u8; 32], +} + +fn join_addrs(addrs: &[String]) -> String { + addrs + .iter() + .map(|a| a.to_lowercase()) + .collect::>() + .join(",") +} + +pub fn email_index( + message_ids: &[String], + from: &[String], + subject: &str, + sent_at: &str, + to: &[String], +) -> EmailIndex { + let mut mids: Vec = message_ids + .iter() + .map(|m| normalize_message_id(m)) + .filter(|s| !s.is_empty()) + .collect(); + mids.sort(); + mids.dedup(); + let fb = blake3_fields(&[&join_addrs(from), subject, sent_at, &join_addrs(to)]); + EmailIndex { mids, fb } +} + +pub fn index_to_json(idx: &EmailIndex) -> String { + json!({ "m": idx.mids, "f": hex(&idx.fb) }).to_string() +} + +pub fn index_from_json(text: &str) -> EmailIndex { + let v: Value = serde_json::from_str(text).unwrap_or(Value::Null); + let mids = v + .get("m") + .and_then(Value::as_array) + .map(|a| { + a.iter() + .filter_map(|x| x.as_str().map(str::to_owned)) + .collect() + }) + .unwrap_or_default(); + let fb = v + .get("f") + .and_then(Value::as_str) + .and_then(unhex) + .unwrap_or([0u8; 32]); + EmailIndex { mids, fb } +} + +fn hex(bytes: &[u8; 32]) -> String { + let mut s = String::with_capacity(64); + for b in bytes { + use std::fmt::Write; + let _ = write!(s, "{b:02x}"); + } + s +} + +fn unhex(s: &str) -> Option<[u8; 32]> { + if s.len() != 64 { + return None; + } + let mut out = [0u8; 32]; + for (i, slot) in out.iter_mut().enumerate() { + *slot = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).ok()?; + } + Some(out) +} + +pub fn email_key(idx: &EmailIndex) -> EmailKey { + if idx.mids.is_empty() { + EmailKey::Fallback(idx.fb) + } else { + EmailKey::MessageId(idx.mids.join("\x1f")) + } +} + +pub fn email_keys(indices: &[EmailIndex]) -> Vec { + indices.iter().map(email_key).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn message_id_normalization_strips_brackets_and_lowercases() { + assert_eq!(normalize_message_id(" "), "abc@host.com"); + assert_eq!(normalize_message_id("plain@x"), "plain@x"); + } + + #[test] + fn name_fold_is_case_insensitive() { + assert_eq!(fold_name(" Sales "), fold_name("sales")); + assert_ne!(fold_name("Sales"), fold_name("sale")); + } + + #[test] + fn identity_collapse_ignores_signature() { + assert_eq!( + identity_key("Alice", "A@x.test"), + identity_key("Alice", "a@x.test") + ); + } + + #[test] + fn participant_identity_key_lowercases_address_only() { + let a = participant_identity_key("MailTo:User@Host", "Bob"); + let b = participant_identity_key("mailto:user@host", "Bob"); + assert_eq!(a, b); + assert_ne!(a, participant_identity_key("mailto:user@host", "bob")); + } + + fn idx(mids: &[&str], from: &[&str], subj: &str, sent: &str, to: &[&str]) -> EmailIndex { + let m: Vec = mids.iter().map(|s| (*s).to_owned()).collect(); + let f: Vec = from.iter().map(|s| (*s).to_owned()).collect(); + let t: Vec = to.iter().map(|s| (*s).to_owned()).collect(); + email_index(&m, &f, subj, sent, &t) + } + + #[test] + fn message_id_array_is_normalized_sorted_and_order_independent() { + let a = idx(&["", ""], &[], "", "", &[]); + let b = idx(&["", ""], &[], "", "", &[]); + assert_eq!(a.mids, vec!["a@h".to_owned(), "b@h".to_owned()]); + assert_eq!(a.mids, b.mids); + } + + #[test] + fn unique_single_message_id_uses_message_id_key() { + let keys = email_keys(&[ + idx( + &[""], + &["a@x"], + "Hi", + "2020-01-01T00:00:00Z", + &["b@y"], + ), + idx( + &[""], + &["a@x"], + "Yo", + "2020-01-02T00:00:00Z", + &["b@y"], + ), + ]); + assert_eq!(keys[0], EmailKey::MessageId("m1@h".to_owned())); + assert_eq!(keys[1], EmailKey::MessageId("m2@h".to_owned())); + } + + #[test] + fn shared_message_id_matches_regardless_of_other_fields() { + let keys = email_keys(&[ + idx( + &[""], + &["a@x"], + "S", + "2020-01-01T00:00:00Z", + &["b@y"], + ), + idx( + &[""], + &["c@x"], + "T", + "2020-01-02T00:00:00Z", + &["d@y"], + ), + ]); + assert_eq!(keys[0], EmailKey::MessageId("dup@h".to_owned())); + assert_eq!(keys[0], keys[1], "same Message-ID is the same message"); + } + + #[test] + fn only_absent_message_id_falls_back_multi_is_still_message_id() { + let keys = email_keys(&[ + idx(&[], &["a@x"], "S", "2020-01-01T00:00:00Z", &["b@y"]), + idx( + &["", ""], + &["a@x"], + "S", + "2020-01-01T00:00:00Z", + &["b@y"], + ), + ]); + assert!(matches!(keys[0], EmailKey::Fallback(_))); + assert_eq!(keys[1], EmailKey::MessageId("a@h\u{1f}b@h".to_owned())); + } + + #[test] + fn fallback_is_case_insensitive_on_addresses_and_offset_preserving_on_sent_at() { + let a = idx(&[], &["A@X"], "Subj", "2020-01-01T10:00:00+02:00", &["B@Y"]); + let b = idx(&[], &["a@x"], "Subj", "2020-01-01T10:00:00+02:00", &["b@y"]); + let c = idx(&[], &["a@x"], "Subj", "2020-01-01T08:00:00Z", &["b@y"]); + assert_eq!(a.fb, b.fb); + assert_ne!(a.fb, c.fb); + } + + #[test] + fn index_json_roundtrips() { + let i = idx(&[""], &["a@x"], "S", "2020-01-01T00:00:00Z", &["b@y"]); + let j = index_to_json(&i); + let back = index_from_json(&j); + assert_eq!(i, back); + assert_eq!(email_keys(&[i]), email_keys(&[back])); + } + + #[test] + fn malformed_stored_json_degrades_to_empty_fallback() { + let back = index_from_json("{}"); + assert!(back.mids.is_empty()); + assert_eq!(back.fb, [0u8; 32]); + } +} diff --git a/src/sync/mod.rs b/src/sync/mod.rs new file mode 100644 index 0000000..1efbb14 --- /dev/null +++ b/src/sync/mod.rs @@ -0,0 +1,120 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +pub mod emailmeta; +pub mod export; +pub mod import_dav; +pub mod import_exchange_ews; +pub mod import_exchange_graph; +pub mod import_imap; +pub mod import_jmap; +pub mod import_maildir; +pub mod import_managesieve; +pub mod import_takeout; +pub mod keys; +pub mod prune; + +use std::path::PathBuf; + +use rusqlite::Connection; + +use crate::db; +use crate::error::Error; + +pub(crate) fn table_name(ty: ObjectType) -> &'static str { + match ty { + ObjectType::Mailbox => "mailboxes", + ObjectType::Email => "emails", + ObjectType::Identity => "identities", + ObjectType::SieveScript => "sieve_scripts", + ObjectType::AddressBook => "address_books", + ObjectType::ContactCard => "contact_cards", + ObjectType::Calendar => "calendars", + ObjectType::CalendarEvent => "calendar_events", + ObjectType::ParticipantIdentity => "participant_identities", + ObjectType::FileNode => "file_nodes", + } +} +use crate::jmap::account::AccountSelector; +use crate::jmap::http::{Auth, HttpClient, RetryPolicy}; +use crate::logging::Logger; +use crate::types::ObjectType; + +pub struct CommonConfig { + pub archive: PathBuf, + pub threads: usize, + pub dry_run: bool, + pub max_retries: u32, + pub allow_invalid_certs: bool, + pub logger: Logger, +} + +pub struct ConnectConfig { + pub url: String, + pub auth: Auth, + pub account: AccountSelector, +} + +pub struct ImportConfig { + pub connect: ConnectConfig, + pub objects: Option>, + pub allow_source_change: bool, +} + +pub struct ExportConfig { + pub connect: ConnectConfig, + pub objects: Option>, + pub prune: bool, + pub yes: bool, +} + +#[derive(Debug, Default, Clone)] +pub struct TypeCounts { + pub created: u64, + pub fetched: u64, + pub deleted: u64, + pub skipped: u64, + pub failed: u64, +} + +#[derive(Debug, Default, Clone)] +pub struct Summary { + pub per_type: Vec<(&'static str, TypeCounts)>, + pub retries_observed: u64, + pub retry_after_sleeps: u64, +} + +impl Summary { + pub fn any_failed(&self) -> bool { + self.per_type.iter().any(|(_, c)| c.failed > 0) + } +} + +pub struct Context { + pub conn: Connection, + pub client: HttpClient, + pub common: CommonConfig, +} + +impl Context { + pub fn open(common: CommonConfig, connect: &ConnectConfig) -> Result { + let conn = db::init::open(&common.archive)?; + let client = HttpClient::new( + connect.auth.clone(), + RetryPolicy::new(common.max_retries), + common.allow_invalid_certs, + ); + Ok(Context { + conn, + client, + common, + }) + } + + pub fn dry_run(&self) -> bool { + self.common.dry_run + } +} diff --git a/src/sync/prune.rs b/src/sync/prune.rs new file mode 100644 index 0000000..61eedaf --- /dev/null +++ b/src/sync/prune.rs @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone)] +pub struct TargetObj { + pub id: String, + pub matched: bool, + pub protected: bool, + pub may_delete: bool, + pub parent: Option, +} + +fn protected_with_ancestors(objs: &[TargetObj]) -> HashSet { + let by_id: HashMap<&str, &TargetObj> = objs.iter().map(|o| (o.id.as_str(), o)).collect(); + let mut protected: HashSet = HashSet::new(); + for o in objs { + if o.protected { + let mut cur = Some(o.id.clone()); + while let Some(id) = cur { + if !protected.insert(id.clone()) { + break; + } + cur = by_id + .get(id.as_str()) + .and_then(|x| x.parent.clone()) + .filter(|p| by_id.contains_key(p.as_str())); + } + } + } + protected +} + +fn depth(id: &str, by_id: &HashMap<&str, &TargetObj>) -> usize { + let mut d = 0; + let mut cur = by_id.get(id).and_then(|o| o.parent.clone()); + let mut seen: HashSet = HashSet::new(); + while let Some(p) = cur { + if !seen.insert(p.clone()) || !by_id.contains_key(p.as_str()) { + break; + } + d += 1; + cur = by_id.get(p.as_str()).and_then(|o| o.parent.clone()); + } + d +} + +pub fn candidates(objs: &[TargetObj], tree: bool) -> Vec { + let protected = protected_with_ancestors(objs); + let by_id: HashMap<&str, &TargetObj> = objs.iter().map(|o| (o.id.as_str(), o)).collect(); + let mut out: Vec<&TargetObj> = objs + .iter() + .filter(|o| !o.matched && o.may_delete && !protected.contains(&o.id)) + .collect(); + if tree { + out.sort_by(|a, b| { + depth(&b.id, &by_id) + .cmp(&depth(&a.id, &by_id)) + .then_with(|| a.id.cmp(&b.id)) + }); + } else { + out.sort_by(|a, b| a.id.cmp(&b.id)); + } + out.into_iter().map(|o| o.id.clone()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn o(id: &str, matched: bool, protected: bool, parent: Option<&str>) -> TargetObj { + TargetObj { + id: id.to_owned(), + matched, + protected, + may_delete: true, + parent: parent.map(str::to_owned), + } + } + + #[test] + fn unmatched_unprotected_are_candidates() { + let objs = vec![ + o("a", true, false, None), + o("b", false, false, None), + o("c", false, true, None), + ]; + assert_eq!(candidates(&objs, false), vec!["b".to_owned()]); + } + + #[test] + fn protected_excludes_ancestors() { + let objs = vec![ + o("root", false, false, None), + o("mid", false, false, Some("root")), + o("leaf", false, true, Some("mid")), + ]; + assert!(candidates(&objs, true).is_empty()); + } + + #[test] + fn may_delete_false_is_skipped() { + let mut x = o("x", false, false, None); + x.may_delete = false; + assert!(candidates(&[x], false).is_empty()); + } + + #[test] + fn tree_destroy_is_leaf_first() { + let objs = vec![ + o("root", false, false, None), + o("child", false, false, Some("root")), + o("grand", false, false, Some("child")), + ]; + assert_eq!( + candidates(&objs, true), + vec!["grand".to_owned(), "child".to_owned(), "root".to_owned()] + ); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..66649e1 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,142 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use crate::error::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ObjectType { + Mailbox, + Email, + Identity, + SieveScript, + AddressBook, + ContactCard, + Calendar, + CalendarEvent, + ParticipantIdentity, + FileNode, +} + +impl ObjectType { + pub const ALL: [ObjectType; 10] = [ + ObjectType::Mailbox, + ObjectType::Email, + ObjectType::Identity, + ObjectType::SieveScript, + ObjectType::AddressBook, + ObjectType::ContactCard, + ObjectType::Calendar, + ObjectType::CalendarEvent, + ObjectType::ParticipantIdentity, + ObjectType::FileNode, + ]; + + pub fn token(self) -> &'static str { + match self { + ObjectType::Mailbox => "mailbox", + ObjectType::Email => "email", + ObjectType::Identity => "identity", + ObjectType::SieveScript => "sievescript", + ObjectType::AddressBook => "addressbook", + ObjectType::ContactCard => "contactcard", + ObjectType::Calendar => "calendar", + ObjectType::CalendarEvent => "calendarevent", + ObjectType::ParticipantIdentity => "participantidentity", + ObjectType::FileNode => "filenode", + } + } + + pub fn jmap_name(self) -> &'static str { + match self { + ObjectType::Mailbox => "Mailbox", + ObjectType::Email => "Email", + ObjectType::Identity => "Identity", + ObjectType::SieveScript => "SieveScript", + ObjectType::AddressBook => "AddressBook", + ObjectType::ContactCard => "ContactCard", + ObjectType::Calendar => "Calendar", + ObjectType::CalendarEvent => "CalendarEvent", + ObjectType::ParticipantIdentity => "ParticipantIdentity", + ObjectType::FileNode => "FileNode", + } + } + + pub fn capability_urn(self) -> &'static str { + match self { + ObjectType::Mailbox | ObjectType::Email | ObjectType::Identity => { + "urn:ietf:params:jmap:mail" + } + ObjectType::SieveScript => "urn:ietf:params:jmap:sieve", + ObjectType::AddressBook | ObjectType::ContactCard => "urn:ietf:params:jmap:contacts", + ObjectType::Calendar | ObjectType::CalendarEvent | ObjectType::ParticipantIdentity => { + "urn:ietf:params:jmap:calendars" + } + ObjectType::FileNode => "urn:ietf:params:jmap:filenode", + } + } + + pub fn parse(token: &str) -> Result { + let normalized = token.trim().to_ascii_lowercase(); + ObjectType::ALL + .into_iter() + .find(|t| t.token() == normalized) + .ok_or_else(|| Error::Usage(format!("unknown object type: {token}"))) + } +} + +pub fn parse_object_list(list: &str) -> Result, Error> { + let mut out = Vec::new(); + for token in list.split(',') { + let token = token.trim(); + if token.is_empty() { + continue; + } + let parsed = ObjectType::parse(token)?; + if !out.contains(&parsed) { + out.push(parsed); + } + } + if out.is_empty() { + return Err(Error::Usage( + "--objects given but resolved to an empty type list".to_owned(), + )); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn token_roundtrip() { + for t in ObjectType::ALL { + assert_eq!(ObjectType::parse(t.token()).unwrap(), t); + } + } + + #[test] + fn parse_list_dedups_and_trims() { + let parsed = parse_object_list(" mailbox, email ,mailbox").unwrap(); + assert_eq!(parsed, vec![ObjectType::Mailbox, ObjectType::Email]); + } + + #[test] + fn parse_list_case_insensitive() { + let parsed = parse_object_list("Mailbox,EMAIL").unwrap(); + assert_eq!(parsed, vec![ObjectType::Mailbox, ObjectType::Email]); + } + + #[test] + fn parse_list_rejects_unknown() { + assert!(parse_object_list("mailbox,bogus").is_err()); + } + + #[test] + fn parse_list_rejects_empty() { + assert!(parse_object_list(" , ").is_err()); + } +} diff --git a/tests/integration/baikal.rs b/tests/integration/baikal.rs new file mode 100644 index 0000000..e200421 --- /dev/null +++ b/tests/integration/baikal.rs @@ -0,0 +1,463 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; +use std::time::Duration; + +use rusqlite::params; +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::{SyncBuilder, SyncRunner}; +use testcontainers::{Container, GenericBuildableImage, GenericImage, ImageExt}; + +use super::data::{RawFixture, load_icals, load_vcards, malformed_ical, rewrite_uid}; +use super::dav_client::DavSeed; +use super::error::{ContainerError, ContainerResult}; +use super::layouts::{self, Layout}; +use super::{Account, Endpoint}; + +const BASE_IMAGE: &str = "ckulka/baikal:0.10.1-nginx"; +const HTTP_PORT: u16 = 80; +const REALM: &str = "BaikalDAV"; + +const ADMIN_PASSWORD_HASH: &str = "004b53ae84286ba6003b34e8ef404826"; +const DIGEST_USER1: &str = "b7a47e657a4a966d0d844b1211416024"; +const DIGEST_USER2: &str = "9fc67834c5a099f0a2eccea3f6918169"; +const DIGEST_USER3: &str = "e1b30b108dad01b323a999b0bb1252ab"; + +const SCHEMA: &str = include_str!("baikal_schema.sql"); + +const BAIKAL_YAML: &str = "system: + configured_version: '0.10.1' + timezone: 'UTC' + card_enabled: true + cal_enabled: true + invite_from: 'noreply@vandelay.test' + dav_auth_type: 'Basic' + admin_passwordhash: '004b53ae84286ba6003b34e8ef404826' + failed_access_message: 'user %u authentication failure for Baikal' + auth_realm: 'BaikalDAV' + base_uri: '' +database: + backend: 'sqlite' + sqlite_file: '/var/www/baikal/Specific/db/db.sqlite' +"; + +const DOCKERFILE: &str = r#"FROM ckulka/baikal:0.10.1-nginx + +COPY baikal.yaml /var/www/baikal/config/baikal.yaml +COPY db.sqlite /var/www/baikal/Specific/db/db.sqlite +RUN touch /var/www/baikal/Specific/INSTALL_DISABLED && \ + chown -R www-data:www-data /var/www/baikal/config /var/www/baikal/Specific && \ + chmod 0640 /var/www/baikal/config/baikal.yaml /var/www/baikal/Specific/db/db.sqlite +"#; + +pub struct Baikal { + container: Container, + pub http: Endpoint, + pub accounts: Vec, +} + +impl Baikal { + pub fn start() -> ContainerResult { + let db_bytes = build_sqlite_db()?; + + let image: GenericImage = GenericBuildableImage::new("vandelay-baikal", "test") + .with_dockerfile_string(DOCKERFILE.to_owned()) + .with_data(BAIKAL_YAML.as_bytes().to_vec(), "baikal.yaml") + .with_data(db_bytes, "db.sqlite") + .build_image() + .map_err(|e| ContainerError::Seed(format!("baikal build: {e}")))?; + + let _ = BASE_IMAGE; + + let request = image + .with_exposed_port(HTTP_PORT.tcp()) + .with_wait_for(WaitFor::message_on_stderr("start worker process")) + .with_startup_timeout(Duration::from_secs(180)); + + let container = request.start()?; + let host = container.get_host()?.to_string(); + let http = Endpoint::new(host, container.get_host_port_ipv4(HTTP_PORT.tcp())?); + + let accounts: Vec = layouts::accounts() + .iter() + .map(|name| Account { + username: (*name).to_owned(), + password: layouts::PASSWORD.to_owned(), + layout: layouts::layout_for(name), + }) + .collect(); + + Ok(Self { + container, + http, + accounts, + }) + } + + pub fn base_url(&self) -> String { + self.http.http_base() + } + + pub fn dav_root(&self) -> String { + format!("{}/dav.php", self.base_url()) + } + + pub fn calendar_home(&self, account: &Account) -> String { + format!("{}/calendars/{}/", self.dav_root(), account.username) + } + + pub fn addressbook_home(&self, account: &Account) -> String { + format!("{}/addressbooks/{}/", self.dav_root(), account.username) + } + + pub fn seed_all(&self) -> ContainerResult> { + let icals = load_icals()?; + let vcards = load_vcards()?; + let mut out = Vec::new(); + for acct in &self.accounts { + let seed = self.seed_account(acct, &icals, &vcards)?; + out.push(seed); + } + Ok(out) + } + + pub fn seed_account( + &self, + account: &Account, + icals: &[RawFixture], + vcards: &[RawFixture], + ) -> ContainerResult { + let client = DavSeed::new(self.dav_root(), &account.username, &account.password); + let layout = &account.layout; + let mut seed = AccountSeed::new(account.username.clone()); + + for (idx, cal) in layout.calendars.iter().enumerate() { + let segment = collection_segment("cal", idx, cal); + let path = format!("/calendars/{}/{}/", account.username, segment); + let mkcal = format!( + r#" + + + {cal} + + + + + +"# + ); + client.mkcalendar(&path, Some(&mkcal))?; + let mut plan = CollectionPlan::new((*cal).to_owned(), path.clone()); + + let count = events_per_calendar(layout, idx); + let offset = events_offset(layout, idx); + for (i, fx) in icals.iter().cycle().skip(offset).take(count).enumerate() { + let item = format!("{path}{}-{}.ics", fx.name, i); + let suffix = format!("{segment}-{i}"); + let (bytes, uid) = rewrite_uid(&fx.bytes, &suffix).ok_or_else(|| { + ContainerError::Seed(format!("ical fixture {} has no UID", fx.name)) + })?; + client.put(&item, "text/calendar; charset=utf-8", &bytes)?; + plan.items.push(SeededItem { + uid, + href: item, + source: bytes, + }); + } + seed.calendars.push(plan); + } + + for (idx, ab) in layout.address_books.iter().enumerate() { + let segment = collection_segment("ab", idx, ab); + let path = format!("/addressbooks/{}/{}/", account.username, segment); + let mkbook = format!( + r#" + + + + {ab} + +"# + ); + client.mkcol(&path, Some(&mkbook))?; + let mut plan = CollectionPlan::new((*ab).to_owned(), path.clone()); + + let count = contacts_per_book(layout, idx); + let offset = contacts_offset(layout, idx); + for (i, fx) in vcards.iter().cycle().skip(offset).take(count).enumerate() { + let item = format!("{path}{}-{}.vcf", fx.name, i); + let suffix = format!("{segment}-{i}"); + let (bytes, uid) = rewrite_uid(&fx.bytes, &suffix).ok_or_else(|| { + ContainerError::Seed(format!("vcard fixture {} has no UID", fx.name)) + })?; + client.put(&item, "text/vcard; charset=utf-8", &bytes)?; + plan.items.push(SeededItem { + uid, + href: item, + source: bytes, + }); + } + seed.address_books.push(plan); + } + + Ok(seed) + } + + pub fn seed_malformed_event( + &self, + account: &Account, + calendar_segment_idx: usize, + tag: &str, + ) -> ContainerResult { + let client = DavSeed::new(self.dav_root(), &account.username, &account.password); + let segment = collection_segment( + "cal", + calendar_segment_idx, + account.layout.calendars[calendar_segment_idx], + ); + let path = format!( + "/calendars/{}/{}/broken-{tag}.ics", + account.username, segment + ); + let bad = malformed_ical(tag); + client.put(&path, "text/calendar; charset=utf-8", &bad.bytes)?; + Ok(path) + } + + pub fn delete_item(&self, account: &Account, path: &str) -> ContainerResult<()> { + let client = DavSeed::new(self.dav_root(), &account.username, &account.password); + client.delete(path)?; + Ok(()) + } + + pub fn add_event( + &self, + account: &Account, + calendar_segment_idx: usize, + tag: &str, + icals: &[RawFixture], + ) -> ContainerResult<(String, String)> { + let client = DavSeed::new(self.dav_root(), &account.username, &account.password); + let display = account.layout.calendars[calendar_segment_idx]; + let segment = collection_segment("cal", calendar_segment_idx, display); + let fixture = icals.first().ok_or_else(|| { + ContainerError::Seed("no ical fixture available for add_event".to_owned()) + })?; + let suffix = format!("added-{tag}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix) + .ok_or_else(|| ContainerError::Seed("ical fixture has no UID".to_owned()))?; + let path = format!("/calendars/{}/{segment}/added-{tag}.ics", account.username); + client.put(&path, "text/calendar; charset=utf-8", &bytes)?; + Ok((path, uid)) + } + + pub fn add_contact( + &self, + account: &Account, + book_segment_idx: usize, + tag: &str, + vcards: &[RawFixture], + ) -> ContainerResult<(String, String)> { + let client = DavSeed::new(self.dav_root(), &account.username, &account.password); + let display = account.layout.address_books[book_segment_idx]; + let segment = collection_segment("ab", book_segment_idx, display); + let fixture = vcards.first().ok_or_else(|| { + ContainerError::Seed("no vcard fixture available for add_contact".to_owned()) + })?; + let suffix = format!("added-{tag}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix) + .ok_or_else(|| ContainerError::Seed("vcard fixture has no UID".to_owned()))?; + let path = format!( + "/addressbooks/{}/{segment}/added-{tag}.vcf", + account.username + ); + client.put(&path, "text/vcard; charset=utf-8", &bytes)?; + Ok((path, uid)) + } + + pub fn verify_seed(&self, seeds: &[AccountSeed]) -> ContainerResult<()> { + for (acct, seed) in self.accounts.iter().zip(seeds) { + let client = DavSeed::new(self.dav_root(), &acct.username, &acct.password); + let body = client.propfind(&format!("/calendars/{}/", acct.username), 1)?; + if !body.contains("multistatus") { + return Err(ContainerError::Protocol(format!( + "baikal propfind for {} returned no multistatus", + acct.username + ))); + } + let _ = seed; + } + Ok(()) + } + + pub fn stop(self) -> ContainerResult<()> { + self.container.stop()?; + Ok(()) + } +} + +fn build_sqlite_db() -> ContainerResult> { + let tmp = tempfile::NamedTempFile::new() + .map_err(|e| ContainerError::Seed(format!("baikal tempfile: {e}")))?; + let path = tmp.path().to_path_buf(); + drop(tmp); + + { + let conn = rusqlite::Connection::open(&path) + .map_err(|e| ContainerError::Seed(format!("baikal sqlite open: {e}")))?; + conn.execute_batch(SCHEMA) + .map_err(|e| ContainerError::Seed(format!("baikal schema: {e}")))?; + + let users: [(&str, &str); 3] = [ + (layouts::ACCOUNT1, DIGEST_USER1), + (layouts::ACCOUNT2, DIGEST_USER2), + (layouts::ACCOUNT3, DIGEST_USER3), + ]; + for (name, digest) in users { + conn.execute( + "INSERT INTO users (username, digesta1) VALUES (?1, ?2)", + params![name, digest], + ) + .map_err(|e| ContainerError::Seed(format!("baikal user {name}: {e}")))?; + let uri = format!("principals/{name}"); + let email = format!("{name}@vandelay.test"); + let display = format!("Vandelay {name}"); + conn.execute( + "INSERT INTO principals (uri, email, displayname) VALUES (?1, ?2, ?3)", + params![uri, email, display], + ) + .map_err(|e| ContainerError::Seed(format!("baikal principal {name}: {e}")))?; + conn.execute( + "INSERT INTO principals (uri, email, displayname) VALUES (?1, NULL, NULL)", + params![format!("{uri}/calendar-proxy-read")], + ) + .map_err(|e| ContainerError::Seed(format!("baikal cal-proxy-read {name}: {e}")))?; + conn.execute( + "INSERT INTO principals (uri, email, displayname) VALUES (?1, NULL, NULL)", + params![format!("{uri}/calendar-proxy-write")], + ) + .map_err(|e| ContainerError::Seed(format!("baikal cal-proxy-write {name}: {e}")))?; + } + + let _ = REALM; + let _ = ADMIN_PASSWORD_HASH; + } + + let bytes = + std::fs::read(&path).map_err(|e| ContainerError::Seed(format!("baikal read db: {e}")))?; + let _ = std::fs::remove_file(&path); + Ok(bytes) +} + +fn collection_segment(prefix: &str, idx: usize, name: &str) -> String { + let mut out = format!("{prefix}-{:02}-", idx); + for c in name.chars() { + if c.is_ascii_alphanumeric() { + out.push(c.to_ascii_lowercase()); + } else if !out.ends_with('-') { + out.push('-'); + } + } + out.trim_end_matches('-').to_owned() +} + +fn events_per_calendar(layout: &Layout, idx: usize) -> usize { + if layout.calendars.is_empty() { + return 0; + } + let base = layout.event_count / layout.calendars.len().max(1); + let extra = if idx == 0 { + layout.event_count % layout.calendars.len() + } else { + 0 + }; + base + extra +} + +fn events_offset(layout: &Layout, idx: usize) -> usize { + (0..idx).map(|i| events_per_calendar(layout, i)).sum() +} + +fn contacts_per_book(layout: &Layout, idx: usize) -> usize { + if layout.address_books.is_empty() { + return 0; + } + let base = layout.contact_count / layout.address_books.len().max(1); + let extra = if idx == 0 { + layout.contact_count % layout.address_books.len() + } else { + 0 + }; + base + extra +} + +fn contacts_offset(layout: &Layout, idx: usize) -> usize { + (0..idx).map(|i| contacts_per_book(layout, i)).sum() +} + +#[derive(Debug, Clone)] +pub struct SeededItem { + pub uid: String, + pub href: String, + pub source: Vec, +} + +#[derive(Debug, Clone)] +pub struct CollectionPlan { + pub display_name: String, + pub base_href: String, + pub items: Vec, +} + +impl CollectionPlan { + fn new(display_name: String, base_href: String) -> Self { + Self { + display_name, + base_href, + items: Vec::new(), + } + } +} + +#[derive(Debug, Clone)] +pub struct AccountSeed { + pub username: String, + pub calendars: Vec, + pub address_books: Vec, +} + +impl AccountSeed { + fn new(username: String) -> Self { + Self { + username, + calendars: Vec::new(), + address_books: Vec::new(), + } + } + + pub fn total_events(&self) -> usize { + self.calendars.iter().map(|c| c.items.len()).sum() + } + + pub fn total_contacts(&self) -> usize { + self.address_books.iter().map(|c| c.items.len()).sum() + } + + pub fn event_uids(&self) -> HashSet { + self.calendars + .iter() + .flat_map(|c| c.items.iter().map(|i| i.uid.clone())) + .collect() + } + + pub fn contact_uids(&self) -> HashSet { + self.address_books + .iter() + .flat_map(|c| c.items.iter().map(|i| i.uid.clone())) + .collect() + } +} diff --git a/tests/integration/baikal_schema.sql b/tests/integration/baikal_schema.sql new file mode 100644 index 0000000..6d3bf7c --- /dev/null +++ b/tests/integration/baikal_schema.sql @@ -0,0 +1,147 @@ +CREATE TABLE addressbooks ( + id integer primary key asc NOT NULL, + principaluri text NOT NULL, + displayname text, + uri text NOT NULL, + description text, + synctoken integer DEFAULT 1 NOT NULL +); + +CREATE TABLE cards ( + id integer primary key asc NOT NULL, + addressbookid integer NOT NULL, + carddata blob, + uri text NOT NULL, + lastmodified integer, + etag text, + size integer +); + +CREATE TABLE addressbookchanges ( + id integer primary key asc NOT NULL, + uri text, + synctoken integer NOT NULL, + addressbookid integer NOT NULL, + operation integer NOT NULL +); + +CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken); +CREATE TABLE calendarobjects ( + id integer primary key asc NOT NULL, + calendardata blob NOT NULL, + uri text NOT NULL, + calendarid integer NOT NULL, + lastmodified integer NOT NULL, + etag text NOT NULL, + size integer NOT NULL, + componenttype text, + firstoccurence integer, + lastoccurence integer, + uid text +); + +CREATE TABLE calendars ( + id integer primary key asc NOT NULL, + synctoken integer DEFAULT 1 NOT NULL, + components text NOT NULL +); + +CREATE TABLE calendarinstances ( + id integer primary key asc NOT NULL, + calendarid integer, + principaluri text, + access integer, + displayname text, + uri text NOT NULL, + description text, + calendarorder integer, + calendarcolor text, + timezone text, + transparent bool, + share_href text, + share_displayname text, + share_invitestatus integer DEFAULT '2', + UNIQUE (principaluri, uri), + UNIQUE (calendarid, principaluri), + UNIQUE (calendarid, share_href) +); + +CREATE TABLE calendarchanges ( + id integer primary key asc NOT NULL, + uri text, + synctoken integer NOT NULL, + calendarid integer NOT NULL, + operation integer NOT NULL +); + +CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken); + +CREATE TABLE calendarsubscriptions ( + id integer primary key asc NOT NULL, + uri text NOT NULL, + principaluri text NOT NULL, + source text NOT NULL, + displayname text, + refreshrate text, + calendarorder integer, + calendarcolor text, + striptodos bool, + stripalarms bool, + stripattachments bool, + lastmodified int +); + +CREATE TABLE schedulingobjects ( + id integer primary key asc NOT NULL, + principaluri text NOT NULL, + calendardata blob, + uri text NOT NULL, + lastmodified integer, + etag text NOT NULL, + size integer NOT NULL +); + +CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri); +BEGIN TRANSACTION; +CREATE TABLE locks ( + id integer primary key asc NOT NULL, + owner text, + timeout integer, + created integer, + token text, + scope integer, + depth integer, + uri text +); +COMMIT; +CREATE TABLE principals ( + id INTEGER PRIMARY KEY ASC NOT NULL, + uri TEXT NOT NULL, + email TEXT, + displayname TEXT, + UNIQUE(uri) +); + +CREATE TABLE groupmembers ( + id INTEGER PRIMARY KEY ASC NOT NULL, + principal_id INTEGER NOT NULL, + member_id INTEGER NOT NULL, + UNIQUE(principal_id, member_id) +); + +CREATE TABLE propertystorage ( + id integer primary key asc NOT NULL, + path text NOT NULL, + name text NOT NULL, + valuetype integer NOT NULL, + value string +); + + +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); +CREATE TABLE users ( + id integer primary key asc NOT NULL, + username TEXT NOT NULL, + digesta1 TEXT NOT NULL, + UNIQUE(username) +); diff --git a/tests/integration/cyrus.rs b/tests/integration/cyrus.rs new file mode 100644 index 0000000..5103423 --- /dev/null +++ b/tests/integration/cyrus.rs @@ -0,0 +1,393 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::time::Duration; + +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::{SyncBuilder, SyncRunner}; +use testcontainers::{Container, GenericBuildableImage, GenericImage, ImageExt}; + +use super::data::{MboxMessage, load_mbox}; +use super::error::{ContainerError, ContainerResult}; +use super::imap_client::{ImapSeed, full_path}; +use super::layouts::{self}; +use super::{Account, Endpoint}; + +const IMAP_PORT: u16 = 143; + +const DOCKERFILE: &str = r#"FROM debian:bookworm-20260518-slim + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + cyrus-imapd cyrus-admin cyrus-clients sasl2-bin \ + libsasl2-modules libsasl2-modules-db \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /var/lib/cyrus /var/spool/cyrus /run/cyrus && \ + chown -R cyrus:mail /var/lib/cyrus /var/spool/cyrus /run/cyrus + +COPY imapd.conf /etc/imapd.conf +COPY cyrus.conf /etc/cyrus.conf +COPY sasldb.txt /tmp/sasldb.txt +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 143 +CMD ["/entrypoint.sh"] +"#; + +const IMAPD_CONF: &str = r#"configdirectory: /var/lib/cyrus +partition-default: /var/spool/cyrus +admins: cyrus +allowanonymouslogin: no +allowplaintext: yes +sasl_mech_list: PLAIN LOGIN +sasl_pwcheck_method: auxprop +sasl_auxprop_plugin: sasldb +sasl_sasldb_path: /etc/sasldb2 +unixhierarchysep: yes +altnamespace: yes +sasl_minimum_layer: 0 +"#; + +const CYRUS_CONF: &str = r#"START { + recover cmd="ctl_cyrusdb -r" +} + +SERVICES { + imap cmd="imapd" listen="143" prefork=0 +} + +EVENTS { + checkpoint cmd="ctl_cyrusdb -c" period=30 + delprune cmd="cyr_expire -E 3" at=0400 +} +"#; + +fn sasldb_entries() -> String { + let mut out = String::new(); + for u in layouts::accounts() { + out.push_str(u); + out.push(' '); + out.push_str(layouts::PASSWORD); + out.push('\n'); + } + out.push_str("cyrus admin-secret\n"); + out +} + +const ENTRYPOINT: &str = r#"#!/bin/bash +set -e + +while IFS=' ' read -r user pw; do + [ -z "$user" ] && continue + echo "$pw" | saslpasswd2 -p -c -f /etc/sasldb2 "$user" +done < /tmp/sasldb.txt +chown cyrus:mail /etc/sasldb2 +chmod 0640 /etc/sasldb2 + +mkdir -p /var/lib/cyrus /var/spool/cyrus /run/cyrus +chown -R cyrus:mail /var/lib/cyrus /var/spool/cyrus /run/cyrus +su cyrus -s /bin/bash -c '/usr/sbin/cyrus makedirs' || true + +/usr/sbin/cyrmaster -C /etc/imapd.conf -M /etc/cyrus.conf & +master_pid=$! + +for _ in $(seq 1 120); do + if (exec 3<>/dev/tcp/127.0.0.1/143) 2>/dev/null; then + exec 3<&- 3>&- + break + fi + sleep 0.5 +done + +provision() { + exec 3<>/dev/tcp/127.0.0.1/143 + read -r _greeting <&3 + printf 'a1 LOGIN cyrus admin-secret\r\n' >&3 + read -r _ <&3 + local tag=1 + for u in "$@"; do + tag=$((tag + 1)) + printf 'a%d CREATE user/%s\r\n' "$tag" "$u" >&3 + read -r _ <&3 + done + tag=$((tag + 1)) + printf 'a%d LOGOUT\r\n' "$tag" >&3 + read -r _ <&3 + exec 3<&- 3>&- +} + +users=() +while IFS=' ' read -r user _pw; do + [ -z "$user" ] && continue + [ "$user" = "cyrus" ] && continue + users+=("$user") +done < /tmp/sasldb.txt +provision "${users[@]}" + +echo "cyrus provisioned" +wait "$master_pid" +"#; + +pub struct Cyrus { + container: Container, + pub imap: Endpoint, + pub accounts: Vec, +} + +impl Cyrus { + pub fn start() -> ContainerResult { + let image: GenericImage = GenericBuildableImage::new("vandelay-cyrus", "test") + .with_dockerfile_string(DOCKERFILE.to_owned()) + .with_data(IMAPD_CONF.as_bytes().to_vec(), "imapd.conf") + .with_data(CYRUS_CONF.as_bytes().to_vec(), "cyrus.conf") + .with_data(sasldb_entries().into_bytes(), "sasldb.txt") + .with_data(ENTRYPOINT.as_bytes().to_vec(), "entrypoint.sh") + .build_image() + .map_err(|e| ContainerError::Seed(format!("cyrus build: {e}")))?; + + let request = image + .with_exposed_port(IMAP_PORT.tcp()) + .with_wait_for(WaitFor::message_on_stdout("cyrus provisioned")) + .with_startup_timeout(Duration::from_secs(180)); + + let container = request.start()?; + let host = container.get_host()?.to_string(); + let imap = Endpoint::new(host, container.get_host_port_ipv4(IMAP_PORT.tcp())?); + + let accounts: Vec = layouts::accounts() + .iter() + .map(|name| Account { + username: (*name).to_owned(), + password: layouts::PASSWORD.to_owned(), + layout: layouts::layout_for(name), + }) + .collect(); + + Ok(Self { + container, + imap, + accounts, + }) + } + + pub fn seed_all(&self) -> ContainerResult> { + let limit = self + .accounts + .iter() + .map(|a| a.layout.email_count) + .max() + .unwrap_or(0) + .max(1); + let messages = load_mbox(limit)?; + let mut out = Vec::new(); + for acct in &self.accounts { + let m = self.seed_account(acct, &messages)?; + out.push(m); + } + Ok(out) + } + + fn seed_account( + &self, + account: &Account, + messages: &[MboxMessage], + ) -> ContainerResult { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + let sep = client.discover_separator().unwrap_or('/'); + + let mut paths = Vec::new(); + for spec in account.layout.mailboxes { + let path = full_path(account.layout.mailboxes, spec.key, sep).ok_or_else(|| { + ContainerError::Seed(format!("missing mailbox key: {}", spec.key)) + })?; + client.create(&path)?; + client.subscribe(&path)?; + paths.push(path); + } + let mut targets: Vec = vec!["INBOX".to_owned()]; + targets.extend(paths.iter().cloned()); + let mut histogram: HashMap = + targets.iter().map(|t| (t.clone(), 0)).collect(); + let mut appends: Vec = Vec::new(); + + let total = account.layout.email_count.min(messages.len()); + if total < account.layout.email_count { + return Err(ContainerError::Seed(format!( + "{} only has {} messages but layout requires {}", + account.username, + messages.len(), + account.layout.email_count + ))); + } + for (i, msg) in messages.iter().take(total).enumerate() { + let target = targets[i % targets.len()].clone(); + client.append_with_flags(&target, &[], &msg.raw)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: msg.raw.clone(), + target, + flags: Vec::new(), + tag: SeedTag::Bulk(i), + }); + } + + let mut dedup_target: Option = None; + if targets.len() > 1 && !messages.is_empty() { + let target = targets[1].clone(); + client.append_with_flags(&target, &[], &messages[0].raw)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: messages[0].raw.clone(), + target: target.clone(), + flags: Vec::new(), + tag: SeedTag::Dedup, + }); + dedup_target = Some(target); + } + + let mut flagged_target: Option = None; + if !messages.is_empty() { + let target = "INBOX".to_owned(); + let probe = flag_probe_message(); + client.append_with_flags(&target, &["\\Seen", "\\Flagged"], &probe)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: probe, + target: target.clone(), + flags: vec!["$seen".to_owned(), "$flagged".to_owned()], + tag: SeedTag::FlagProbe, + }); + flagged_target = Some(target); + } + + client.logout()?; + + let total_appends = histogram.values().sum(); + Ok(AccountSeed { + username: account.username.clone(), + paths, + histogram, + total_appends, + dedup_target, + flagged_target, + appends, + }) + } + + pub fn delete_first_inbox_message(&self, account: &Account) -> ContainerResult<()> { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + client.delete_and_expunge_first("INBOX")?; + client.logout()?; + Ok(()) + } + + pub fn append_new_message( + &self, + account: &Account, + mailbox: &str, + tag: &str, + ) -> ContainerResult<(Vec, String)> { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + let message_id = format!("", account.username); + let body = format!( + "From: cyrus-added-{tag}@vandelay.test\r\n\ + To: {}@vandelay.test\r\n\ + Subject: Cyrus added probe {tag}\r\n\ + Message-ID: {message_id}\r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + Cyrus added probe body {tag}.\r\n", + account.username + ); + let raw = body.into_bytes(); + client.append_with_flags(mailbox, &[], &raw)?; + client.logout()?; + Ok((raw, message_id)) + } + + pub fn verify_seed(&self, seeds: &[AccountSeed]) -> ContainerResult<()> { + for (acct, seed) in self.accounts.iter().zip(seeds) { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&acct.username, &acct.password)?; + let names = client.list_all()?; + let expected = acct.layout.mailboxes.len() + 1; + if names.len() < expected { + return Err(ContainerError::Seed(format!( + "cyrus {}: expected >= {expected} mailboxes, got {}", + acct.username, + names.len() + ))); + } + let inbox_n = client.select("INBOX")?; + if acct.layout.email_count > 0 && inbox_n == 0 { + return Err(ContainerError::Seed(format!( + "cyrus {}: INBOX EXISTS = 0 after seed", + acct.username + ))); + } + for path in &seed.paths { + let n = client.select(path)?; + let want = seed.histogram.get(path).copied().unwrap_or(0); + if n < want { + return Err(ContainerError::Seed(format!( + "cyrus {}: mailbox {path} EXISTS={n} but {want} were appended", + acct.username + ))); + } + } + client.logout()?; + } + Ok(()) + } + + pub fn stop(self) -> ContainerResult<()> { + self.container.stop()?; + Ok(()) + } +} + +pub fn flag_probe_message() -> Vec { + let body = "From: probe-flags@vandelay.test\r\n\ + To: user@vandelay.test\r\n\ + Subject: Cyrus flag probe\r\n\ + Message-ID: \r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + Cyrus flag probe body.\r\n"; + body.as_bytes().to_vec() +} + +#[derive(Debug, Clone)] +pub struct AccountSeed { + pub username: String, + pub paths: Vec, + pub histogram: HashMap, + pub total_appends: usize, + pub dedup_target: Option, + pub flagged_target: Option, + pub appends: Vec, +} + +#[derive(Debug, Clone)] +pub struct SeededAppend { + pub raw: Vec, + pub target: String, + pub flags: Vec, + pub tag: SeedTag, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SeedTag { + Bulk(usize), + Dedup, + FlagProbe, +} diff --git a/tests/integration/data.rs b/tests/integration/data.rs new file mode 100644 index 0000000..7c6b441 --- /dev/null +++ b/tests/integration/data.rs @@ -0,0 +1,475 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufReader, Read}; +use std::path::{Path, PathBuf}; + +use flate2::read::GzDecoder; +use mail_parser::mailbox::mbox::MessageIterator; + +use super::error::{ContainerError, ContainerResult}; + +fn resources_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("resources") +} + +#[derive(Debug, Clone)] +pub struct MboxMessage { + pub raw: Vec, + pub received_at: i64, +} + +pub fn load_mbox(limit: usize) -> ContainerResult> { + let path = resources_dir().join("mailbox.gz"); + if path.exists() { + let file = std::fs::File::open(&path)?; + let mut decoder = GzDecoder::new(file); + let mut bytes = Vec::new(); + decoder.read_to_end(&mut bytes)?; + let reader = BufReader::new(std::io::Cursor::new(bytes)); + let mut out = Vec::new(); + for item in MessageIterator::new(reader) { + let message = item.map_err(|e| ContainerError::Resource(format!("mbox parse: {e}")))?; + let received_at = message.internal_date() as i64; + out.push(MboxMessage { + raw: message.unwrap_contents(), + received_at, + }); + if out.len() >= limit { + break; + } + } + if !out.is_empty() { + return Ok(out); + } + } + if let Ok(takeout) = load_takeout_mbox("all_mail_including_spam_and_trash.mbox") + && !takeout.is_empty() + { + return Ok(takeout.into_iter().take(limit).collect()); + } + Ok(synth_messages(limit)) +} + +#[derive(Debug, Clone)] +pub struct RawFixture { + pub name: String, + pub bytes: Vec, +} + +pub fn load_vcards() -> ContainerResult> { + Ok(synth_vcards(48)) +} + +pub fn load_icals() -> ContainerResult> { + Ok(synth_icals(48)) +} + +pub fn load_takeout_mbox(name: &str) -> ContainerResult> { + let path = resources_dir().join("takeout").join(name); + let bytes = std::fs::read(&path) + .map_err(|e| ContainerError::Resource(format!("open {}: {e}", path.display())))?; + let reader = BufReader::new(std::io::Cursor::new(bytes)); + let mut out = Vec::new(); + for item in MessageIterator::new(reader) { + let message = item.map_err(|e| ContainerError::Resource(format!("mbox parse: {e}")))?; + let received_at = message.internal_date() as i64; + out.push(MboxMessage { + raw: message.unwrap_contents(), + received_at, + }); + } + Ok(out) +} + +fn synth_messages(n: usize) -> Vec { + let mut out = Vec::with_capacity(n); + for i in 0..n { + let body = format!( + "From: sender{i}@vandelay.test\r\n\ + To: user@vandelay.test\r\n\ + Subject: Synthetic test message {i}\r\n\ + Message-ID: \r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + MIME-Version: 1.0\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\ + \r\n\ + This is synthetic seed message #{i} for vandelay container tests.\r\n", + ); + out.push(MboxMessage { + raw: body.into_bytes(), + received_at: 0, + }); + } + out +} + +const VCARD_VARIANTS: &[fn(usize) -> String] = &[ + vcard_v3_basic, + vcard_v3_full, + vcard_v3_multi_email, + vcard_v3_org_and_title, + vcard_v4_basic, + vcard_v4_with_address, + vcard_v4_nickname, + vcard_v4_birthday, +]; + +fn synth_vcards(n: usize) -> Vec { + let mut out = Vec::with_capacity(n); + for i in 0..n { + let body = VCARD_VARIANTS[i % VCARD_VARIANTS.len()](i); + out.push(RawFixture { + name: format!("synth-{i:03}"), + bytes: body.into_bytes(), + }); + } + out +} + +fn vcard_v3_basic(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:3.0\r\n\ + UID:vandelay-card-basic-{i}\r\n\ + FN:Basic Contact {i}\r\n\ + N:Contact{i};Basic;;;\r\n\ + EMAIL;TYPE=INTERNET:basic-{i}@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v3_full(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:3.0\r\n\ + UID:vandelay-card-full-{i}\r\n\ + FN:Full Contact {i}\r\n\ + N:Contact{i};Full;Middle;;\r\n\ + EMAIL;TYPE=INTERNET;TYPE=WORK:full-{i}@vandelay.test\r\n\ + TEL;TYPE=CELL:+15550010{i:03}\r\n\ + ORG:Vandelay Test\r\n\ + TITLE:Senior Test Engineer\r\n\ + URL:https://vandelay.test/{i}\r\n\ + NOTE:Synthetic full v3 contact #{i}\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v3_multi_email(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:3.0\r\n\ + UID:vandelay-card-multi-{i}\r\n\ + FN:Multi Email {i}\r\n\ + N:Contact{i};MultiEmail;;;\r\n\ + EMAIL;TYPE=INTERNET;TYPE=HOME:multi-{i}-home@vandelay.test\r\n\ + EMAIL;TYPE=INTERNET;TYPE=WORK:multi-{i}-work@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v3_org_and_title(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:3.0\r\n\ + UID:vandelay-card-org-{i}\r\n\ + FN:Org Contact {i}\r\n\ + N:Contact{i};Org;;Dr.;PhD\r\n\ + ORG:Vandelay Industries;Engineering;Test Group\r\n\ + TITLE:Principal Researcher\r\n\ + EMAIL;TYPE=INTERNET:org-{i}@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v4_basic(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:4.0\r\n\ + UID:urn:uuid:00000000-0000-4000-a000-0000{i:08x}\r\n\ + FN:V4 Basic {i}\r\n\ + N:Contact{i};V4Basic;;;\r\n\ + EMAIL:v4-basic-{i}@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v4_with_address(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:4.0\r\n\ + UID:urn:uuid:00000000-0000-4000-b000-0000{i:08x}\r\n\ + FN:V4 Address {i}\r\n\ + N:Contact{i};V4Address;;;\r\n\ + EMAIL:v4-address-{i}@vandelay.test\r\n\ + ADR;TYPE=home:;;{i} Test Street;Springfield;OR;97477;US\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v4_nickname(i: usize) -> String { + format!( + "BEGIN:VCARD\r\n\ + VERSION:4.0\r\n\ + UID:urn:uuid:00000000-0000-4000-c000-0000{i:08x}\r\n\ + FN:V4 Nick {i}\r\n\ + N:Contact{i};V4Nick;;;\r\n\ + NICKNAME:Nicky{i}\r\n\ + EMAIL:v4-nick-{i}@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +fn vcard_v4_birthday(i: usize) -> String { + let year = 1970 + (i % 30); + let month = (i % 12) + 1; + let day = (i % 28) + 1; + format!( + "BEGIN:VCARD\r\n\ + VERSION:4.0\r\n\ + UID:urn:uuid:00000000-0000-4000-d000-0000{i:08x}\r\n\ + FN:V4 Birthday {i}\r\n\ + N:Contact{i};V4Birthday;;;\r\n\ + BDAY:{year:04}{month:02}{day:02}\r\n\ + EMAIL:v4-bday-{i}@vandelay.test\r\n\ + END:VCARD\r\n" + ) +} + +const ICAL_VARIANTS: &[fn(usize) -> String] = &[ + ical_simple_dt, + ical_all_day, + ical_multi_day_dt, + ical_with_organizer_attendees, + ical_with_categories_location, + ical_recurring_daily, + ical_recurring_weekly_until, + ical_with_alarm, +]; + +fn synth_icals(n: usize) -> Vec { + let mut out = Vec::with_capacity(n); + for i in 0..n { + let body = ICAL_VARIANTS[i % ICAL_VARIANTS.len()](i); + out.push(RawFixture { + name: format!("synth-{i:03}"), + bytes: body.into_bytes(), + }); + } + out +} + +fn ical_simple_dt(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + let hour = (i % 24) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-simple-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T{hour:02}0000Z\r\n\ + DTEND:2025{month:02}{day:02}T{hour:02}3000Z\r\n\ + SUMMARY:Simple datetime event {i}\r\n\ + DESCRIPTION:Simple synthetic event #{i}\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_all_day(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + let next_day = day + 1; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-allday-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART;VALUE=DATE:2025{month:02}{day:02}\r\n\ + DTEND;VALUE=DATE:2025{month:02}{next_day:02}\r\n\ + SUMMARY:All-day event {i}\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_multi_day_dt(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 27) + 1) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-multiday-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T090000Z\r\n\ + DTEND:2025{month:02}{day:02}T170000Z\r\n\ + SUMMARY:Workshop session {i}\r\n\ + DESCRIPTION:Multi-hour workshop with description.\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_with_organizer_attendees(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-meeting-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T140000Z\r\n\ + DTEND:2025{month:02}{day:02}T150000Z\r\n\ + SUMMARY:Sync meeting {i}\r\n\ + ORGANIZER;CN=Organiser:mailto:organiser-{i}@vandelay.test\r\n\ + ATTENDEE;CN=Alice;PARTSTAT=ACCEPTED:mailto:alice-{i}@vandelay.test\r\n\ + ATTENDEE;CN=Bob;PARTSTAT=NEEDS-ACTION:mailto:bob-{i}@vandelay.test\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_with_categories_location(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-cat-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T100000Z\r\n\ + DTEND:2025{month:02}{day:02}T110000Z\r\n\ + SUMMARY:Categorised event {i}\r\n\ + LOCATION:Test Lab\\, Room {i}\r\n\ + CATEGORIES:VANDELAY,TEST,FIXTURE\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_recurring_daily(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-daily-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T070000Z\r\n\ + DTEND:2025{month:02}{day:02}T073000Z\r\n\ + RRULE:FREQ=DAILY;COUNT=10\r\n\ + SUMMARY:Daily standup {i}\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_recurring_weekly_until(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + let until_month = (month % 12) + 1; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-weekly-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T160000Z\r\n\ + DTEND:2025{month:02}{day:02}T170000Z\r\n\ + RRULE:FREQ=WEEKLY;UNTIL=2025{until_month:02}{day:02}T160000Z;BYDAY=MO\r\n\ + SUMMARY:Weekly retro {i}\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +fn ical_with_alarm(i: usize) -> String { + let month = ((i % 12) + 1) as u32; + let day = ((i % 28) + 1) as u32; + format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//container-test//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:vandelay-alarm-{i}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:2025{month:02}{day:02}T080000Z\r\n\ + DTEND:2025{month:02}{day:02}T083000Z\r\n\ + SUMMARY:Event with alarm {i}\r\n\ + BEGIN:VALARM\r\n\ + ACTION:DISPLAY\r\n\ + DESCRIPTION:Reminder {i}\r\n\ + TRIGGER:-PT15M\r\n\ + END:VALARM\r\n\ + END:VEVENT\r\n\ + END:VCALENDAR\r\n" + ) +} + +pub fn malformed_ical(name: &str) -> RawFixture { + let body = format!( + "BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//vandelay//broken//EN\r\n\ + BEGIN:VEVENT\r\n\ + UID:broken-{name}@vandelay.test\r\n\ + DTSTAMP:20250101T120000Z\r\n\ + DTSTART:NOT-A-DATE\r\n\ + RRULE:FREQ=BOGUS;INTERVAL=oops\r\n\ + END:NOT-A-VEVENT\r\n", + ); + RawFixture { + name: format!("broken-{name}"), + bytes: body.into_bytes(), + } +} + +pub fn rewrite_uid(bytes: &[u8], suffix: &str) -> Option<(Vec, String)> { + let text = std::str::from_utf8(bytes).ok()?; + let mut out = String::with_capacity(text.len() + 32); + let mut new_uid: Option = None; + for line in text.lines() { + if let Some(rest) = line.strip_prefix("UID:") { + let combined = format!("{rest}-{suffix}"); + if new_uid.is_none() { + new_uid = Some(combined.clone()); + } + out.push_str("UID:"); + out.push_str(&combined); + } else { + out.push_str(line); + } + out.push_str("\r\n"); + } + let uid = new_uid?; + Some((out.into_bytes(), uid)) +} + +pub fn extract_uid(bytes: &[u8]) -> Option { + let text = std::str::from_utf8(bytes).ok()?; + for line in text.lines() { + let l = line.trim_end_matches('\r'); + if let Some(rest) = l.strip_prefix("UID:") { + return Some(rest.to_owned()); + } + } + None +} diff --git a/tests/integration/dav_client.rs b/tests/integration/dav_client.rs new file mode 100644 index 0000000..9ff394b --- /dev/null +++ b/tests/integration/dav_client.rs @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::time::Duration; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD as B64; +use ureq::Agent; +use ureq::http::{Method, Request}; + +use super::error::{ContainerError, ContainerResult}; + +pub struct DavSeed { + agent: Agent, + base: String, + auth: String, +} + +impl DavSeed { + pub fn new(base: impl Into, user: &str, password: &str) -> Self { + let agent = ureq::Agent::config_builder() + .http_status_as_error(false) + .allow_non_standard_methods(true) + .timeout_global(Some(Duration::from_secs(30))) + .build() + .new_agent(); + let credentials = B64.encode(format!("{user}:{password}")); + Self { + agent, + base: base.into(), + auth: format!("Basic {credentials}"), + } + } + + fn url(&self, path: &str) -> String { + if path.starts_with("http://") || path.starts_with("https://") { + path.to_owned() + } else if path.starts_with('/') { + format!("{}{path}", self.base.trim_end_matches('/')) + } else { + format!("{}/{}", self.base.trim_end_matches('/'), path) + } + } + + fn run( + &self, + method: &str, + url: &str, + depth: Option, + content_type: Option<&str>, + body: Option<&[u8]>, + ) -> ContainerResult<(u16, Vec)> { + let parsed = Method::from_bytes(method.as_bytes()) + .map_err(|e| ContainerError::Protocol(format!("bad method {method}: {e}")))?; + let mut builder = Request::builder() + .method(parsed) + .uri(url) + .header("Authorization", &self.auth); + if let Some(d) = depth { + builder = builder.header("Depth", d.to_string()); + } + if let Some(ct) = content_type { + builder = builder.header("Content-Type", ct); + } + let payload: Vec = body.map(|b| b.to_vec()).unwrap_or_default(); + let request = builder + .body(payload) + .map_err(|e| ContainerError::Protocol(format!("build request: {e}")))?; + let mut response = self.agent.run(request)?; + let status = response.status().as_u16(); + let bytes = response.body_mut().read_to_vec()?; + Ok((status, bytes)) + } + + pub fn mkcol(&self, path: &str, body: Option<&str>) -> ContainerResult { + self.mkcol_with_method("MKCOL", path, body) + } + + pub fn mkcalendar(&self, path: &str, body: Option<&str>) -> ContainerResult { + self.mkcol_with_method("MKCALENDAR", path, body) + } + + fn mkcol_with_method( + &self, + method: &str, + path: &str, + body: Option<&str>, + ) -> ContainerResult { + let url = self.url(path); + let bytes = body.map(|b| b.as_bytes()); + let (status, response_body) = self.run( + method, + &url, + None, + Some("application/xml; charset=utf-8"), + bytes, + )?; + if !is_collection_created(status) { + return Err(ContainerError::Protocol(format!( + "{method} {url} -> {status}: {}", + truncate(&response_body) + ))); + } + Ok(status) + } + + pub fn put(&self, path: &str, content_type: &str, body: &[u8]) -> ContainerResult { + let url = self.url(path); + let (status, response_body) = + self.run("PUT", &url, None, Some(content_type), Some(body))?; + if !(200..300).contains(&status) { + return Err(ContainerError::Protocol(format!( + "PUT {url} -> {status}: {}", + truncate(&response_body) + ))); + } + Ok(status) + } + + pub fn delete(&self, path: &str) -> ContainerResult { + let url = self.url(path); + let (status, response_body) = self.run("DELETE", &url, None, None, None)?; + if !(200..300).contains(&status) && status != 404 { + return Err(ContainerError::Protocol(format!( + "DELETE {url} -> {status}: {}", + truncate(&response_body) + ))); + } + Ok(status) + } + + pub fn propfind(&self, path: &str, depth: u8) -> ContainerResult { + let url = self.url(path); + let body = r#" +"#; + let (status, bytes) = self.run( + "PROPFIND", + &url, + Some(depth), + Some("application/xml; charset=utf-8"), + Some(body.as_bytes()), + )?; + if !(200..400).contains(&status) { + return Err(ContainerError::Protocol(format!( + "PROPFIND {url} -> {status}: {}", + truncate(&bytes) + ))); + } + Ok(String::from_utf8_lossy(&bytes).into_owned()) + } +} + +fn is_collection_created(status: u16) -> bool { + matches!(status, 200 | 201 | 204) +} + +fn truncate(body: &[u8]) -> String { + let s = String::from_utf8_lossy(body); + if s.len() > 2000 { + format!("{}…", &s[..2000]) + } else { + s.into_owned() + } +} diff --git a/tests/integration/dovecot.rs b/tests/integration/dovecot.rs new file mode 100644 index 0000000..170fb71 --- /dev/null +++ b/tests/integration/dovecot.rs @@ -0,0 +1,503 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashMap; +use std::time::Duration; + +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::{SyncBuilder, SyncRunner}; +use testcontainers::{Container, GenericBuildableImage, GenericImage, ImageExt}; + +use super::data::{MboxMessage, load_mbox}; +use super::error::{ContainerError, ContainerResult}; +use super::imap_client::{ImapSeed, full_path}; +use super::layouts::{self}; +use super::sieve_client::SieveSeed as SieveClient; +use super::{Account, Endpoint}; + +const IMAP_PORT: u16 = 143; +const SIEVE_PORT: u16 = 4190; + +const DOCKERFILE: &str = r#"FROM debian:bookworm-20260518-slim + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + dovecot-imapd dovecot-managesieved dovecot-pop3d dovecot-sieve \ + openssl ca-certificates && \ + rm -rf /var/lib/apt/lists/* && \ + addgroup --gid 5000 vmail && \ + adduser --system --no-create-home --uid 5000 --gid 5000 vmail && \ + mkdir -p /var/vmail && chown -R vmail:vmail /var/vmail && \ + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout /etc/dovecot/dovecot.key \ + -out /etc/dovecot/dovecot.crt \ + -subj /CN=localhost && \ + chmod 600 /etc/dovecot/dovecot.key + +COPY dovecot.conf /etc/dovecot/dovecot.conf +COPY users /etc/dovecot/users + +EXPOSE 143 4190 +CMD ["dovecot", "-F"] +"#; + +const DOVECOT_CONF: &str = r#"protocols = imap sieve +listen = * + +mail_location = maildir:/var/vmail/%u/Maildir +mail_uid = 5000 +mail_gid = 5000 +mail_privileged_group = vmail + +namespace inbox { + inbox = yes + separator = / + prefix = +} + +auth_mechanisms = plain login +disable_plaintext_auth = no + +passdb { + driver = passwd-file + args = /etc/dovecot/users +} +userdb { + driver = static + args = uid=5000 gid=5000 home=/var/vmail/%u +} + +service imap-login { + inet_listener imap { + port = 143 + } +} + +service managesieve-login { + inet_listener sieve { + port = 4190 + } +} + +protocol sieve { + managesieve_max_line_length = 1M + managesieve_max_compile_errors = 5 +} + +ssl = yes +ssl_cert = String { + let mut out = String::new(); + for u in layouts::accounts() { + out.push_str(u); + out.push_str(":{plain}"); + out.push_str(layouts::PASSWORD); + out.push_str(":5000:5000::/var/vmail/"); + out.push_str(u); + out.push_str("::\n"); + } + out +} + +pub struct Dovecot { + container: Container, + pub imap: Endpoint, + pub sieve: Endpoint, + pub accounts: Vec, +} + +impl Dovecot { + pub fn start() -> ContainerResult { + let image: GenericImage = GenericBuildableImage::new("vandelay-dovecot", "test") + .with_dockerfile_string(DOCKERFILE.to_owned()) + .with_data(DOVECOT_CONF.as_bytes().to_vec(), "dovecot.conf") + .with_data(users_file().into_bytes(), "users") + .build_image() + .map_err(|e| ContainerError::Seed(format!("dovecot build: {e}")))?; + + let request = image + .with_exposed_port(IMAP_PORT.tcp()) + .with_exposed_port(SIEVE_PORT.tcp()) + .with_wait_for(WaitFor::message_on_stderr("starting up")) + .with_startup_timeout(Duration::from_secs(120)); + + let container = request.start()?; + let host = container.get_host()?.to_string(); + let imap = Endpoint::new(host.clone(), container.get_host_port_ipv4(IMAP_PORT.tcp())?); + let sieve = Endpoint::new(host, container.get_host_port_ipv4(SIEVE_PORT.tcp())?); + + let accounts: Vec = layouts::accounts() + .iter() + .map(|name| Account { + username: (*name).to_owned(), + password: layouts::PASSWORD.to_owned(), + layout: layouts::layout_for(name), + }) + .collect(); + + Ok(Self { + container, + imap, + sieve, + accounts, + }) + } + + pub fn seed_all(&self) -> ContainerResult> { + let limit = self + .accounts + .iter() + .map(|a| a.layout.email_count) + .max() + .unwrap_or(0) + .max(1); + let messages = load_mbox(limit)?; + let mut out = Vec::new(); + for acct in &self.accounts { + let mailbox = self.seed_imap(acct, &messages)?; + let sieve = self.seed_sieve(acct)?; + out.push(AccountSeed { + username: acct.username.clone(), + mailbox, + sieve, + }); + } + Ok(out) + } + + fn seed_imap( + &self, + account: &Account, + messages: &[MboxMessage], + ) -> ContainerResult { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + let sep = client.discover_separator().unwrap_or('/'); + + let mut paths = Vec::new(); + for spec in account.layout.mailboxes { + let path = full_path(account.layout.mailboxes, spec.key, sep).ok_or_else(|| { + ContainerError::Seed(format!("missing mailbox key: {}", spec.key)) + })?; + client.create(&path)?; + client.subscribe(&path)?; + paths.push(path); + } + + let mut targets: Vec = vec!["INBOX".to_owned()]; + targets.extend(paths.iter().cloned()); + let mut histogram: HashMap = + targets.iter().map(|t| (t.clone(), 0)).collect(); + let mut appends: Vec = Vec::new(); + + let total = account.layout.email_count.min(messages.len()); + if total < account.layout.email_count { + return Err(ContainerError::Seed(format!( + "{} only has {} messages but layout requires {}", + account.username, + messages.len(), + account.layout.email_count + ))); + } + for (i, msg) in messages.iter().take(total).enumerate() { + let target = targets[i % targets.len()].clone(); + client.append_with_flags(&target, &[], &msg.raw)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: msg.raw.clone(), + target, + flags: Vec::new(), + tag: SeedTag::Bulk(i), + }); + } + + let mut dedup_target: Option = None; + if targets.len() > 1 && !messages.is_empty() { + let target = targets[1].clone(); + client.append_with_flags(&target, &[], &messages[0].raw)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: messages[0].raw.clone(), + target: target.clone(), + flags: Vec::new(), + tag: SeedTag::Dedup, + }); + dedup_target = Some(target); + } + + let mut flagged_target: Option = None; + if !messages.is_empty() { + let target = "INBOX".to_owned(); + let probe = flag_probe_message(); + client.append_with_flags(&target, &["\\Seen", "\\Flagged"], &probe)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: probe, + target: target.clone(), + flags: vec!["$seen".to_owned(), "$flagged".to_owned()], + tag: SeedTag::FlagProbe, + }); + flagged_target = Some(target); + } + + let mut nomid_target: Option = None; + if !messages.is_empty() { + let target = "INBOX".to_owned(); + let probe = no_message_id_probe(); + client.append_with_flags(&target, &[], &probe)?; + *histogram.entry(target.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: probe, + target: target.clone(), + flags: Vec::new(), + tag: SeedTag::NoMessageId, + }); + nomid_target = Some(target); + } + + let mut mid_dedup_targets: Option<(String, String)> = None; + if targets.len() >= 3 { + let body = shared_message_id_probe(); + let a = targets[1].clone(); + let b = targets[2].clone(); + client.append_with_flags(&a, &[], &body)?; + client.append_with_flags(&b, &[], &body)?; + *histogram.entry(a.clone()).or_insert(0) += 1; + *histogram.entry(b.clone()).or_insert(0) += 1; + appends.push(SeededAppend { + raw: body.clone(), + target: a.clone(), + flags: Vec::new(), + tag: SeedTag::SharedMid, + }); + appends.push(SeededAppend { + raw: body, + target: b.clone(), + flags: Vec::new(), + tag: SeedTag::SharedMid, + }); + mid_dedup_targets = Some((a, b)); + } + + client.logout()?; + + let extras = ExtraAppends { + dedup_target, + flagged_target, + nomid_target, + mid_dedup_targets, + }; + + let total_appends = histogram_total(&histogram); + Ok(MailboxSeed { + paths, + histogram, + total_appends, + extras, + appends, + }) + } + + fn seed_sieve(&self, account: &Account) -> ContainerResult { + if account.layout.sieve_scripts.is_empty() { + return Ok(SieveSeed::default()); + } + let mut client = SieveClient::connect_seed(&self.sieve.host, self.sieve.port)?; + client.authenticate(&account.username, &account.password)?; + let mut active_name: Option<&'static str> = None; + let mut names = Vec::new(); + for script in account.layout.sieve_scripts { + client.putscript(script.name, script.body)?; + names.push(script.name.to_owned()); + if script.active { + active_name = Some(script.name); + } + } + if let Some(name) = active_name { + client.setactive(name)?; + } + client.logout()?; + Ok(SieveSeed { + names, + active: active_name.map(str::to_owned), + }) + } + + pub fn delete_first_inbox_message(&self, account: &Account) -> ContainerResult<()> { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + client.delete_and_expunge_first("INBOX")?; + client.logout()?; + Ok(()) + } + + pub fn append_new_message( + &self, + account: &Account, + mailbox: &str, + tag: &str, + ) -> ContainerResult<(Vec, String)> { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&account.username, &account.password)?; + let message_id = format!("", account.username); + let body = format!( + "From: added-{tag}@vandelay.test\r\n\ + To: {}@vandelay.test\r\n\ + Subject: Added probe {tag}\r\n\ + Message-ID: {message_id}\r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + Added probe body {tag}.\r\n", + account.username + ); + let raw = body.into_bytes(); + client.append_with_flags(mailbox, &[], &raw)?; + client.logout()?; + Ok((raw, message_id)) + } + + pub fn install_broken_sieve(&self, account: &Account) -> ContainerResult { + let mut client = SieveClient::connect_seed(&self.sieve.host, self.sieve.port)?; + client.authenticate(&account.username, &account.password)?; + let name = "broken-script"; + client.putscript_raw(name, "INVALID:::not_a_sieve_program;;;")?; + client.logout()?; + Ok(name.to_owned()) + } + + pub fn verify_seed(&self, seeds: &[AccountSeed]) -> ContainerResult<()> { + for (acct, seed) in self.accounts.iter().zip(seeds) { + let mut client = ImapSeed::connect(&self.imap.host, self.imap.port)?; + client.login(&acct.username, &acct.password)?; + let names = client.list_all()?; + let expected = acct.layout.mailboxes.len() + 1; + if names.len() < expected { + return Err(ContainerError::Seed(format!( + "{}: expected >= {expected} mailboxes, got {}", + acct.username, + names.len() + ))); + } + let inbox_n = client.select("INBOX")?; + if acct.layout.email_count > 0 && inbox_n == 0 { + return Err(ContainerError::Seed(format!( + "{}: INBOX EXISTS = 0 after seed", + acct.username + ))); + } + for path in &seed.mailbox.paths { + let n = client.select(path)?; + let want = seed.mailbox.histogram.get(path).copied().unwrap_or(0); + if n < want { + return Err(ContainerError::Seed(format!( + "{}: mailbox {path} EXISTS={n} but {want} were appended", + acct.username + ))); + } + } + client.logout()?; + } + Ok(()) + } + + pub fn stop(self) -> ContainerResult<()> { + self.container.stop()?; + Ok(()) + } +} + +fn histogram_total(h: &HashMap) -> usize { + h.values().sum() +} + +pub fn flag_probe_message() -> Vec { + let body = "From: probe-flags@vandelay.test\r\n\ + To: user@vandelay.test\r\n\ + Subject: Flag probe\r\n\ + Message-ID: \r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + Flag probe body.\r\n"; + body.as_bytes().to_vec() +} + +pub fn no_message_id_probe() -> Vec { + let body = "From: nomid@vandelay.test\r\n\ + To: user@vandelay.test\r\n\ + Subject: No Message-ID probe\r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + No Message-ID body.\r\n"; + body.as_bytes().to_vec() +} + +pub fn shared_message_id_probe() -> Vec { + let body = "From: shared@vandelay.test\r\n\ + To: user@vandelay.test\r\n\ + Subject: Shared MID probe\r\n\ + Message-ID: \r\n\ + Date: Wed, 01 Jan 2025 12:00:00 +0000\r\n\ + \r\n\ + Shared MID body.\r\n"; + body.as_bytes().to_vec() +} + +#[derive(Debug, Clone)] +pub struct MailboxSeed { + pub paths: Vec, + pub histogram: HashMap, + pub total_appends: usize, + pub extras: ExtraAppends, + pub appends: Vec, +} + +#[derive(Debug, Clone)] +pub struct SeededAppend { + pub raw: Vec, + pub target: String, + pub flags: Vec, + pub tag: SeedTag, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SeedTag { + Bulk(usize), + Dedup, + FlagProbe, + NoMessageId, + SharedMid, +} + +#[derive(Debug, Clone)] +pub struct ExtraAppends { + pub dedup_target: Option, + pub flagged_target: Option, + pub nomid_target: Option, + pub mid_dedup_targets: Option<(String, String)>, +} + +#[derive(Debug, Default, Clone)] +pub struct SieveSeed { + pub names: Vec, + pub active: Option, +} + +#[derive(Debug, Clone)] +pub struct AccountSeed { + pub username: String, + pub mailbox: MailboxSeed, + pub sieve: SieveSeed, +} diff --git a/tests/integration/error.rs b/tests/integration/error.rs new file mode 100644 index 0000000..1317541 --- /dev/null +++ b/tests/integration/error.rs @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io; + +use thiserror::Error; + +pub type ContainerResult = Result; + +#[derive(Debug, Error)] +pub enum ContainerError { + #[error("io: {0}")] + Io(#[from] io::Error), + #[error("testcontainers: {0}")] + Testcontainers(#[from] testcontainers::TestcontainersError), + #[error("ureq: {0}")] + Ureq(#[from] Box), + #[error("utf8: {0}")] + Utf8(#[from] std::string::FromUtf8Error), + #[error("env: {0}")] + Env(String), + #[error("protocol: {0}")] + Protocol(String), + #[error("seed: {0}")] + Seed(String), + #[error("resource: {0}")] + Resource(String), +} + +impl From for ContainerError { + fn from(e: ureq::Error) -> Self { + ContainerError::Ureq(Box::new(e)) + } +} diff --git a/tests/integration/imap_client.rs b/tests/integration/imap_client.rs new file mode 100644 index 0000000..a2a770a --- /dev/null +++ b/tests/integration/imap_client.rs @@ -0,0 +1,283 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::TcpStream; +use std::time::Duration; + +use super::error::{ContainerError, ContainerResult}; +use super::layouts::MailboxSpec; + +pub struct ImapSeed { + reader: BufReader, + writer: TcpStream, + tag_seq: u32, + pub separator: char, +} + +impl ImapSeed { + pub fn connect(host: &str, port: u16) -> ContainerResult { + let deadline = std::time::Instant::now() + Duration::from_secs(30); + let stream = loop { + match TcpStream::connect((host, port)) { + Ok(s) => break s, + Err(e) => { + if std::time::Instant::now() >= deadline { + return Err(e.into()); + } + std::thread::sleep(Duration::from_millis(250)); + } + } + }; + stream.set_read_timeout(Some(Duration::from_secs(30)))?; + stream.set_write_timeout(Some(Duration::from_secs(30)))?; + let writer = stream.try_clone()?; + let mut me = Self { + reader: BufReader::new(stream), + writer, + tag_seq: 0, + separator: '/', + }; + let greeting = me.read_line()?; + if !greeting.starts_with("* OK") && !greeting.starts_with("* PREAUTH") { + return Err(ContainerError::Protocol(format!( + "imap greeting unexpected: {greeting}" + ))); + } + Ok(me) + } + + fn next_tag(&mut self) -> String { + self.tag_seq += 1; + format!("a{:04}", self.tag_seq) + } + + fn read_line(&mut self) -> ContainerResult { + let mut line = String::new(); + let n = self.reader.read_line(&mut line)?; + if n == 0 { + return Err(ContainerError::Protocol("imap eof".to_owned())); + } + if line.ends_with('\n') { + line.pop(); + if line.ends_with('\r') { + line.pop(); + } + } + Ok(line) + } + + fn read_tagged(&mut self, tag: &str) -> ContainerResult> { + let mut untagged = Vec::new(); + loop { + let line = self.read_line()?; + if let Some(rest) = line.strip_prefix(&format!("{tag} ")) { + if rest.starts_with("OK") { + return Ok(untagged); + } + return Err(ContainerError::Protocol(format!( + "imap tagged failure: {line}" + ))); + } + untagged.push(line); + } + } + + fn send(&mut self, line: &str) -> ContainerResult<()> { + self.writer.write_all(line.as_bytes())?; + self.writer.write_all(b"\r\n")?; + self.writer.flush()?; + Ok(()) + } + + pub fn login(&mut self, user: &str, password: &str) -> ContainerResult<()> { + let tag = self.next_tag(); + let escaped_user = quote(user); + let escaped_pw = quote(password); + self.send(&format!("{tag} LOGIN {escaped_user} {escaped_pw}"))?; + self.read_tagged(&tag)?; + Ok(()) + } + + pub fn discover_separator(&mut self) -> ContainerResult { + let tag = self.next_tag(); + self.send(&format!(r#"{tag} LIST "" """#))?; + let untagged = self.read_tagged(&tag)?; + for line in &untagged { + if let Some(rest) = line.strip_prefix("* LIST ") + && let Some(open) = rest.find('"') + && let Some(close) = rest[open + 1..].find('"') + { + let sep = &rest[open + 1..open + 1 + close]; + if let Some(c) = sep.chars().next() { + self.separator = c; + return Ok(c); + } + } + } + Ok('/') + } + + pub fn create(&mut self, full_path: &str) -> ContainerResult<()> { + let tag = self.next_tag(); + self.send(&format!("{tag} CREATE {}", quote(full_path)))?; + match self.read_tagged(&tag) { + Ok(_) => Ok(()), + Err(ContainerError::Protocol(msg)) if msg.contains("already exists") => Ok(()), + Err(e) => Err(e), + } + } + + pub fn subscribe(&mut self, full_path: &str) -> ContainerResult<()> { + let tag = self.next_tag(); + self.send(&format!("{tag} SUBSCRIBE {}", quote(full_path)))?; + let _ = self.read_tagged(&tag); + Ok(()) + } + + pub fn append(&mut self, mailbox: &str, message: &[u8]) -> ContainerResult<()> { + self.append_with_flags(mailbox, &[], message) + } + + pub fn append_with_flags( + &mut self, + mailbox: &str, + flags: &[&str], + message: &[u8], + ) -> ContainerResult<()> { + let tag = self.next_tag(); + let flag_clause = if flags.is_empty() { + String::new() + } else { + format!(" ({})", flags.join(" ")) + }; + let cmd = format!( + "{tag} APPEND {}{flag_clause} {{{}}}\r\n", + quote(mailbox), + message.len(), + ); + self.writer.write_all(cmd.as_bytes())?; + self.writer.flush()?; + let cont = self.read_line()?; + if !cont.starts_with('+') { + return Err(ContainerError::Protocol(format!( + "append expected continuation, got: {cont}" + ))); + } + self.writer.write_all(message)?; + self.writer.write_all(b"\r\n")?; + self.writer.flush()?; + self.read_tagged(&tag)?; + Ok(()) + } + + pub fn list_all(&mut self) -> ContainerResult> { + let tag = self.next_tag(); + self.send(&format!(r#"{tag} LIST "" "*""#))?; + let untagged = self.read_tagged(&tag)?; + let mut names = Vec::new(); + for line in untagged { + if let Some(rest) = line.strip_prefix("* LIST ") + && let Some(name) = parse_list_name(rest) + { + names.push(name); + } + } + Ok(names) + } + + pub fn select(&mut self, mailbox: &str) -> ContainerResult { + let tag = self.next_tag(); + self.send(&format!("{tag} SELECT {}", quote(mailbox)))?; + let untagged = self.read_tagged(&tag)?; + for line in untagged { + if let Some(rest) = line.strip_prefix("* ") + && let Some(num) = rest.strip_suffix(" EXISTS") + && let Ok(n) = num.parse::() + { + return Ok(n); + } + } + Ok(0) + } + + pub fn delete_and_expunge_first(&mut self, mailbox: &str) -> ContainerResult<()> { + self.select(mailbox)?; + let store_tag = self.next_tag(); + self.send(&format!("{store_tag} STORE 1 +FLAGS (\\Deleted)"))?; + self.read_tagged(&store_tag)?; + let expunge_tag = self.next_tag(); + self.send(&format!("{expunge_tag} EXPUNGE"))?; + self.read_tagged(&expunge_tag)?; + Ok(()) + } + + pub fn logout(&mut self) -> ContainerResult<()> { + let tag = self.next_tag(); + self.send(&format!("{tag} LOGOUT"))?; + let mut buf = String::new(); + let _ = self.reader.read_to_string(&mut buf); + Ok(()) + } +} + +pub fn full_path(specs: &[MailboxSpec], key: &str, separator: char) -> Option { + let mut chain: Vec<&str> = Vec::new(); + let mut cur = key; + loop { + let spec = specs.iter().find(|s| s.key == cur)?; + chain.push(spec.name); + match spec.parent { + Some(p) => cur = p, + None => break, + } + } + chain.reverse(); + Some(chain.join(&separator.to_string())) +} + +fn quote(s: &str) -> String { + let mut out = String::with_capacity(s.len() + 2); + out.push('"'); + for c in s.chars() { + if c == '\\' || c == '"' { + out.push('\\'); + } + out.push(c); + } + out.push('"'); + out +} + +fn parse_list_name(line: &str) -> Option { + let mut chars = line.chars(); + let _ = chars.next()?; + let mut depth = 1; + let mut idx = 1; + for (i, c) in line[1..].char_indices() { + match c { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth == 0 { + idx = i + 2; + break; + } + } + _ => {} + } + } + let rest = line[idx..].trim_start(); + let mut parts = rest.splitn(2, ' '); + let _sep_token = parts.next()?; + let name_token = parts.next()?.trim(); + if let Some(stripped) = name_token.strip_prefix('"') { + let end = stripped.find('"')?; + Some(stripped[..end].to_owned()) + } else { + Some(name_token.to_owned()) + } +} diff --git a/tests/integration/layouts.rs b/tests/integration/layouts.rs new file mode 100644 index 0000000..688d41b --- /dev/null +++ b/tests/integration/layouts.rs @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#[derive(Debug, Clone, Copy)] +pub struct MailboxSpec { + pub key: &'static str, + pub name: &'static str, + pub parent: Option<&'static str>, +} + +#[derive(Debug, Clone, Copy)] +pub struct FileSpec { + pub key: &'static str, + pub name: &'static str, + pub parent: Option<&'static str>, + pub directory: bool, +} + +#[derive(Debug, Clone, Copy)] +pub struct SieveScript { + pub name: &'static str, + pub body: &'static str, + pub active: bool, +} + +#[derive(Debug, Clone, Copy)] +pub struct Layout { + pub mailboxes: &'static [MailboxSpec], + pub files: &'static [FileSpec], + pub calendars: &'static [&'static str], + pub address_books: &'static [&'static str], + pub sieve_scripts: &'static [SieveScript], + pub email_count: usize, + pub contact_count: usize, + pub event_count: usize, +} + +macro_rules! mailbox { + ($k:expr, $n:expr, $p:expr) => { + MailboxSpec { + key: $k, + name: $n, + parent: $p, + } + }; +} + +macro_rules! node { + ($k:expr, $n:expr, $p:expr, $d:expr) => { + FileSpec { + key: $k, + name: $n, + parent: $p, + directory: $d, + } + }; +} + +pub const ACCOUNT1: &str = "user1"; +pub const ACCOUNT2: &str = "user2"; +pub const ACCOUNT3: &str = "user3"; + +pub const PASSWORD: &str = "VandelayUser#2026"; + +pub static MAILBOXES_DEEP: &[MailboxSpec] = &[ + mailbox!("proj", "Projects", None), + mailbox!("y2024", "2024", Some("proj")), + mailbox!("q1", "Q1", Some("y2024")), + mailbox!("acme", "ClientAcme", Some("q1")), + mailbox!("acme_in", "Internal", Some("acme")), + mailbox!("q2", "Q2", Some("y2024")), + mailbox!("globex", "ClientGlobex", Some("q2")), + mailbox!("y2025", "2025", Some("proj")), + mailbox!("alpha", "Alpha", Some("y2025")), + mailbox!("beta", "Beta", Some("y2025")), + mailbox!("travel", "Travel", None), + mailbox!("asia", "Asia", Some("travel")), + mailbox!("jp", "Japan", Some("asia")), + mailbox!("tokyo", "Tokyo", Some("jp")), + mailbox!("eu", "Europe", Some("travel")), + mailbox!("it", "Italy", Some("eu")), + mailbox!("rome", "Rome", Some("it")), + mailbox!("arch", "Archive", None), + mailbox!("arch23", "2023", Some("arch")), + mailbox!("arch24", "2024", Some("arch")), +]; + +pub static MAILBOXES_MEDIUM: &[MailboxSpec] = &[ + mailbox!("work", "Work", None), + mailbox!("clients", "Clients", Some("work")), + mailbox!("acme", "AcmeCorp", Some("clients")), + mailbox!("inv", "Invoices", Some("acme")), + mailbox!("globex", "Globex", Some("clients")), + mailbox!("internal", "Internal", Some("work")), + mailbox!("pers", "Personal", None), + mailbox!("rcpt", "Receipts", Some("pers")), +]; + +pub static MAILBOXES_FLAT: &[MailboxSpec] = &[ + mailbox!("lists", "Lists", None), + mailbox!("news", "Newsletters", Some("lists")), + mailbox!("misc", "Misc", None), +]; + +pub static FILES_DEEP: &[FileSpec] = &[ + node!("docs", "Documents", None, true), + node!("rep", "Reports", Some("docs"), true), + node!("d24", "2024", Some("rep"), true), + node!("q1f", "q1-results.bin", Some("d24"), false), + node!("q2f", "q2-results.bin", Some("d24"), false), + node!("d25", "2025", Some("rep"), true), + node!("q1f25", "q1-results.bin", Some("d25"), false), + node!("spec", "Specs", Some("docs"), true), + node!("ovr", "overview.bin", Some("spec"), false), + node!("notes", "notes.bin", Some("docs"), false), + node!("photos", "Photos", None, true), + node!("vac", "Vacations", Some("photos"), true), + node!("vit", "Italy", Some("vac"), true), + node!("vrome", "rome.bin", Some("vit"), false), + node!("vjp", "Japan", Some("vac"), true), + node!("vtokyo", "tokyo.bin", Some("vjp"), false), + node!("readme", "README.bin", None, false), +]; + +pub static FILES_MEDIUM: &[FileSpec] = &[ + node!("media", "Media", None, true), + node!("audio", "Audio", Some("media"), true), + node!("song", "song.bin", Some("audio"), false), + node!("video", "Video", Some("media"), true), + node!("clip", "clip.bin", Some("video"), false), + node!("proj", "Projects", None, true), + node!("src", "src", Some("proj"), true), + node!("main", "main.bin", Some("src"), false), +]; + +pub static FILES_FLAT: &[FileSpec] = &[ + node!("bak", "Backup", None, true), + node!("snap", "snapshot.bin", Some("bak"), false), + node!("scratch", "scratch.bin", None, false), +]; + +pub static CALENDARS_DEEP: &[&str] = &["Personal", "Work", "Birthdays", "Travel"]; +pub static CALENDARS_MEDIUM: &[&str] = &["Personal", "Work"]; +pub static CALENDARS_FLAT: &[&str] = &["Personal", "Holidays"]; + +pub static ADDRESS_BOOKS_DEEP: &[&str] = &["Personal", "Work", "Friends"]; +pub static ADDRESS_BOOKS_MEDIUM: &[&str] = &["Personal", "Work"]; +pub static ADDRESS_BOOKS_FLAT: &[&str] = &["Personal", "Family"]; + +pub static SIEVE_DEEP: &[SieveScript] = &[ + SieveScript { + name: "spam", + body: "require [\"fileinto\"];\nif header :contains \"X-Spam\" \"YES\" {\n fileinto \"Junk\";\n stop;\n}\n", + active: true, + }, + SieveScript { + name: "vacation", + body: "require [\"vacation\"];\nvacation :days 7 :subject \"Out of office\" \"I am away, will reply later.\";\n", + active: false, + }, + SieveScript { + name: "organize", + body: "require [\"fileinto\",\"mailbox\"];\nif address :is \"from\" \"newsletter@example.com\" {\n fileinto :create \"Projects/2025/Beta\";\n}\n", + active: false, + }, +]; + +pub static SIEVE_MEDIUM: &[SieveScript] = &[ + SieveScript { + name: "filter", + body: "require [\"fileinto\"];\nif address :is \"to\" \"work@example.com\" {\n fileinto \"Work\";\n}\n", + active: true, + }, + SieveScript { + name: "draft", + body: "require [\"reject\"];\nif address :is \"from\" \"blocked@example.com\" {\n reject \"go away\";\n}\n", + active: false, + }, +]; + +pub static SIEVE_FLAT: &[SieveScript] = &[]; + +pub fn layout_for(username: &str) -> Layout { + match username { + s if s == ACCOUNT1 => Layout { + mailboxes: MAILBOXES_DEEP, + files: FILES_DEEP, + calendars: CALENDARS_DEEP, + address_books: ADDRESS_BOOKS_DEEP, + sieve_scripts: SIEVE_DEEP, + email_count: 200, + contact_count: 40, + event_count: 40, + }, + s if s == ACCOUNT2 => Layout { + mailboxes: MAILBOXES_MEDIUM, + files: FILES_MEDIUM, + calendars: CALENDARS_MEDIUM, + address_books: ADDRESS_BOOKS_MEDIUM, + sieve_scripts: SIEVE_MEDIUM, + email_count: 80, + contact_count: 15, + event_count: 15, + }, + _ => Layout { + mailboxes: MAILBOXES_FLAT, + files: FILES_FLAT, + calendars: CALENDARS_FLAT, + address_books: ADDRESS_BOOKS_FLAT, + sieve_scripts: SIEVE_FLAT, + email_count: 20, + contact_count: 10, + event_count: 10, + }, + } +} + +pub fn accounts() -> [&'static str; 3] { + [ACCOUNT1, ACCOUNT2, ACCOUNT3] +} diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs new file mode 100644 index 0000000..e0b04dc --- /dev/null +++ b/tests/integration/mod.rs @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#![allow(dead_code, unused_imports)] + +pub mod baikal; +pub mod cyrus; +pub mod data; +pub mod dav_client; +pub mod dovecot; +pub mod error; +pub mod imap_client; +pub mod layouts; +pub mod radicale; +pub mod sieve_client; +pub mod stalwart; +pub mod validate; +pub mod webdav; + +pub use error::{ContainerError, ContainerResult}; + +#[derive(Debug, Clone)] +pub struct Endpoint { + pub host: String, + pub port: u16, +} + +impl Endpoint { + pub fn new(host: impl Into, port: u16) -> Self { + Self { + host: host.into(), + port, + } + } + + pub fn http_base(&self) -> String { + format!("http://{}:{}", self.host, self.port) + } +} + +#[derive(Debug, Clone)] +pub struct Account { + pub username: String, + pub password: String, + pub layout: layouts::Layout, +} diff --git a/tests/integration/radicale.rs b/tests/integration/radicale.rs new file mode 100644 index 0000000..f35e630 --- /dev/null +++ b/tests/integration/radicale.rs @@ -0,0 +1,385 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; +use std::time::Duration; + +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::SyncRunner; +use testcontainers::{Container, GenericImage, ImageExt}; + +use super::data::{RawFixture, load_icals, load_vcards, malformed_ical, rewrite_uid}; +use super::dav_client::DavSeed; +use super::error::{ContainerError, ContainerResult}; +use super::layouts::{self, Layout}; +use super::{Account, Endpoint}; + +const IMAGE_NAME: &str = "tomsquest/docker-radicale"; +const IMAGE_TAG: &str = "3.7.3.0"; +const RADICALE_PORT: u16 = 5232; + +const CONFIG: &str = "[server] +hosts = 0.0.0.0:5232 + +[auth] +type = htpasswd +htpasswd_filename = /config/users +htpasswd_encryption = plain + +[storage] +filesystem_folder = /data/collections +"; + +pub struct Radicale { + container: Container, + pub endpoint: Endpoint, + pub accounts: Vec, +} + +impl Radicale { + pub fn start() -> ContainerResult { + let mut users = String::new(); + for u in layouts::accounts() { + users.push_str(u); + users.push(':'); + users.push_str(layouts::PASSWORD); + users.push('\n'); + } + + let image = GenericImage::new(IMAGE_NAME, IMAGE_TAG) + .with_exposed_port(RADICALE_PORT.tcp()) + .with_wait_for(WaitFor::message_on_stderr("Radicale server ready")); + + let request = image + .with_copy_to("/config/config", CONFIG.as_bytes().to_vec()) + .with_copy_to("/config/users", users.into_bytes()) + .with_startup_timeout(Duration::from_secs(90)); + + let container = request.start()?; + let host = container.get_host()?.to_string(); + let port = container.get_host_port_ipv4(RADICALE_PORT.tcp())?; + + let accounts: Vec = layouts::accounts() + .iter() + .map(|name| Account { + username: (*name).to_owned(), + password: layouts::PASSWORD.to_owned(), + layout: layouts::layout_for(name), + }) + .collect(); + + Ok(Self { + container, + endpoint: Endpoint::new(host, port), + accounts, + }) + } + + pub fn base_url(&self) -> String { + self.endpoint.http_base() + } + + pub fn account_url(&self, account: &Account) -> String { + format!("{}/{}/", self.base_url(), account.username) + } + + pub fn seed_all(&self) -> ContainerResult> { + let icals = load_icals()?; + let vcards = load_vcards()?; + let mut out = Vec::new(); + for acct in &self.accounts { + let seed = self.seed_account(acct, &icals, &vcards)?; + out.push(seed); + } + Ok(out) + } + + pub fn seed_account( + &self, + account: &Account, + icals: &[RawFixture], + vcards: &[RawFixture], + ) -> ContainerResult { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let layout = &account.layout; + let mut seed = AccountSeed::new(account.username.clone()); + + for (idx, cal) in layout.calendars.iter().enumerate() { + let collection = collection_segment("cal", idx, cal); + let base = format!("/{}/{}/", account.username, collection); + let body = format!( + r#" + + + + {cal} + +"# + ); + client.mkcol(&base, Some(&body))?; + let mut plan = CollectionPlan::new((*cal).to_owned(), base.clone()); + + let count = events_per_calendar(layout, idx); + let offset = events_offset(layout, idx); + for (i, fixture) in icals.iter().cycle().skip(offset).take(count).enumerate() { + let path = format!("{base}{}-{}.ics", fixture.name, i); + let suffix = format!("{collection}-{i}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix).ok_or_else(|| { + ContainerError::Seed(format!("ical fixture {} has no UID", fixture.name)) + })?; + client.put(&path, "text/calendar; charset=utf-8", &bytes)?; + plan.items.push(SeededItem { + uid, + href: path, + source: bytes, + }); + } + seed.calendars.push(plan); + } + + for (idx, ab) in layout.address_books.iter().enumerate() { + let collection = collection_segment("ab", idx, ab); + let base = format!("/{}/{}/", account.username, collection); + let body = format!( + r#" + + + + {ab} + +"# + ); + client.mkcol(&base, Some(&body))?; + let mut plan = CollectionPlan::new((*ab).to_owned(), base.clone()); + + let count = contacts_per_book(layout, idx); + let offset = contacts_offset(layout, idx); + for (i, fixture) in vcards.iter().cycle().skip(offset).take(count).enumerate() { + let path = format!("{base}{}-{}.vcf", fixture.name, i); + let suffix = format!("{collection}-{i}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix).ok_or_else(|| { + ContainerError::Seed(format!("vcard fixture {} has no UID", fixture.name)) + })?; + client.put(&path, "text/vcard; charset=utf-8", &bytes)?; + plan.items.push(SeededItem { + uid, + href: path, + source: bytes, + }); + } + seed.address_books.push(plan); + } + + Ok(seed) + } + + pub fn seed_malformed_event( + &self, + account: &Account, + calendar_segment_idx: usize, + tag: &str, + ) -> ContainerResult { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let collection = collection_segment( + "cal", + calendar_segment_idx, + account.layout.calendars[calendar_segment_idx], + ); + let path = format!("/{}/{}/broken-{tag}.ics", account.username, collection); + let bad = malformed_ical(tag); + client.put(&path, "text/calendar; charset=utf-8", &bad.bytes)?; + Ok(path) + } + + pub fn delete_item(&self, account: &Account, path: &str) -> ContainerResult<()> { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + client.delete(path)?; + Ok(()) + } + + pub fn add_event( + &self, + account: &Account, + calendar_segment_idx: usize, + tag: &str, + icals: &[RawFixture], + ) -> ContainerResult<(String, String)> { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let display = account.layout.calendars[calendar_segment_idx]; + let collection = collection_segment("cal", calendar_segment_idx, display); + let fixture = icals.first().ok_or_else(|| { + ContainerError::Seed("no ical fixture available for add_event".to_owned()) + })?; + let suffix = format!("added-{tag}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix) + .ok_or_else(|| ContainerError::Seed("ical fixture has no UID".to_owned()))?; + let path = format!("/{}/{collection}/added-{tag}.ics", account.username); + client.put(&path, "text/calendar; charset=utf-8", &bytes)?; + Ok((path, uid)) + } + + pub fn add_contact( + &self, + account: &Account, + book_segment_idx: usize, + tag: &str, + vcards: &[RawFixture], + ) -> ContainerResult<(String, String)> { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let display = account.layout.address_books[book_segment_idx]; + let collection = collection_segment("ab", book_segment_idx, display); + let fixture = vcards.first().ok_or_else(|| { + ContainerError::Seed("no vcard fixture available for add_contact".to_owned()) + })?; + let suffix = format!("added-{tag}"); + let (bytes, uid) = rewrite_uid(&fixture.bytes, &suffix) + .ok_or_else(|| ContainerError::Seed("vcard fixture has no UID".to_owned()))?; + let path = format!("/{}/{collection}/added-{tag}.vcf", account.username); + client.put(&path, "text/vcard; charset=utf-8", &bytes)?; + Ok((path, uid)) + } + + pub fn verify_seed(&self, seeds: &[AccountSeed]) -> ContainerResult<()> { + for (acct, seed) in self.accounts.iter().zip(seeds) { + let client = DavSeed::new(self.base_url(), &acct.username, &acct.password); + let body = client.propfind(&format!("/{}/", acct.username), 1)?; + if !body.contains("multistatus") { + return Err(ContainerError::Protocol(format!( + "radicale propfind for {} returned no multistatus", + acct.username + ))); + } + let expected_cal_names: HashSet<&str> = seed + .calendars + .iter() + .map(|c| c.display_name.as_str()) + .collect(); + let expected_ab_names: HashSet<&str> = seed + .address_books + .iter() + .map(|c| c.display_name.as_str()) + .collect(); + if expected_cal_names.is_empty() && expected_ab_names.is_empty() { + continue; + } + } + Ok(()) + } + + pub fn stop(self) -> ContainerResult<()> { + self.container.stop()?; + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct SeededItem { + pub uid: String, + pub href: String, + pub source: Vec, +} + +#[derive(Debug, Clone)] +pub struct CollectionPlan { + pub display_name: String, + pub base_href: String, + pub items: Vec, +} + +impl CollectionPlan { + fn new(display_name: String, base_href: String) -> Self { + Self { + display_name, + base_href, + items: Vec::new(), + } + } +} + +#[derive(Debug, Clone)] +pub struct AccountSeed { + pub username: String, + pub calendars: Vec, + pub address_books: Vec, +} + +impl AccountSeed { + fn new(username: String) -> Self { + Self { + username, + calendars: Vec::new(), + address_books: Vec::new(), + } + } + + pub fn total_events(&self) -> usize { + self.calendars.iter().map(|c| c.items.len()).sum() + } + + pub fn total_contacts(&self) -> usize { + self.address_books.iter().map(|c| c.items.len()).sum() + } + + pub fn event_uids(&self) -> HashSet { + self.calendars + .iter() + .flat_map(|c| c.items.iter().map(|i| i.uid.clone())) + .collect() + } + + pub fn contact_uids(&self) -> HashSet { + self.address_books + .iter() + .flat_map(|c| c.items.iter().map(|i| i.uid.clone())) + .collect() + } +} + +fn collection_segment(prefix: &str, idx: usize, name: &str) -> String { + let mut out = format!("{prefix}-{:02}-", idx); + for c in name.chars() { + if c.is_ascii_alphanumeric() { + out.push(c.to_ascii_lowercase()); + } else if !out.ends_with('-') { + out.push('-'); + } + } + out.trim_end_matches('-').to_owned() +} + +fn events_per_calendar(layout: &Layout, idx: usize) -> usize { + if layout.calendars.is_empty() { + return 0; + } + let base = layout.event_count / layout.calendars.len().max(1); + let extra = if idx == 0 { + layout.event_count % layout.calendars.len() + } else { + 0 + }; + base + extra +} + +fn events_offset(layout: &Layout, idx: usize) -> usize { + (0..idx).map(|i| events_per_calendar(layout, i)).sum() +} + +fn contacts_per_book(layout: &Layout, idx: usize) -> usize { + if layout.address_books.is_empty() { + return 0; + } + let base = layout.contact_count / layout.address_books.len().max(1); + let extra = if idx == 0 { + layout.contact_count % layout.address_books.len() + } else { + 0 + }; + base + extra +} + +fn contacts_offset(layout: &Layout, idx: usize) -> usize { + (0..idx).map(|i| contacts_per_book(layout, i)).sum() +} diff --git a/tests/integration/sieve_client.rs b/tests/integration/sieve_client.rs new file mode 100644 index 0000000..c98a721 --- /dev/null +++ b/tests/integration/sieve_client.rs @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::TcpStream; +use std::time::Duration; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD as B64; + +use super::error::{ContainerError, ContainerResult}; + +pub struct SieveSeed { + reader: BufReader, + writer: TcpStream, +} + +impl SieveSeed { + pub fn connect_seed(host: &str, port: u16) -> ContainerResult { + Self::connect(host, port) + } + + pub fn connect(host: &str, port: u16) -> ContainerResult { + let stream = TcpStream::connect((host, port))?; + stream.set_read_timeout(Some(Duration::from_secs(30)))?; + stream.set_write_timeout(Some(Duration::from_secs(30)))?; + let writer = stream.try_clone()?; + let mut me = Self { + reader: BufReader::new(stream), + writer, + }; + me.read_until_ok()?; + Ok(me) + } + + fn read_line(&mut self) -> ContainerResult { + let mut line = String::new(); + let n = self.reader.read_line(&mut line)?; + if n == 0 { + return Err(ContainerError::Protocol("sieve eof".to_owned())); + } + if line.ends_with('\n') { + line.pop(); + if line.ends_with('\r') { + line.pop(); + } + } + Ok(line) + } + + fn read_until_ok(&mut self) -> ContainerResult> { + let mut lines = Vec::new(); + loop { + let line = self.read_line()?; + if line.starts_with("OK") { + return Ok(lines); + } + if line.starts_with("NO") || line.starts_with("BYE") { + return Err(ContainerError::Protocol(format!("sieve: {line}"))); + } + lines.push(line); + } + } + + fn write_all(&mut self, bytes: &[u8]) -> ContainerResult<()> { + self.writer.write_all(bytes)?; + self.writer.flush()?; + Ok(()) + } + + pub fn authenticate(&mut self, user: &str, password: &str) -> ContainerResult<()> { + let mut blob = Vec::new(); + blob.push(0u8); + blob.extend_from_slice(user.as_bytes()); + blob.push(0u8); + blob.extend_from_slice(password.as_bytes()); + let encoded = B64.encode(&blob); + let cmd = format!( + "AUTHENTICATE \"PLAIN\" {{{}+}}\r\n{encoded}\r\n", + encoded.len() + ); + self.write_all(cmd.as_bytes())?; + self.read_until_ok()?; + Ok(()) + } + + pub fn putscript(&mut self, name: &str, body: &str) -> ContainerResult<()> { + let cmd = format!( + "PUTSCRIPT \"{}\" {{{}+}}\r\n{}\r\n", + escape(name), + body.len(), + body + ); + self.write_all(cmd.as_bytes())?; + self.read_until_ok()?; + Ok(()) + } + + pub fn putscript_raw(&mut self, name: &str, body: &str) -> ContainerResult { + let cmd = format!( + "PUTSCRIPT \"{}\" {{{}+}}\r\n{}\r\n", + escape(name), + body.len(), + body + ); + self.write_all(cmd.as_bytes())?; + match self.read_until_ok() { + Ok(_) => Ok(true), + Err(ContainerError::Protocol(_)) => Ok(false), + Err(e) => Err(e), + } + } + + pub fn setactive(&mut self, name: &str) -> ContainerResult<()> { + self.write_all(format!("SETACTIVE \"{}\"\r\n", escape(name)).as_bytes())?; + self.read_until_ok()?; + Ok(()) + } + + pub fn listscripts(&mut self) -> ContainerResult> { + self.write_all(b"LISTSCRIPTS\r\n")?; + let lines = self.read_until_ok()?; + let mut scripts = Vec::new(); + for line in lines { + let trimmed = line.trim(); + if let Some(stripped) = trimmed.strip_prefix('"') { + let close = stripped.find('"').unwrap_or(stripped.len()); + let name = stripped[..close].to_owned(); + let rest = &stripped[close..]; + let active = rest.contains("ACTIVE"); + scripts.push((name, active)); + } + } + Ok(scripts) + } + + pub fn logout(&mut self) -> ContainerResult<()> { + let _ = self.write_all(b"LOGOUT\r\n"); + let mut buf = String::new(); + let _ = self.reader.read_to_string(&mut buf); + Ok(()) + } +} + +fn escape(s: &str) -> String { + s.replace('\\', "\\\\").replace('"', "\\\"") +} diff --git a/tests/integration/stalwart.rs b/tests/integration/stalwart.rs new file mode 100644 index 0000000..6b0a54a --- /dev/null +++ b/tests/integration/stalwart.rs @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::net::TcpListener; +use std::sync::{Arc, OnceLock}; +use std::time::{Duration, Instant}; + +use base64::Engine; +use serde_json::Value; +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::SyncRunner; +use testcontainers::{Container, GenericImage, ImageExt}; +use ureq::Agent; +use ureq::config::RedirectAuthHeaders; +use ureq::tls::{TlsConfig, TlsProvider}; + +use super::error::{ContainerError, ContainerResult}; + +const IMAGE_NAME: &str = "stalwartlabs/stalwart"; +const IMAGE_TAG: &str = "latest"; + +const HTTPS_PORT: u16 = 443; +const IMAP_PORT: u16 = 143; +const IMAPS_PORT: u16 = 993; +const SIEVE_PORT: u16 = 4190; + +const CONFIG_PATH: &str = "/etc/stalwart/config.json"; +const CONFIG_JSON: &str = r#"{"@type":"RocksDb","blobSize":16834,"bufferSize":134217728,"path":"/var/lib/stalwart","poolWorkers":null}"#; + +pub const ADMIN_USER: &str = "admin"; +pub const ADMIN_PASSWORD: &str = "admin"; + +pub struct Stalwart { + _container: Container, + pub host: String, + pub https_port: u16, + pub imap_port: u16, + pub imaps_port: u16, + pub sieve_port: u16, + pub public_url: String, +} + +impl Stalwart { + pub fn start() -> ContainerResult { + let https_port = pick_free_port()?; + let imap_port = pick_free_port()?; + let imaps_port = pick_free_port()?; + let sieve_port = pick_free_port()?; + + let host = "127.0.0.1".to_owned(); + let public_url = format!("https://{host}:{https_port}"); + + let image = GenericImage::new(IMAGE_NAME, IMAGE_TAG) + .with_exposed_port(HTTPS_PORT.tcp()) + .with_exposed_port(IMAP_PORT.tcp()) + .with_exposed_port(IMAPS_PORT.tcp()) + .with_exposed_port(SIEVE_PORT.tcp()) + .with_wait_for(WaitFor::seconds(2)); + + let request = image + .with_env_var("STALWART_PUBLIC_URL", &public_url) + .with_env_var("STALWART_RECOVERY_ADMIN", "admin:admin") + .with_copy_to(CONFIG_PATH, CONFIG_JSON.as_bytes().to_vec()) + .with_mapped_port(https_port, HTTPS_PORT.tcp()) + .with_mapped_port(imap_port, IMAP_PORT.tcp()) + .with_mapped_port(imaps_port, IMAPS_PORT.tcp()) + .with_mapped_port(sieve_port, SIEVE_PORT.tcp()) + .with_startup_timeout(Duration::from_secs(180)); + + let container = request.start()?; + + let me = Self { + _container: container, + host, + https_port, + imap_port, + imaps_port, + sieve_port, + public_url, + }; + + me.wait_ready(Duration::from_secs(120))?; + Ok(me) + } + + pub fn base_url(&self) -> &str { + &self.public_url + } + + pub fn fetch_jmap_session(&self) -> ContainerResult { + let agent = build_agent(); + let auth = basic(ADMIN_USER, ADMIN_PASSWORD); + let url = format!("{}/.well-known/jmap", self.public_url); + let mut resp = agent.get(&url).header("Authorization", &auth).call()?; + let status = resp.status().as_u16(); + let text = resp + .body_mut() + .read_to_string() + .map_err(|e| ContainerError::Protocol(format!("body read: {e}")))?; + if status != 200 { + return Err(ContainerError::Protocol(format!( + "jmap session status {status}: {text}" + ))); + } + serde_json::from_str(&text) + .map_err(|e| ContainerError::Protocol(format!("jmap session parse: {e}"))) + } + + fn wait_ready(&self, total: Duration) -> ContainerResult<()> { + let deadline = Instant::now() + total; + let mut last_err = String::from("no probe attempted"); + while Instant::now() < deadline { + match self.fetch_jmap_session() { + Ok(v) if v.get("apiUrl").is_some() => return Ok(()), + Ok(v) => last_err = format!("session missing apiUrl: {v}"), + Err(e) => last_err = e.to_string(), + } + std::thread::sleep(Duration::from_millis(500)); + } + Err(ContainerError::Protocol(format!( + "stalwart did not become ready in {total:?}: {last_err}" + ))) + } +} + +static SHARED: OnceLock = OnceLock::new(); + +pub fn shared() -> &'static Stalwart { + SHARED.get_or_init(|| Stalwart::start().expect("start shared stalwart container")) +} + +fn pick_free_port() -> ContainerResult { + let listener = TcpListener::bind("127.0.0.1:0")?; + Ok(listener.local_addr()?.port()) +} + +fn build_agent() -> Agent { + let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let tls = TlsConfig::builder() + .provider(TlsProvider::Rustls) + .unversioned_rustls_crypto_provider(provider) + .disable_verification(true) + .build(); + Agent::config_builder() + .tls_config(tls) + .http_status_as_error(false) + .max_redirects(10) + .redirect_auth_headers(RedirectAuthHeaders::SameHost) + .build() + .new_agent() +} + +fn basic(user: &str, password: &str) -> String { + let mut raw = String::with_capacity(user.len() + password.len() + 1); + raw.push_str(user); + raw.push(':'); + raw.push_str(password); + format!( + "Basic {}", + base64::engine::general_purpose::STANDARD.encode(raw.as_bytes()) + ) +} diff --git a/tests/integration/validate.rs b/tests/integration/validate.rs new file mode 100644 index 0000000..f487470 --- /dev/null +++ b/tests/integration/validate.rs @@ -0,0 +1,610 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::collections::HashSet; +use std::path::{Path, PathBuf}; + +use rusqlite::Connection; + +use vandelay::logging::Logger; +use vandelay::sync::CommonConfig; + +pub fn tmp_archive(tag: &str) -> PathBuf { + let mut p = std::env::temp_dir(); + p.push(format!( + "vandelay-container-{tag}-{}-{}.sqlite", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(), + )); + let _ = std::fs::remove_file(&p); + p +} + +pub fn common(archive: &Path) -> CommonConfig { + CommonConfig { + archive: archive.to_path_buf(), + threads: 2, + dry_run: false, + max_retries: 3, + allow_invalid_certs: true, + logger: Logger::from_flags(false, 0), + } +} + +pub fn open_archive(archive: &Path) -> Connection { + Connection::open(archive).expect("open archive") +} + +pub fn count(conn: &Connection, table: &str) -> i64 { + conn.query_row(&format!("SELECT count(*) FROM {table}"), [], |r| r.get(0)) + .unwrap_or(0) +} + +pub fn blob_bytes(conn: &Connection, blob_id: i64) -> Vec { + conn.query_row("SELECT data FROM blobs WHERE id = ?1", [blob_id], |r| { + r.get::<_, Vec>(0) + }) + .expect("blob fetch") +} + +pub fn mailbox_path(conn: &Connection, mailbox_id: i64, sep: char) -> String { + let mut parts: Vec = Vec::new(); + let mut cur = Some(mailbox_id); + while let Some(id) = cur { + let (name, parent): (String, Option) = conn + .query_row( + "SELECT name, parent_id FROM mailboxes WHERE id = ?1", + [id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .expect("mailbox path"); + parts.push(name); + cur = parent; + } + parts.reverse(); + parts.join(&sep.to_string()) +} + +pub fn file_path(conn: &Connection, node_id: i64) -> String { + let mut parts: Vec = Vec::new(); + let mut cur = Some(node_id); + while let Some(id) = cur { + let (name, parent): (String, Option) = conn + .query_row( + "SELECT name, parent_id FROM file_nodes WHERE id = ?1", + [id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .expect("file path"); + parts.push(name); + cur = parent; + } + parts.reverse(); + parts.join("/") +} + +pub fn emails_in_mailbox(conn: &Connection, mailbox_id: i64) -> i64 { + conn.query_row( + "SELECT count(*) FROM emails + WHERE EXISTS (SELECT 1 FROM json_each(emails.mailbox_ids) j WHERE j.value = ?1)", + [mailbox_id], + |r| r.get(0), + ) + .unwrap_or(0) +} + +pub fn email_rows_for_blob(conn: &Connection, blob_id: i64) -> i64 { + conn.query_row( + "SELECT count(*) FROM emails WHERE blob_id = ?1", + [blob_id], + |r| r.get(0), + ) + .unwrap_or(0) +} + +pub fn blob_id_for_hash(conn: &Connection, hash: &[u8]) -> Option { + conn.query_row("SELECT id FROM blobs WHERE hash = ?1", [hash], |r| r.get(0)) + .ok() +} + +pub fn mailbox_id_by_name(conn: &Connection, name: &str) -> Option { + conn.query_row("SELECT id FROM mailboxes WHERE name = ?1", [name], |r| { + r.get(0) + }) + .ok() +} + +pub fn mailbox_id_by_path(conn: &Connection, path: &str, sep: char) -> Option { + let mut parent: Option = None; + for segment in path.split(sep) { + let id: i64 = match parent { + Some(p) => conn + .query_row( + "SELECT id FROM mailboxes WHERE parent_id = ?1 AND name = ?2", + rusqlite::params![p, segment], + |r| r.get(0), + ) + .ok()?, + None => conn + .query_row( + "SELECT id FROM mailboxes WHERE parent_id IS NULL AND name = ?1", + [segment], + |r| r.get(0), + ) + .ok()?, + }; + parent = Some(id); + } + parent +} + +pub fn file_node_id_by_path(conn: &Connection, segments: &[&str]) -> Option { + let mut parent: Option = None; + for segment in segments { + let id: i64 = match parent { + Some(p) => conn + .query_row( + "SELECT id FROM file_nodes WHERE parent_id = ?1 AND name = ?2", + rusqlite::params![p, segment], + |r| r.get(0), + ) + .ok()?, + None => conn + .query_row( + "SELECT id FROM file_nodes WHERE parent_id IS NULL AND name = ?1", + [segment], + |r| r.get(0), + ) + .ok()?, + }; + parent = Some(id); + } + parent +} + +pub fn collection_names(conn: &Connection, table: &str) -> HashSet { + let mut stmt = conn + .prepare(&format!("SELECT name FROM {table}")) + .expect("prepare"); + stmt.query_map([], |r| r.get::<_, String>(0)) + .expect("query") + .filter_map(|r| r.ok()) + .collect() +} + +pub fn collection_id_by_name(conn: &Connection, table: &str, name: &str) -> Option { + conn.query_row( + &format!("SELECT id FROM {table} WHERE name = ?1"), + [name], + |r| r.get(0), + ) + .ok() +} + +pub fn calendar_event_count(conn: &Connection, calendar_id: i64) -> i64 { + conn.query_row( + "SELECT count(*) FROM calendar_events + WHERE EXISTS (SELECT 1 FROM json_each(calendar_events.calendar_ids) j WHERE j.value = ?1)", + [calendar_id], + |r| r.get(0), + ) + .unwrap_or(0) +} + +pub fn contact_card_count(conn: &Connection, address_book_id: i64) -> i64 { + conn.query_row( + "SELECT count(*) FROM contact_cards + WHERE EXISTS (SELECT 1 FROM json_each(contact_cards.address_book_ids) j WHERE j.value = ?1)", + [address_book_id], + |r| r.get(0), + ) + .unwrap_or(0) +} + +pub fn all_event_uids(conn: &Connection) -> HashSet { + let mut stmt = conn + .prepare( + "SELECT json_extract(data, '$.uid') FROM calendar_events + WHERE json_extract(data, '$.uid') IS NOT NULL", + ) + .expect("prepare"); + stmt.query_map([], |r| r.get::<_, String>(0)) + .expect("query") + .filter_map(|r| r.ok()) + .collect() +} + +pub fn all_contact_uids(conn: &Connection) -> HashSet { + let mut stmt = conn + .prepare("SELECT uid FROM contact_cards") + .expect("prepare"); + stmt.query_map([], |r| r.get::<_, String>(0)) + .expect("query") + .filter_map(|r| r.ok()) + .collect() +} + +pub fn keywords_for_blob(conn: &Connection, blob_id: i64) -> Vec { + let json: String = conn + .query_row( + "SELECT keywords FROM emails WHERE blob_id = ?1 LIMIT 1", + [blob_id], + |r| r.get(0), + ) + .expect("keywords fetch"); + serde_json::from_str::>(&json).expect("keywords json") +} + +pub fn event_data_by_uid(conn: &Connection, uid: &str) -> Option { + let raw: String = conn + .query_row( + "SELECT data FROM calendar_events WHERE json_extract(data,'$.uid') = ?1", + [uid], + |r| r.get(0), + ) + .ok()?; + serde_json::from_str(&raw).ok() +} + +pub fn contact_data_by_uid(conn: &Connection, uid: &str) -> Option { + let raw: String = conn + .query_row( + "SELECT data FROM contact_cards WHERE uid = ?1", + [uid], + |r| r.get(0), + ) + .ok()?; + serde_json::from_str(&raw).ok() +} + +pub fn unfold_text(text: &str) -> String { + let mut out = String::with_capacity(text.len()); + for line in text.lines() { + let l = line.trim_end_matches('\r'); + if l.starts_with(' ') || l.starts_with('\t') { + out.push_str(&l[1..]); + } else { + if !out.is_empty() { + out.push('\n'); + } + out.push_str(l); + } + } + out +} + +pub fn extract_property(text: &str, name: &str) -> Option { + component_top_level_properties(text, name) + .into_iter() + .next() +} + +pub fn extract_property_all(text: &str, name: &str) -> Vec { + component_top_level_properties(text, name) +} + +fn component_top_level_properties(text: &str, name: &str) -> Vec { + let value_colon = format!("{name}:"); + let value_semi = format!("{name};"); + let mut out = Vec::new(); + let mut depth: i32 = 0; + let mut item_depth: Option = None; + for line in unfold_text(text).lines() { + let l = line.trim_end_matches('\r'); + if l.starts_with("BEGIN:") { + depth += 1; + if matches!(l, "BEGIN:VEVENT" | "BEGIN:VTODO" | "BEGIN:VCARD") { + item_depth = Some(depth); + } + continue; + } + if l.starts_with("END:") { + if Some(depth) == item_depth { + item_depth = None; + } + depth -= 1; + continue; + } + if item_depth.is_some_and(|d| d == depth) { + if let Some(rest) = l.strip_prefix(&value_colon) { + out.push(rest.to_owned()); + } else if l.starts_with(&value_semi) + && let Some(colon) = l.find(':') + { + out.push(l[colon + 1..].to_owned()); + } + } + } + out +} + +pub fn has_line_prefix(text: &str, prefix: &str) -> bool { + let unfolded = unfold_text(text); + unfolded.lines().any(|l| l.starts_with(prefix)) +} + +pub fn json_contains_string(value: &serde_json::Value, needle: &str) -> bool { + let serialised = value.to_string(); + serialised.contains(needle) +} + +pub fn assert_message_round_trip(conn: &Connection, raw: &[u8], target_mailbox: &str, label: &str) { + let hash = blake3::hash(raw); + let blob_id = blob_id_for_hash(conn, hash.as_bytes()) + .unwrap_or_else(|| panic!("{label}: blob for seeded message missing in archive")); + let stored = blob_bytes(conn, blob_id); + assert_eq!( + stored, raw, + "{label}: stored blob bytes differ from seeded raw" + ); + let mailbox_id = mailbox_id_by_path(conn, target_mailbox, '/') + .unwrap_or_else(|| panic!("{label}: target mailbox {target_mailbox} missing in archive")); + let count: i64 = conn + .query_row( + "SELECT count(*) FROM emails + WHERE blob_id = ?1 + AND EXISTS (SELECT 1 FROM json_each(mailbox_ids) j WHERE j.value = ?2)", + rusqlite::params![blob_id, mailbox_id], + |r| r.get(0), + ) + .unwrap(); + assert!( + count >= 1, + "{label}: blob {blob_id} not linked to mailbox {target_mailbox} (id {mailbox_id})" + ); +} + +pub fn assert_event_round_trip(conn: &Connection, source: &[u8], uid: &str, label: &str) { + let text = std::str::from_utf8(source).expect("seeded ical bytes utf-8"); + let data = event_data_by_uid(conn, uid) + .unwrap_or_else(|| panic!("{label}: event uid {uid} missing in archive")); + let obj = data.as_object().expect("event data is object"); + + let stored_uid = obj.get("uid").and_then(|v| v.as_str()).unwrap_or(""); + assert_eq!( + stored_uid, uid, + "{label}: event uid mismatch in stored data" + ); + + let summary = extract_property(text, "SUMMARY").unwrap_or_default(); + if !summary.is_empty() { + let title = obj.get("title").and_then(|v| v.as_str()).unwrap_or(""); + let expected = unescape_text(&summary); + assert!( + title == expected || title.contains(&expected), + "{label}: event {uid} title mismatch: stored={title:?} expected to contain={expected:?}" + ); + } + + if extract_property(text, "DESCRIPTION").is_some() { + let descr = obj + .get("description") + .and_then(|v| v.as_str()) + .unwrap_or(""); + assert!( + !descr.is_empty(), + "{label}: event {uid} description was seeded but stored data has no description" + ); + } + + let start = obj.get("start").and_then(|v| v.as_str()).unwrap_or(""); + assert!( + !start.is_empty(), + "{label}: event {uid} stored data.start is empty" + ); + + if has_line_prefix(text, "RRULE:") || has_line_prefix(text, "RRULE;") { + let has_rule = obj + .get("recurrenceRules") + .map(|v| !v.as_array().is_some_and(|a| a.is_empty())) + .unwrap_or(false) + || obj + .get("recurrenceRule") + .map(|v| { + !v.as_object().is_some_and(|o| o.is_empty()) + && !v.as_array().is_some_and(|a| a.is_empty()) + }) + .unwrap_or(false); + assert!( + has_rule, + "{label}: event {uid} seeded RRULE but no recurrenceRule/recurrenceRules in stored data: {obj:?}" + ); + } + + if has_line_prefix(text, "BEGIN:VALARM") { + let alerts = obj + .get("alerts") + .and_then(|v| v.as_object()) + .cloned() + .unwrap_or_default(); + assert!( + !alerts.is_empty(), + "{label}: event {uid} seeded VALARM but alerts empty" + ); + } + + if has_line_prefix(text, "ORGANIZER:") + || has_line_prefix(text, "ORGANIZER;") + || has_line_prefix(text, "ATTENDEE:") + || has_line_prefix(text, "ATTENDEE;") + { + let parts = obj + .get("participants") + .and_then(|v| v.as_object()) + .cloned() + .unwrap_or_default(); + assert!( + !parts.is_empty(), + "{label}: event {uid} seeded ORGANIZER/ATTENDEE but participants empty" + ); + for addr in extract_property_all(text, "ATTENDEE") { + if let Some(idx) = addr.find("mailto:") + && let Some(end) = addr[idx + 7..].find([',', ';']) + { + let email = &addr[idx + 7..idx + 7 + end]; + assert!( + json_contains_string(&data, email), + "{label}: event {uid} attendee {email} not present in stored JSON" + ); + } else if let Some(idx) = addr.find("mailto:") { + let email = &addr[idx + 7..]; + assert!( + json_contains_string(&data, email), + "{label}: event {uid} attendee {email} not present in stored JSON" + ); + } + } + } + + if let Some(location) = extract_property(text, "LOCATION") { + let cleaned = unescape_text(&location); + let probe = cleaned.split(',').next().unwrap_or(&cleaned).trim(); + if !probe.is_empty() { + assert!( + json_contains_string(&data, probe), + "{label}: event {uid} location fragment {probe:?} not present in stored JSON" + ); + } + } + + if let Some(cats) = extract_property(text, "CATEGORIES") { + let keywords = obj + .get("keywords") + .and_then(|v| v.as_object()) + .cloned() + .unwrap_or_default(); + assert!( + !keywords.is_empty(), + "{label}: event {uid} seeded CATEGORIES={cats} but keywords empty" + ); + for cat in cats.split(',') { + let cat = cat.trim(); + if cat.is_empty() { + continue; + } + assert!( + keywords.contains_key(cat), + "{label}: event {uid} category {cat} not in keywords {keywords:?}" + ); + } + } +} + +pub fn assert_contact_round_trip(conn: &Connection, source: &[u8], uid: &str, label: &str) { + let text = std::str::from_utf8(source).expect("seeded vcard bytes utf-8"); + let data = contact_data_by_uid(conn, uid) + .unwrap_or_else(|| panic!("{label}: contact uid {uid} missing in archive")); + + if let Some(fn_value) = extract_property(text, "FN") { + let cleaned = unescape_text(&fn_value); + assert!( + json_contains_string(&data, &cleaned), + "{label}: contact {uid} FN {cleaned:?} not present in stored JSContact" + ); + } + + for email in extract_property_all(text, "EMAIL") { + assert!( + json_contains_string(&data, &email), + "{label}: contact {uid} EMAIL {email:?} not present in stored JSContact" + ); + } + + for tel in extract_property_all(text, "TEL") { + assert!( + json_contains_string(&data, &tel), + "{label}: contact {uid} TEL {tel:?} not present in stored JSContact" + ); + } + + if let Some(org) = extract_property(text, "ORG") { + let primary = org.split(';').next().unwrap_or(&org).trim(); + if !primary.is_empty() { + assert!( + json_contains_string(&data, primary), + "{label}: contact {uid} ORG {primary:?} not present in stored JSContact" + ); + } + } + + if let Some(nick) = extract_property(text, "NICKNAME") { + assert!( + json_contains_string(&data, &nick), + "{label}: contact {uid} NICKNAME {nick:?} not present" + ); + } + + if let Some(bday) = extract_property(text, "BDAY") { + let iso = format_vcard_date_as_iso(&bday); + let present_raw = json_contains_string(&data, &bday); + let present_iso = iso + .as_deref() + .map(|s| json_contains_string(&data, s)) + .unwrap_or(false); + let obj = data.as_object().expect("contact data is object"); + let has_anniversaries = obj + .get("anniversaries") + .map(|v| !v.as_object().is_some_and(|o| o.is_empty())) + .unwrap_or(false); + assert!( + present_raw || present_iso || has_anniversaries, + "{label}: contact {uid} BDAY {bday:?} not present in stored JSContact (no anniversaries either)" + ); + } +} + +fn format_vcard_date_as_iso(bday: &str) -> Option { + let trimmed = bday.trim(); + if trimmed.len() == 8 && trimmed.bytes().all(|b| b.is_ascii_digit()) { + Some(format!( + "{}-{}-{}", + &trimmed[..4], + &trimmed[4..6], + &trimmed[6..8] + )) + } else { + None + } +} + +fn unescape_text(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + let mut chars = s.chars().peekable(); + while let Some(c) = chars.next() { + if c == '\\' + && let Some(&next) = chars.peek() + { + match next { + ',' | ';' | '\\' => { + out.push(next); + chars.next(); + continue; + } + 'n' | 'N' => { + out.push('\n'); + chars.next(); + continue; + } + _ => {} + } + } + out.push(c); + } + out +} + +pub fn cleanup(archive: &Path) { + let _ = std::fs::remove_file(archive); + let mut wal = archive.as_os_str().to_owned(); + wal.push("-wal"); + let _ = std::fs::remove_file(Path::new(&wal)); + let mut shm = archive.as_os_str().to_owned(); + shm.push("-shm"); + let _ = std::fs::remove_file(Path::new(&shm)); +} diff --git a/tests/integration/webdav.rs b/tests/integration/webdav.rs new file mode 100644 index 0000000..9e52954 --- /dev/null +++ b/tests/integration/webdav.rs @@ -0,0 +1,321 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::time::Duration; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD as B64; +use testcontainers::core::{IntoContainerPort, WaitFor}; +use testcontainers::runners::{SyncBuilder, SyncRunner}; +use testcontainers::{Container, GenericBuildableImage, GenericImage, ImageExt}; + +use super::dav_client::DavSeed; +use super::error::{ContainerError, ContainerResult}; +use super::layouts::{self, FileSpec}; +use super::{Account, Endpoint}; + +const HTTP_PORT: u16 = 80; + +const DOCKERFILE: &str = r#"FROM debian:bookworm-20260518-slim + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + apache2 apache2-utils ca-certificates && \ + a2enmod dav dav_fs auth_basic authn_file && \ + rm -rf /var/lib/apt/lists/* + +COPY apache.conf /etc/apache2/sites-available/000-default.conf +COPY htpasswd /etc/apache2/htpasswd + +RUN mkdir -p /var/dav && \ + chown -R www-data:www-data /var/dav + +EXPOSE 80 +CMD ["apachectl", "-D", "FOREGROUND"] +"#; + +const APACHE_CONF: &str = r#"DavLockDB /tmp/DavLock + + + DocumentRoot /var/dav + ErrorLog /dev/stderr + CustomLog /dev/stdout combined + + + Dav On + AuthType Basic + AuthName "WebDAV" + AuthUserFile /etc/apache2/htpasswd + Require valid-user + Options Indexes FollowSymLinks + + +"#; + +fn htpasswd_for_test() -> String { + let mut out = String::new(); + for u in layouts::accounts() { + out.push_str(u); + out.push(':'); + out.push_str("{SHA}"); + out.push_str(&sha1_b64(layouts::PASSWORD.as_bytes())); + out.push('\n'); + } + out +} + +fn sha1_b64(bytes: &[u8]) -> String { + let digest = simple_sha1(bytes); + B64.encode(digest) +} + +fn simple_sha1(bytes: &[u8]) -> [u8; 20] { + let mut h0: u32 = 0x6745_2301; + let mut h1: u32 = 0xEFCD_AB89; + let mut h2: u32 = 0x98BA_DCFE; + let mut h3: u32 = 0x1032_5476; + let mut h4: u32 = 0xC3D2_E1F0; + + let bit_len = (bytes.len() as u64).wrapping_mul(8); + let mut msg = bytes.to_vec(); + msg.push(0x80); + while msg.len() % 64 != 56 { + msg.push(0); + } + msg.extend_from_slice(&bit_len.to_be_bytes()); + + for chunk in msg.chunks(64) { + let mut w = [0u32; 80]; + for (i, word) in w.iter_mut().take(16).enumerate() { + *word = u32::from_be_bytes(chunk[i * 4..i * 4 + 4].try_into().unwrap()); + } + for i in 16..80 { + w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1); + } + let (mut a, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4); + for (i, &wi) in w.iter().enumerate() { + let (f, k): (u32, u32) = match i { + 0..=19 => ((b & c) | ((!b) & d), 0x5A82_7999), + 20..=39 => (b ^ c ^ d, 0x6ED9_EBA1), + 40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1B_BCDC), + _ => (b ^ c ^ d, 0xCA62_C1D6), + }; + let temp = a + .rotate_left(5) + .wrapping_add(f) + .wrapping_add(e) + .wrapping_add(k) + .wrapping_add(wi); + e = d; + d = c; + c = b.rotate_left(30); + b = a; + a = temp; + } + h0 = h0.wrapping_add(a); + h1 = h1.wrapping_add(b); + h2 = h2.wrapping_add(c); + h3 = h3.wrapping_add(d); + h4 = h4.wrapping_add(e); + } + let mut out = [0u8; 20]; + out[..4].copy_from_slice(&h0.to_be_bytes()); + out[4..8].copy_from_slice(&h1.to_be_bytes()); + out[8..12].copy_from_slice(&h2.to_be_bytes()); + out[12..16].copy_from_slice(&h3.to_be_bytes()); + out[16..20].copy_from_slice(&h4.to_be_bytes()); + out +} + +pub struct WebDav { + container: Container, + pub http: Endpoint, + pub accounts: Vec, +} + +impl WebDav { + pub fn start() -> ContainerResult { + let image: GenericImage = GenericBuildableImage::new("vandelay-webdav", "test") + .with_dockerfile_string(DOCKERFILE.to_owned()) + .with_data(APACHE_CONF.as_bytes().to_vec(), "apache.conf") + .with_data(htpasswd_for_test().into_bytes(), "htpasswd") + .build_image() + .map_err(|e| ContainerError::Seed(format!("webdav build: {e}")))?; + + let request = image + .with_exposed_port(HTTP_PORT.tcp()) + .with_wait_for(WaitFor::message_on_stderr("AH00558")) + .with_startup_timeout(Duration::from_secs(120)); + + let container = request.start()?; + let host = container.get_host()?.to_string(); + let http = Endpoint::new(host, container.get_host_port_ipv4(HTTP_PORT.tcp())?); + + let accounts: Vec = layouts::accounts() + .iter() + .map(|name| Account { + username: (*name).to_owned(), + password: layouts::PASSWORD.to_owned(), + layout: layouts::layout_for(name), + }) + .collect(); + + Ok(Self { + container, + http, + accounts, + }) + } + + pub fn base_url(&self) -> String { + self.http.http_base() + } + + pub fn account_url(&self, account: &Account) -> String { + format!("{}/{}/", self.base_url(), account.username) + } + + pub fn seed_all(&self) -> ContainerResult> { + let mut out = Vec::new(); + for acct in &self.accounts { + let m = self.seed_account(acct)?; + out.push(m); + } + Ok(out) + } + + pub fn seed_account(&self, account: &Account) -> ContainerResult { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let root = format!("/{}/", account.username); + client.mkcol(&root, None)?; + let mut seed = AccountSeed::new(account.username.clone()); + + let specs = account.layout.files; + for spec in specs { + let path = build_path(&root, specs, spec.key) + .ok_or_else(|| ContainerError::Seed(format!("missing file key: {}", spec.key)))?; + if spec.directory { + client.mkcol(&path, None)?; + seed.directories += 1; + } else { + let payload = synth_payload(spec.name); + client.put(&path, "application/octet-stream", payload.as_bytes())?; + seed.files.push(SeededFile { + key: spec.key.to_owned(), + name: spec.name.to_owned(), + href: path, + payload: payload.into_bytes(), + }); + } + } + Ok(seed) + } + + pub fn delete_resource(&self, account: &Account, href: &str) -> ContainerResult<()> { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + client.delete(href)?; + Ok(()) + } + + pub fn add_file( + &self, + account: &Account, + parent_segments: &[&str], + name: &str, + payload: &[u8], + ) -> ContainerResult { + let client = DavSeed::new(self.base_url(), &account.username, &account.password); + let mut href = format!("/{}/", account.username); + for seg in parent_segments { + href.push_str(seg); + href.push('/'); + } + href.push_str(name); + client.put(&href, "application/octet-stream", payload)?; + Ok(href) + } + + pub fn verify_seed(&self) -> ContainerResult<()> { + for acct in &self.accounts { + let client = DavSeed::new(self.base_url(), &acct.username, &acct.password); + let body = client.propfind(&format!("/{}/", acct.username), 1)?; + if !body.contains("multistatus") { + return Err(ContainerError::Protocol(format!( + "webdav propfind for {} returned no multistatus", + acct.username + ))); + } + } + Ok(()) + } + + pub fn stop(self) -> ContainerResult<()> { + self.container.stop()?; + Ok(()) + } +} + +fn build_path(root: &str, specs: &[FileSpec], key: &str) -> Option { + let mut chain: Vec<&str> = Vec::new(); + let mut cur = key; + let mut leaf_dir = false; + loop { + let spec = specs.iter().find(|s| s.key == cur)?; + chain.push(spec.name); + if chain.len() == 1 { + leaf_dir = spec.directory; + } + match spec.parent { + Some(p) => cur = p, + None => break, + } + } + chain.reverse(); + let mut path = String::from(root); + for (i, name) in chain.iter().enumerate() { + path.push_str(name); + let is_last = i + 1 == chain.len(); + if !is_last || leaf_dir { + path.push('/'); + } + } + Some(path) +} + +pub fn synth_payload(name: &str) -> String { + let mut out = String::new(); + out.push_str(name); + out.push('\n'); + for i in 0..16 { + out.push_str(&format!("line {i} for {name}\n")); + } + out +} + +#[derive(Debug, Clone)] +pub struct SeededFile { + pub key: String, + pub name: String, + pub href: String, + pub payload: Vec, +} + +#[derive(Debug, Clone)] +pub struct AccountSeed { + pub username: String, + pub directories: usize, + pub files: Vec, +} + +impl AccountSeed { + fn new(username: String) -> Self { + Self { + username, + directories: 0, + files: Vec::new(), + } + } +} diff --git a/tests/integration_baikal.rs b/tests/integration_baikal.rs new file mode 100644 index 0000000..042a4eb --- /dev/null +++ b/tests/integration_baikal.rs @@ -0,0 +1,489 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; + +use std::collections::HashSet; + +use integration::Account; +use integration::baikal::{AccountSeed, Baikal}; +use integration::validate::{ + all_contact_uids, all_event_uids, assert_contact_round_trip, assert_event_round_trip, + calendar_event_count, cleanup, collection_id_by_name, collection_names, common, + contact_card_count, count, open_archive, tmp_archive, +}; + +use rusqlite::Connection; +use serde_json::Value; +use vandelay::error::Error; +use vandelay::sync::import_dav; +use vandelay::sync::import_dav::{DavAuth, DavImportConfig, DavKindArg}; + +fn dav_config(kind: DavKindArg, account: &Account, dav_root: &str) -> DavImportConfig { + let path = match kind { + DavKindArg::Caldav => format!("{dav_root}/calendars/{}/", account.username), + DavKindArg::Carddav => format!("{dav_root}/addressbooks/{}/", account.username), + DavKindArg::Webdav => format!("{dav_root}/"), + }; + DavImportConfig { + kind, + url: path, + auth: DavAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + dav_connections: 2, + multiget_batch: 25, + allow_source_change: false, + } +} + +fn dav_config_via_discovery( + kind: DavKindArg, + account: &Account, + dav_root: &str, +) -> DavImportConfig { + DavImportConfig { + kind, + url: format!("{dav_root}/"), + auth: DavAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + dav_connections: 2, + multiget_batch: 25, + allow_source_change: false, + } +} + +#[test] +#[ignore = "requires Docker"] +fn baikal_starts_seeds_and_imports() { + let b = Baikal::start().expect("baikal start"); + let per_account = b.seed_all().expect("baikal seed"); + assert_eq!( + per_account.len(), + b.accounts.len(), + "seed should return stats for every account" + ); + b.verify_seed(&per_account).expect("baikal verify"); + + let dav_root = b.dav_root(); + + for seed in &per_account { + let account = b + .accounts + .iter() + .find(|a| a.username == seed.username) + .expect("account lookup"); + assert!( + !seed.calendars.is_empty(), + "{}: expected calendars seeded", + seed.username + ); + assert!( + !seed.address_books.is_empty(), + "{}: expected address books seeded", + seed.username + ); + assert!( + seed.total_events() > 0, + "{}: expected events seeded", + seed.username + ); + assert!( + seed.total_contacts() > 0, + "{}: expected contacts seeded", + seed.username + ); + + let cal_archive = tmp_archive(&format!("baikal-cal-{}", seed.username)); + let summary = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &dav_root), + ) + .expect("caldav import"); + assert!( + !summary.any_failed(), + "{}: first caldav import had failures: {summary:?}", + seed.username + ); + let conn = open_archive(&cal_archive); + assert_event_collection_exact(&conn, seed); + assert_event_uids_round_trip(&conn, seed); + assert_event_data_well_formed(&conn); + for plan in &seed.calendars { + for item in &plan.items { + assert_event_round_trip( + &conn, + &item.source, + &item.uid, + &format!("{}/{}", seed.username, plan.display_name), + ); + } + } + let cal_count = count(&conn, "calendars") as usize; + let ev_count = count(&conn, "calendar_events") as usize; + let blob_count = count(&conn, "blobs") as usize; + drop(conn); + + let summary2 = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &dav_root), + ) + .expect("idempotent caldav re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent caldav re-import had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + assert_eq!( + count(&conn, "calendars") as usize, + cal_count, + "{}: idempotent re-import changed calendar count", + seed.username + ); + assert_eq!( + count(&conn, "calendar_events") as usize, + ev_count, + "{}: idempotent re-import changed event count", + seed.username + ); + assert_eq!( + count(&conn, "blobs") as usize, + blob_count, + "{}: idempotent re-import changed blob count", + seed.username + ); + drop(conn); + + let card_archive = tmp_archive(&format!("baikal-card-{}", seed.username)); + let summary = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &dav_root), + ) + .expect("carddav import"); + assert!( + !summary.any_failed(), + "{}: first carddav import had failures: {summary:?}", + seed.username + ); + let conn = open_archive(&card_archive); + assert_contact_collection_exact(&conn, seed); + assert_contact_uids_round_trip(&conn, seed); + assert_contact_data_well_formed(&conn); + for plan in &seed.address_books { + for item in &plan.items { + assert_contact_round_trip( + &conn, + &item.source, + &item.uid, + &format!("{}/{}", seed.username, plan.display_name), + ); + } + } + let book_count = count(&conn, "address_books") as usize; + let card_count = count(&conn, "contact_cards") as usize; + drop(conn); + + let summary2 = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &dav_root), + ) + .expect("idempotent carddav re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent carddav re-import had failures", + seed.username + ); + let conn = open_archive(&card_archive); + assert_eq!( + count(&conn, "address_books") as usize, + book_count, + "{}: idempotent re-import changed address book count", + seed.username + ); + assert_eq!( + count(&conn, "contact_cards") as usize, + card_count, + "{}: idempotent re-import changed contact count", + seed.username + ); + drop(conn); + + let vanish_href = seed.calendars[0] + .items + .first() + .map(|i| i.href.clone()) + .expect("at least one event seeded"); + let vanish_uid = seed.calendars[0].items.first().unwrap().uid.clone(); + b.delete_item(account, &vanish_href) + .expect("baikal delete vanished item"); + let summary_after_vanish = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &dav_root), + ) + .expect("caldav re-import after vanish"); + assert!( + !summary_after_vanish.any_failed(), + "{}: re-import after vanish had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + assert_eq!( + count(&conn, "calendar_events") as usize, + ev_count - 1, + "{}: vanished event was not pruned", + seed.username + ); + let uids_now = all_event_uids(&conn); + assert!( + !uids_now.contains(&vanish_uid), + "{}: vanished event uid {vanish_uid} still in archive", + seed.username + ); + drop(conn); + + let icals = integration::data::load_icals().expect("load icals"); + let (_added_event_href, added_event_uid) = b + .add_event(account, 0, &format!("bk-{}", seed.username), &icals) + .expect("baikal add event"); + let summary_after_add_event = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &dav_root), + ) + .expect("caldav re-import after add"); + assert!( + !summary_after_add_event.any_failed(), + "{}: re-import after add-event had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + let after_add_events = count(&conn, "calendar_events") as usize; + assert_eq!( + after_add_events, ev_count, + "{}: add-then-import should restore count to baseline", + seed.username + ); + let uids_now = all_event_uids(&conn); + assert!( + uids_now.contains(&added_event_uid), + "{}: added event uid {added_event_uid} not found in archive", + seed.username + ); + drop(conn); + + let vcards = integration::data::load_vcards().expect("load vcards"); + let (_added_card_href, added_card_uid) = b + .add_contact(account, 0, &format!("bk-{}", seed.username), &vcards) + .expect("baikal add contact"); + let summary_after_add_contact = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &dav_root), + ) + .expect("carddav re-import after add"); + assert!( + !summary_after_add_contact.any_failed(), + "{}: re-import after add-contact had failures", + seed.username + ); + let conn = open_archive(&card_archive); + let after_add_contacts = count(&conn, "contact_cards") as usize; + assert_eq!( + after_add_contacts, + card_count + 1, + "{}: add-then-import should grow contact count by 1", + seed.username + ); + let imported_contact_uids = all_contact_uids(&conn); + assert!( + imported_contact_uids.contains(&added_card_uid), + "{}: added contact uid {added_card_uid} not found", + seed.username + ); + drop(conn); + + let discovery_archive = tmp_archive(&format!("baikal-cal-discovery-{}", seed.username)); + let discovery_summary = import_dav::run( + common(&discovery_archive), + dav_config_via_discovery(DavKindArg::Caldav, account, &dav_root), + ) + .expect("caldav import via discovery from dav root"); + assert!( + !discovery_summary.any_failed(), + "{}: caldav import via discovery had failures: {discovery_summary:?}", + seed.username + ); + let conn = open_archive(&discovery_archive); + let discovered_events = count(&conn, "calendar_events") as usize; + assert_eq!( + discovered_events, ev_count, + "{}: discovery-based import event count mismatch (server holds {ev_count} after vanish+add)", + seed.username + ); + drop(conn); + cleanup(&discovery_archive); + + cleanup(&cal_archive); + cleanup(&card_archive); + } + + let primary = &b.accounts[0]; + let other = &b.accounts[1]; + let shared_archive = tmp_archive("baikal-source-change"); + import_dav::run( + common(&shared_archive), + dav_config(DavKindArg::Caldav, primary, &dav_root), + ) + .expect("seed archive with primary user"); + let err = import_dav::run( + common(&shared_archive), + dav_config(DavKindArg::Caldav, other, &dav_root), + ) + .expect_err("expected source-change abort"); + assert!( + matches!(err, Error::SourceChange(_)), + "expected SourceChange, got {err:?}" + ); + cleanup(&shared_archive); + + b.stop().expect("baikal stop"); +} + +fn assert_event_collection_exact(conn: &Connection, seed: &AccountSeed) { + let names = collection_names(conn, "calendars"); + let expected: HashSet = seed + .calendars + .iter() + .map(|c| c.display_name.clone()) + .collect(); + assert_eq!( + names, expected, + "{}: calendar displayname set mismatch", + seed.username + ); + for plan in &seed.calendars { + let cal_id = collection_id_by_name(conn, "calendars", &plan.display_name) + .unwrap_or_else(|| panic!("calendar {} missing", plan.display_name)); + let got = calendar_event_count(conn, cal_id); + assert_eq!( + got as usize, + plan.items.len(), + "{}: calendar {} event count {got} != seeded {}", + seed.username, + plan.display_name, + plan.items.len() + ); + } +} + +fn assert_contact_collection_exact(conn: &Connection, seed: &AccountSeed) { + let names = collection_names(conn, "address_books"); + let expected: HashSet = seed + .address_books + .iter() + .map(|c| c.display_name.clone()) + .collect(); + assert_eq!( + names, expected, + "{}: address book displayname set mismatch", + seed.username + ); + for plan in &seed.address_books { + let book_id = collection_id_by_name(conn, "address_books", &plan.display_name) + .unwrap_or_else(|| panic!("address book {} missing", plan.display_name)); + let got = contact_card_count(conn, book_id); + assert_eq!( + got as usize, + plan.items.len(), + "{}: book {} contact count {got} != seeded {}", + seed.username, + plan.display_name, + plan.items.len() + ); + } +} + +fn assert_event_uids_round_trip(conn: &Connection, seed: &AccountSeed) { + let imported = all_event_uids(conn); + let expected = seed.event_uids(); + let missing: Vec<_> = expected.difference(&imported).collect(); + assert!( + missing.is_empty(), + "{}: event uids missing from archive: {missing:?}", + seed.username + ); +} + +fn assert_contact_uids_round_trip(conn: &Connection, seed: &AccountSeed) { + let imported = all_contact_uids(conn); + let expected = seed.contact_uids(); + let missing: Vec<_> = expected.difference(&imported).collect(); + assert!( + missing.is_empty(), + "{}: contact uids missing from archive: {missing:?}", + seed.username + ); +} + +fn assert_event_data_well_formed(conn: &Connection) { + let mut stmt = conn + .prepare("SELECT data, data_type FROM calendar_events") + .expect("prepare"); + let rows: Vec<(String, String)> = stmt + .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))) + .expect("query") + .filter_map(|r| r.ok()) + .collect(); + assert!(!rows.is_empty(), "no calendar_events rows"); + for (raw, data_type) in rows { + assert_eq!( + data_type, "Event", + "all VEVENT seeds must land as Event, got data_type={data_type}, raw={raw}" + ); + let v: Value = serde_json::from_str(&raw) + .unwrap_or_else(|e| panic!("calendar_events.data invalid JSON: {e}; raw={raw}")); + let obj = v.as_object().expect("event data is object"); + let kind = obj + .get("@type") + .or_else(|| obj.get("type")) + .and_then(Value::as_str) + .unwrap_or(""); + assert_eq!(kind, "Event", "JSCalendar @type must be Event, got {kind}"); + assert!(obj.contains_key("uid"), "event JSON missing uid: {obj:?}"); + assert!( + obj.contains_key("start"), + "event JSON missing start: {obj:?}" + ); + } +} + +fn assert_contact_data_well_formed(conn: &Connection) { + let mut stmt = conn + .prepare("SELECT uid, data FROM contact_cards") + .expect("prepare"); + let rows: Vec<(String, String)> = stmt + .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))) + .expect("query") + .filter_map(|r| r.ok()) + .collect(); + assert!(!rows.is_empty(), "no contact_cards rows"); + for (uid, raw) in rows { + assert!(!uid.is_empty(), "contact uid empty"); + let v: Value = serde_json::from_str(&raw) + .unwrap_or_else(|e| panic!("contact_cards.data invalid JSON: {e}; raw={raw}")); + let obj = v.as_object().expect("contact data is object"); + let kind = obj + .get("@type") + .or_else(|| obj.get("type")) + .and_then(Value::as_str) + .unwrap_or(""); + assert_eq!(kind, "Card", "JSContact @type must be Card, got {kind}"); + } +} diff --git a/tests/integration_cyrus.rs b/tests/integration_cyrus.rs new file mode 100644 index 0000000..4c68542 --- /dev/null +++ b/tests/integration_cyrus.rs @@ -0,0 +1,320 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; + +use std::collections::HashSet; + +use integration::cyrus::{AccountSeed, Cyrus, flag_probe_message}; +use integration::validate::{ + assert_message_round_trip, blob_bytes, blob_id_for_hash, cleanup, common, count, + email_rows_for_blob, emails_in_mailbox, keywords_for_blob, mailbox_id_by_name, + mailbox_id_by_path, mailbox_path, open_archive, tmp_archive, +}; +use integration::{Account, Endpoint}; + +use vandelay::error::Error; +use vandelay::sync::import_imap; +use vandelay::sync::import_imap::{ImapAuth, ImapImportConfig}; + +fn imap_config(account: &Account, imap: &Endpoint) -> ImapImportConfig { + ImapImportConfig { + url: format!("imap://{}:{}", imap.host, imap.port), + auth: ImapAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + compress: false, + include: Vec::new(), + exclude: Vec::new(), + exclude_special: Vec::new(), + folder: Vec::new(), + subscribed_only: false, + automap: true, + include_deleted: false, + fetch_batch: 64, + imap_connections: 2, + allow_source_change: false, + } +} + +#[test] +#[ignore = "requires Docker"] +fn cyrus_starts_seeds_and_imports() { + let c = Cyrus::start().expect("cyrus start"); + let seeds = c.seed_all().expect("cyrus seed"); + c.verify_seed(&seeds).expect("cyrus verify"); + + for seed in &seeds { + let account = c + .accounts + .iter() + .find(|a| a.username == seed.username) + .expect("account"); + assert!( + !seed.paths.is_empty(), + "{}: expected mailboxes seeded", + seed.username + ); + assert!( + seed.total_appends > 0, + "{}: expected emails appended", + seed.username + ); + + let archive = tmp_archive(&format!("cyrus-{}", seed.username)); + let summary = + import_imap::run(common(&archive), imap_config(account, &c.imap)).expect("imap import"); + assert!( + !summary.any_failed(), + "cyrus import failed for {}: {summary:?}", + seed.username + ); + + let conn = open_archive(&archive); + let mailbox_count = count(&conn, "mailboxes") as usize; + let email_count = count(&conn, "emails") as usize; + let blob_count = count(&conn, "blobs") as usize; + let expected_mailboxes = account.layout.mailboxes.len() + 1; + assert_eq!( + mailbox_count, expected_mailboxes, + "{}: imported {mailbox_count} mailboxes, expected exactly {expected_mailboxes}", + seed.username + ); + assert_eq!( + email_count, seed.total_appends, + "{}: every seeded email must land in archive (appended {}, imported {email_count})", + seed.username, seed.total_appends + ); + assert!(blob_count > 0, "{}: blobs interned", seed.username); + + let bad_match: i64 = conn + .query_row( + "SELECT count(*) FROM emails WHERE message_match IS NULL OR message_match = ''", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + bad_match, 0, + "{}: every imported email must have a non-empty message_match", + seed.username + ); + + if let Some(target) = &seed.flagged_target { + let probe = flag_probe_message(); + let probe_hash = blake3::hash(&probe); + let blob_id = blob_id_for_hash(&conn, probe_hash.as_bytes()).expect("flag probe blob"); + let stored = blob_bytes(&conn, blob_id); + assert_eq!( + stored, probe, + "{}: flag probe blob bytes mismatch", + seed.username + ); + let keywords = keywords_for_blob(&conn, blob_id); + let kw_set: HashSet<_> = keywords.iter().map(String::as_str).collect(); + assert!( + kw_set.contains("$seen"), + "{}: flag probe missing $seen in keywords {keywords:?}", + seed.username + ); + assert!( + kw_set.contains("$flagged"), + "{}: flag probe missing $flagged in keywords {keywords:?}", + seed.username + ); + let _ = target; + } + + if let Some(_target) = &seed.dedup_target + && let Some(first) = seeds + .iter() + .find(|s| s.username == seed.username) + .and_then(|_| account.layout.mailboxes.first()) + { + let _ = first; + let total_dup: i64 = conn + .query_row( + "SELECT count(*) FROM emails e + WHERE EXISTS (SELECT 1 FROM emails e2 + WHERE e2.blob_id = e.blob_id AND e2.id <> e.id)", + [], + |r| r.get(0), + ) + .unwrap(); + assert!( + total_dup >= 2, + "{}: dedup append should produce at least two rows sharing a blob", + seed.username + ); + } + + for path in &seed.paths { + let id = mailbox_id_by_path(&conn, path, '/').unwrap_or_else(|| { + panic!("{}: mailbox path {path} missing in archive", seed.username) + }); + assert_eq!(mailbox_path(&conn, id, '/'), *path); + let expected = seed.histogram.get(path).copied().unwrap_or(0); + let got = emails_in_mailbox(&conn, id) as usize; + assert_eq!( + got, expected, + "{}: mailbox {path} email count {got} != appended {expected}", + seed.username + ); + } + for append in &seed.appends { + assert_message_round_trip( + &conn, + &append.raw, + &append.target, + &format!("{}/append/{:?}", seed.username, append.tag), + ); + } + let inbox_role: Option = conn + .query_row("SELECT role FROM mailboxes WHERE name = 'INBOX'", [], |r| { + r.get(0) + }) + .unwrap(); + assert_eq!( + inbox_role, + Some("inbox".to_owned()), + "{}: INBOX role tagged", + seed.username + ); + let inbox_id = mailbox_id_by_name(&conn, "INBOX").expect("INBOX"); + let inbox_seeded = seed.histogram.get("INBOX").copied().unwrap_or(0); + assert_eq!( + emails_in_mailbox(&conn, inbox_id) as usize, + inbox_seeded, + "{}: INBOX email count mismatch", + seed.username + ); + let probe_blob = blob_id_for_hash(&conn, blake3::hash(&flag_probe_message()).as_bytes()); + if let Some(blob_id) = probe_blob { + let rows = email_rows_for_blob(&conn, blob_id); + assert!(rows >= 1, "{}: flag probe missing", seed.username); + } + + drop(conn); + + let summary2 = import_imap::run(common(&archive), imap_config(account, &c.imap)) + .expect("idempotent imap re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent re-import had failures: {summary2:?}", + seed.username + ); + let conn = open_archive(&archive); + let email_count2 = count(&conn, "emails") as usize; + let blob_count2 = count(&conn, "blobs") as usize; + assert_eq!( + email_count2, email_count, + "{}: idempotent re-import added emails", + seed.username + ); + assert_eq!( + blob_count2, blob_count, + "{}: idempotent re-import added blobs", + seed.username + ); + drop(conn); + + c.delete_first_inbox_message(account) + .expect("expunge first INBOX message"); + let summary_after = import_imap::run(common(&archive), imap_config(account, &c.imap)) + .expect("re-import after expunge"); + assert!( + !summary_after.any_failed(), + "{}: re-import after expunge had failures", + seed.username + ); + let conn = open_archive(&archive); + let email_count3 = count(&conn, "emails") as usize; + assert_eq!( + email_count3, + email_count - 1, + "{}: expunged message was not pruned", + seed.username + ); + drop(conn); + + let (added_raw, added_mid) = c + .append_new_message(account, "INBOX", "post-import") + .expect("append new INBOX message"); + let summary_after_append = + import_imap::run(common(&archive), imap_config(account, &c.imap)) + .expect("re-import after append"); + assert!( + !summary_after_append.any_failed(), + "{}: re-import after append had failures", + seed.username + ); + let conn = open_archive(&archive); + let email_count_after_append = count(&conn, "emails") as usize; + assert_eq!( + email_count_after_append, email_count, + "{}: append-after-expunge should restore email count to baseline", + seed.username + ); + let added_hash = blake3::hash(&added_raw); + let added_blob = + blob_id_for_hash(&conn, added_hash.as_bytes()).expect("added message blob present"); + let mid_normalised = added_mid + .trim_matches(|c| c == '<' || c == '>') + .to_ascii_lowercase(); + let mid_found: i64 = conn + .query_row( + "SELECT count(*) FROM emails + WHERE EXISTS (SELECT 1 FROM json_each(json_extract(message_match,'$.m')) j + WHERE j.value = ?1)", + [mid_normalised.as_str()], + |r| r.get(0), + ) + .unwrap(); + assert!( + mid_found >= 1, + "{}: added Message-ID {added_mid} not found in message_match", + seed.username + ); + let added_inbox_id = mailbox_id_by_name(&conn, "INBOX").expect("INBOX id"); + let added_in_inbox: bool = conn + .query_row( + "SELECT EXISTS (SELECT 1 FROM emails + WHERE blob_id = ?1 + AND EXISTS (SELECT 1 FROM json_each(mailbox_ids) j + WHERE j.value = ?2))", + rusqlite::params![added_blob, added_inbox_id], + |r| r.get::<_, i64>(0).map(|n| n != 0), + ) + .unwrap(); + assert!( + added_in_inbox, + "{}: added message blob not linked to INBOX", + seed.username + ); + drop(conn); + + cleanup(&archive); + } + + let primary = &c.accounts[0]; + let other = &c.accounts[1]; + let shared_archive = tmp_archive("cyrus-source-change"); + import_imap::run(common(&shared_archive), imap_config(primary, &c.imap)) + .expect("seed archive with primary user"); + let err = import_imap::run(common(&shared_archive), imap_config(other, &c.imap)) + .expect_err("expected source-change abort"); + assert!( + matches!(err, Error::SourceChange(_)), + "expected SourceChange, got {err:?}" + ); + cleanup(&shared_archive); + + c.stop().expect("cyrus stop"); +} + +fn _unused(_: &AccountSeed) {} diff --git a/tests/integration_dovecot.rs b/tests/integration_dovecot.rs new file mode 100644 index 0000000..d0ae69a --- /dev/null +++ b/tests/integration_dovecot.rs @@ -0,0 +1,516 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; + +use std::collections::HashSet; + +use integration::Account; +use integration::dovecot::{AccountSeed, Dovecot, flag_probe_message, shared_message_id_probe}; +use integration::validate::{ + assert_message_round_trip, blob_bytes, blob_id_for_hash, cleanup, common, count, + email_rows_for_blob, emails_in_mailbox, keywords_for_blob, mailbox_id_by_name, + mailbox_id_by_path, mailbox_path, open_archive, tmp_archive, +}; + +use rusqlite::Connection; +use vandelay::error::Error; +use vandelay::sync::import_imap::{ImapAuth, ImapImportConfig}; +use vandelay::sync::import_managesieve::{ManageSieveAuth, ManageSieveImportConfig}; +use vandelay::sync::{import_imap, import_managesieve}; + +fn imap_config(account: &Account, imap: &integration::Endpoint) -> ImapImportConfig { + ImapImportConfig { + url: format!("imap://{}:{}", imap.host, imap.port), + auth: ImapAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: false, + compress: false, + include: Vec::new(), + exclude: Vec::new(), + exclude_special: Vec::new(), + folder: Vec::new(), + subscribed_only: false, + automap: true, + include_deleted: false, + fetch_batch: 64, + imap_connections: 2, + allow_source_change: false, + } +} + +fn sieve_config(account: &Account, sieve: &integration::Endpoint) -> ManageSieveImportConfig { + ManageSieveImportConfig { + url: format!("sieve://{}:{}", sieve.host, sieve.port), + auth: ManageSieveAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + allow_source_change: false, + } +} + +#[test] +#[ignore = "requires Docker"] +fn dovecot_starts_seeds_and_imports() { + let d = Dovecot::start().expect("dovecot start"); + let seeds = d.seed_all().expect("dovecot seed"); + assert_eq!( + seeds.len(), + d.accounts.len(), + "seed should return stats for every account" + ); + d.verify_seed(&seeds).expect("dovecot verify"); + + for seed in &seeds { + let account = d + .accounts + .iter() + .find(|a| a.username == seed.username) + .expect("account"); + assert!( + !seed.mailbox.paths.is_empty(), + "{}: expected mailboxes seeded", + seed.username + ); + assert!( + seed.mailbox.total_appends > 0, + "{}: expected emails appended", + seed.username + ); + + let archive = tmp_archive(&format!("dovecot-{}", seed.username)); + let summary = + import_imap::run(common(&archive), imap_config(account, &d.imap)).expect("imap import"); + assert!( + !summary.any_failed(), + "imap import had failures for {}: {summary:?}", + seed.username + ); + + let conn = open_archive(&archive); + let mailbox_count = count(&conn, "mailboxes") as usize; + let email_count = count(&conn, "emails") as usize; + let blob_count = count(&conn, "blobs") as usize; + let expected_mailboxes = account.layout.mailboxes.len() + 1; + assert_eq!( + mailbox_count, expected_mailboxes, + "{}: imported {mailbox_count} mailboxes, expected exactly {expected_mailboxes}", + seed.username + ); + assert_eq!( + email_count, seed.mailbox.total_appends, + "{}: every seeded email must land in archive (appended {}, imported {email_count})", + seed.username, seed.mailbox.total_appends + ); + assert!(blob_count > 0, "{}: blobs interned", seed.username); + + let bad_match: i64 = conn + .query_row( + "SELECT count(*) FROM emails WHERE message_match IS NULL OR message_match = ''", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + bad_match, 0, + "{}: every imported email must have a non-empty message_match", + seed.username + ); + + let nomid_target = seed + .mailbox + .extras + .nomid_target + .clone() + .expect("nomid target"); + let nomid_hash = blake3::hash(&integration::dovecot::no_message_id_probe()) + .as_bytes() + .to_vec(); + let nomid_matches: Vec = { + let mut nomid_stmt = conn + .prepare( + "SELECT message_match FROM emails + WHERE blob_id = (SELECT id FROM blobs WHERE hash = ?1)", + ) + .expect("prepare nomid"); + nomid_stmt + .query_map([nomid_hash.as_slice()], |r| r.get::<_, String>(0)) + .expect("query nomid") + .filter_map(|r| r.ok()) + .collect() + }; + assert!( + !nomid_matches.is_empty(), + "{}: no-Message-ID probe missing in archive (target {nomid_target})", + seed.username + ); + for m in &nomid_matches { + let parsed: serde_json::Value = serde_json::from_str(m).expect("message_match json"); + let mids = parsed + .get("m") + .and_then(|v| v.as_array()) + .cloned() + .unwrap_or_default(); + assert!( + mids.is_empty(), + "{}: no-Message-ID probe should have empty Message-ID set, got {m}", + seed.username + ); + let fb = parsed.get("f").and_then(|v| v.as_str()).unwrap_or(""); + assert_eq!( + fb.len(), + 64, + "{}: no-Message-ID probe must carry a BLAKE3 fallback hex, got {m}", + seed.username + ); + } + + if let Some(target) = &seed.mailbox.extras.flagged_target { + let probe = flag_probe_message(); + let probe_hash = blake3::hash(&probe); + let blob_id = blob_id_for_hash(&conn, probe_hash.as_bytes()).expect("flag probe blob"); + let stored = blob_bytes(&conn, blob_id); + assert_eq!( + stored, probe, + "{}: flag probe blob bytes mismatch", + seed.username + ); + let keywords = keywords_for_blob(&conn, blob_id); + let kw_set: HashSet<_> = keywords.iter().map(String::as_str).collect(); + assert!( + kw_set.contains("$seen"), + "{}: flag probe missing $seen in keywords {keywords:?}", + seed.username + ); + assert!( + kw_set.contains("$flagged"), + "{}: flag probe missing $flagged in keywords {keywords:?}", + seed.username + ); + let _ = target; + } + + if let Some((a, b)) = &seed.mailbox.extras.mid_dedup_targets { + let body = shared_message_id_probe(); + let hash = blake3::hash(&body); + let blob_id = blob_id_for_hash(&conn, hash.as_bytes()).expect("shared-mid probe blob"); + let rows_for_blob = email_rows_for_blob(&conn, blob_id); + assert_eq!( + rows_for_blob, 2, + "{}: shared-MID probe should produce 2 email rows for the dedup blob, got {rows_for_blob}", + seed.username + ); + let matches: Vec = { + let mut stmt = conn + .prepare("SELECT message_match FROM emails WHERE blob_id = ?1") + .expect("prepare matches"); + stmt.query_map([blob_id], |r| r.get::<_, String>(0)) + .expect("query matches") + .filter_map(|r| r.ok()) + .collect() + }; + assert_eq!(matches.len(), 2, "{}: expected 2 matches", seed.username); + assert_eq!( + matches[0], matches[1], + "{}: shared-MID emails must have identical message_match, got {matches:?}", + seed.username + ); + let _ = (a, b); + } + + if let Some(target) = &seed.mailbox.extras.dedup_target { + let _ = target; + } + + for path in &seed.mailbox.paths { + let id = mailbox_id_by_path(&conn, path, '/').unwrap_or_else(|| { + panic!("{}: mailbox path {path} missing in archive", seed.username) + }); + assert_eq!(mailbox_path(&conn, id, '/'), *path); + let expected = seed.mailbox.histogram.get(path).copied().unwrap_or(0); + let got = emails_in_mailbox(&conn, id) as usize; + assert_eq!( + got, expected, + "{}: mailbox {path} email count {got} != appended {expected}", + seed.username + ); + } + for append in &seed.mailbox.appends { + assert_message_round_trip( + &conn, + &append.raw, + &append.target, + &format!("{}/append/{:?}", seed.username, append.tag), + ); + } + let inbox_role: Option = conn + .query_row("SELECT role FROM mailboxes WHERE name = 'INBOX'", [], |r| { + r.get(0) + }) + .unwrap(); + assert_eq!( + inbox_role, + Some("inbox".to_owned()), + "{}: INBOX role tagged", + seed.username + ); + let inbox_id = mailbox_id_by_name(&conn, "INBOX").expect("INBOX"); + let inbox_seeded = seed.mailbox.histogram.get("INBOX").copied().unwrap_or(0); + assert_eq!( + emails_in_mailbox(&conn, inbox_id) as usize, + inbox_seeded, + "{}: INBOX email count mismatch", + seed.username + ); + assert_received_at_rfc3339_ish(&conn); + + drop(conn); + + let summary2 = import_imap::run(common(&archive), imap_config(account, &d.imap)) + .expect("idempotent imap re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent re-import had failures: {summary2:?}", + seed.username + ); + let conn = open_archive(&archive); + let email_count2 = count(&conn, "emails") as usize; + let mailbox_count2 = count(&conn, "mailboxes") as usize; + let blob_count2 = count(&conn, "blobs") as usize; + assert_eq!( + email_count2, email_count, + "{}: idempotent re-import added emails", + seed.username + ); + assert_eq!( + mailbox_count2, mailbox_count, + "{}: idempotent re-import added mailboxes", + seed.username + ); + assert_eq!( + blob_count2, blob_count, + "{}: idempotent re-import added blobs", + seed.username + ); + drop(conn); + + d.delete_first_inbox_message(account) + .expect("expunge first INBOX message"); + let summary_after = import_imap::run(common(&archive), imap_config(account, &d.imap)) + .expect("re-import after expunge"); + assert!( + !summary_after.any_failed(), + "{}: re-import after expunge had failures", + seed.username + ); + let conn = open_archive(&archive); + let email_count3 = count(&conn, "emails") as usize; + assert_eq!( + email_count3, + email_count - 1, + "{}: expunged message was not pruned", + seed.username + ); + drop(conn); + + let (added_raw, added_mid) = d + .append_new_message(account, "INBOX", "post-import") + .expect("append new INBOX message"); + let summary_after_append = + import_imap::run(common(&archive), imap_config(account, &d.imap)) + .expect("re-import after append"); + assert!( + !summary_after_append.any_failed(), + "{}: re-import after append had failures", + seed.username + ); + let conn = open_archive(&archive); + let email_count_after_append = count(&conn, "emails") as usize; + assert_eq!( + email_count_after_append, email_count, + "{}: append-after-expunge should restore email count to baseline", + seed.username + ); + let added_hash = blake3::hash(&added_raw); + let added_blob = integration::validate::blob_id_for_hash(&conn, added_hash.as_bytes()) + .expect("added message blob present"); + let stored = integration::validate::blob_bytes(&conn, added_blob); + assert_eq!( + stored, added_raw, + "{}: added message blob bytes mismatch", + seed.username + ); + let mid_normalised = added_mid + .trim_matches(|c| c == '<' || c == '>') + .to_ascii_lowercase(); + let mid_found: i64 = conn + .query_row( + "SELECT count(*) FROM emails + WHERE EXISTS (SELECT 1 FROM json_each(json_extract(message_match,'$.m')) j + WHERE j.value = ?1)", + [mid_normalised.as_str()], + |r| r.get(0), + ) + .unwrap(); + assert!( + mid_found >= 1, + "{}: added Message-ID {added_mid} not found in message_match", + seed.username + ); + let added_inbox_id = mailbox_id_by_name(&conn, "INBOX").expect("INBOX id"); + let added_in_inbox: bool = conn + .query_row( + "SELECT EXISTS (SELECT 1 FROM emails + WHERE blob_id = ?1 + AND EXISTS (SELECT 1 FROM json_each(mailbox_ids) j + WHERE j.value = ?2))", + rusqlite::params![added_blob, added_inbox_id], + |r| r.get::<_, i64>(0).map(|n| n != 0), + ) + .unwrap(); + assert!( + added_in_inbox, + "{}: added message blob not linked to INBOX", + seed.username + ); + drop(conn); + + if !account.layout.sieve_scripts.is_empty() { + let sieve_archive = tmp_archive(&format!("dovecot-sieve-{}", seed.username)); + let summary = + import_managesieve::run(common(&sieve_archive), sieve_config(account, &d.sieve)) + .expect("sieve import"); + assert!( + !summary.any_failed(), + "sieve import had failures for {}: {summary:?}", + seed.username + ); + let conn = open_archive(&sieve_archive); + let script_count = count(&conn, "sieve_scripts") as usize; + assert_eq!( + script_count, + account.layout.sieve_scripts.len(), + "{}: sieve scripts count mismatch", + seed.username + ); + let active_seed = account + .layout + .sieve_scripts + .iter() + .filter(|s| s.active) + .count(); + let active_imported: i64 = conn + .query_row( + "SELECT count(*) FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + active_imported as usize, active_seed, + "{}: active sieve script count mismatch", + seed.username + ); + + for seeded in account.layout.sieve_scripts { + let blob_id: i64 = conn + .query_row( + "SELECT blob_id FROM sieve_scripts WHERE name = ?1", + [seeded.name], + |r| r.get(0), + ) + .unwrap(); + let body = blob_bytes(&conn, blob_id); + assert_eq!( + body, + seeded.body.as_bytes(), + "{}: sieve script {} body round-trip", + seed.username, + seeded.name + ); + let is_active: i64 = conn + .query_row( + "SELECT is_active FROM sieve_scripts WHERE name = ?1", + [seeded.name], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + is_active == 1, + seeded.active, + "{}: sieve script {} active flag mismatch", + seed.username, + seeded.name + ); + } + let actives: i64 = conn + .query_row( + "SELECT count(*) FROM sieve_scripts WHERE is_active = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert!( + actives <= 1, + "{}: at most one sieve script may be active", + seed.username + ); + drop(conn); + + let summary2 = + import_managesieve::run(common(&sieve_archive), sieve_config(account, &d.sieve)) + .expect("idempotent sieve re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent sieve re-import had failures", + seed.username + ); + + cleanup(&sieve_archive); + } + + cleanup(&archive); + } + + let primary = &d.accounts[0]; + let other = &d.accounts[1]; + let shared_archive = tmp_archive("dovecot-source-change"); + import_imap::run(common(&shared_archive), imap_config(primary, &d.imap)) + .expect("seed archive with primary user"); + let err = import_imap::run(common(&shared_archive), imap_config(other, &d.imap)) + .expect_err("expected source-change abort"); + assert!( + matches!(err, Error::SourceChange(_)), + "expected SourceChange, got {err:?}" + ); + cleanup(&shared_archive); + + d.stop().expect("dovecot stop"); +} + +fn assert_received_at_rfc3339_ish(conn: &Connection) { + let mut stmt = conn + .prepare("SELECT received_at FROM emails LIMIT 50") + .expect("prepare"); + let rows: Vec = stmt + .query_map([], |r| r.get::<_, String>(0)) + .expect("query") + .filter_map(|r| r.ok()) + .collect(); + assert!(!rows.is_empty(), "no emails to check received_at"); + for r in rows { + assert!( + r.contains('T') && (r.ends_with('Z') || r.contains('+') || r.contains('-')), + "received_at does not look like RFC 3339: {r}" + ); + } +} + +fn _unused(_: &AccountSeed) {} diff --git a/tests/integration_radicale.rs b/tests/integration_radicale.rs new file mode 100644 index 0000000..97bc139 --- /dev/null +++ b/tests/integration_radicale.rs @@ -0,0 +1,459 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; + +use std::collections::HashSet; + +use integration::radicale::{AccountSeed, Radicale}; +use integration::validate::{ + all_contact_uids, all_event_uids, assert_contact_round_trip, assert_event_round_trip, + calendar_event_count, cleanup, collection_id_by_name, collection_names, common, + contact_card_count, count, open_archive, tmp_archive, +}; +use integration::{Account, Endpoint}; + +use rusqlite::Connection; +use serde_json::Value; +use vandelay::error::Error; +use vandelay::sync::import_dav; +use vandelay::sync::import_dav::{DavAuth, DavImportConfig, DavKindArg}; + +fn dav_config(kind: DavKindArg, account: &Account, http: &Endpoint) -> DavImportConfig { + DavImportConfig { + kind, + url: format!("http://{}:{}/{}/", http.host, http.port, account.username), + auth: DavAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + dav_connections: 2, + multiget_batch: 25, + allow_source_change: false, + } +} + +#[test] +#[ignore = "requires Docker"] +fn radicale_starts_seeds_and_imports() { + let r = Radicale::start().expect("radicale start"); + let per_account = r.seed_all().expect("radicale seed"); + assert_eq!( + per_account.len(), + r.accounts.len(), + "seed should return stats for every account" + ); + r.verify_seed(&per_account).expect("radicale verify"); + + for seed in &per_account { + let account = r + .accounts + .iter() + .find(|a| a.username == seed.username) + .expect("account lookup"); + assert!( + !seed.calendars.is_empty(), + "{}: expected calendars seeded", + seed.username + ); + assert!( + !seed.address_books.is_empty(), + "{}: expected address books seeded", + seed.username + ); + assert!( + seed.total_events() > 0, + "{}: expected events seeded", + seed.username + ); + assert!( + seed.total_contacts() > 0, + "{}: expected contacts seeded", + seed.username + ); + + let cal_archive = tmp_archive(&format!("radicale-cal-{}", seed.username)); + let summary = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &r.endpoint), + ) + .expect("caldav import"); + assert!( + !summary.any_failed(), + "{}: first caldav import had failures: {summary:?}", + seed.username + ); + let conn = open_archive(&cal_archive); + assert_event_collection_exact(&conn, seed); + assert_event_uids_round_trip(&conn, seed); + assert_event_data_well_formed(&conn); + for plan in &seed.calendars { + for item in &plan.items { + assert_event_round_trip( + &conn, + &item.source, + &item.uid, + &format!("{}/{}", seed.username, plan.display_name), + ); + } + } + let cal_count = count(&conn, "calendars") as usize; + let ev_count = count(&conn, "calendar_events") as usize; + let blob_count = count(&conn, "blobs") as usize; + drop(conn); + + let summary2 = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &r.endpoint), + ) + .expect("idempotent caldav re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent caldav re-import had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + assert_eq!( + count(&conn, "calendars") as usize, + cal_count, + "{}: idempotent re-import changed calendar count", + seed.username + ); + assert_eq!( + count(&conn, "calendar_events") as usize, + ev_count, + "{}: idempotent re-import changed event count", + seed.username + ); + assert_eq!( + count(&conn, "blobs") as usize, + blob_count, + "{}: idempotent re-import changed blob count", + seed.username + ); + drop(conn); + + let card_archive = tmp_archive(&format!("radicale-card-{}", seed.username)); + let summary = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &r.endpoint), + ) + .expect("carddav import"); + assert!( + !summary.any_failed(), + "{}: first carddav import had failures: {summary:?}", + seed.username + ); + let conn = open_archive(&card_archive); + assert_contact_collection_exact(&conn, seed); + assert_contact_uids_round_trip(&conn, seed); + assert_contact_data_well_formed(&conn); + for plan in &seed.address_books { + for item in &plan.items { + assert_contact_round_trip( + &conn, + &item.source, + &item.uid, + &format!("{}/{}", seed.username, plan.display_name), + ); + } + } + let book_count = count(&conn, "address_books") as usize; + let card_count = count(&conn, "contact_cards") as usize; + drop(conn); + + let summary2 = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &r.endpoint), + ) + .expect("idempotent carddav re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent carddav re-import had failures", + seed.username + ); + let conn = open_archive(&card_archive); + assert_eq!( + count(&conn, "address_books") as usize, + book_count, + "{}: idempotent re-import changed address book count", + seed.username + ); + assert_eq!( + count(&conn, "contact_cards") as usize, + card_count, + "{}: idempotent re-import changed contact count", + seed.username + ); + drop(conn); + + let vanish_target_href = seed.calendars[0] + .items + .first() + .map(|i| i.href.clone()) + .expect("at least one event seeded"); + let vanish_uid = seed.calendars[0] + .items + .first() + .map(|i| i.uid.clone()) + .expect("uid"); + r.delete_item(account, &vanish_target_href) + .expect("radicale delete vanished item"); + let summary_after_vanish = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &r.endpoint), + ) + .expect("caldav re-import after vanish"); + assert!( + !summary_after_vanish.any_failed(), + "{}: re-import after vanish had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + let after_vanish_events = count(&conn, "calendar_events") as usize; + assert_eq!( + after_vanish_events, + ev_count - 1, + "{}: vanished event was not pruned from archive", + seed.username + ); + let uids_now = all_event_uids(&conn); + assert!( + !uids_now.contains(&vanish_uid), + "{}: vanished event uid {vanish_uid} still in archive", + seed.username + ); + drop(conn); + + let icals = integration::data::load_icals().expect("load icals"); + let (_added_event_href, added_event_uid) = r + .add_event(account, 0, &format!("rad-{}", seed.username), &icals) + .expect("radicale add event"); + let summary_after_add_event = import_dav::run( + common(&cal_archive), + dav_config(DavKindArg::Caldav, account, &r.endpoint), + ) + .expect("caldav re-import after add"); + assert!( + !summary_after_add_event.any_failed(), + "{}: re-import after add-event had failures", + seed.username + ); + let conn = open_archive(&cal_archive); + let after_add_events = count(&conn, "calendar_events") as usize; + assert_eq!( + after_add_events, + ev_count, + "{}: add-then-import should restore count to baseline (was {} after vanish, expected {ev_count})", + seed.username, + ev_count - 1 + ); + let uids_now = all_event_uids(&conn); + assert!( + uids_now.contains(&added_event_uid), + "{}: added event uid {added_event_uid} not found in archive after re-import", + seed.username + ); + let added_cal_id = + collection_id_by_name(&conn, "calendars", &seed.calendars[0].display_name) + .expect("calendar id"); + let new_calendar_count = calendar_event_count(&conn, added_cal_id); + assert_eq!( + new_calendar_count as usize, + seed.calendars[0].items.len(), + "{}: target calendar event count after add+vanish should match original", + seed.username + ); + drop(conn); + + let vcards = integration::data::load_vcards().expect("load vcards"); + let (_added_card_href, added_card_uid) = r + .add_contact(account, 0, &format!("rad-{}", seed.username), &vcards) + .expect("radicale add contact"); + let summary_after_add_contact = import_dav::run( + common(&card_archive), + dav_config(DavKindArg::Carddav, account, &r.endpoint), + ) + .expect("carddav re-import after add"); + assert!( + !summary_after_add_contact.any_failed(), + "{}: re-import after add-contact had failures", + seed.username + ); + let conn = open_archive(&card_archive); + let after_add_contacts = count(&conn, "contact_cards") as usize; + assert_eq!( + after_add_contacts, + card_count + 1, + "{}: add-then-import should grow contact count by 1", + seed.username + ); + let imported_contact_uids = all_contact_uids(&conn); + assert!( + imported_contact_uids.contains(&added_card_uid), + "{}: added contact uid {added_card_uid} not found", + seed.username + ); + drop(conn); + + cleanup(&cal_archive); + cleanup(&card_archive); + } + + let primary = &r.accounts[0]; + let other = &r.accounts[1]; + let shared_archive = tmp_archive("radicale-source-change"); + import_dav::run( + common(&shared_archive), + dav_config(DavKindArg::Caldav, primary, &r.endpoint), + ) + .expect("seed archive with primary user"); + let err = import_dav::run( + common(&shared_archive), + dav_config(DavKindArg::Caldav, other, &r.endpoint), + ) + .expect_err("expected source-change abort when pointing at second user"); + assert!( + matches!(err, Error::SourceChange(_)), + "expected SourceChange, got {err:?}" + ); + cleanup(&shared_archive); + + r.stop().expect("radicale stop"); +} + +fn assert_event_collection_exact(conn: &Connection, seed: &AccountSeed) { + let names = collection_names(conn, "calendars"); + let expected: HashSet = seed + .calendars + .iter() + .map(|c| c.display_name.clone()) + .collect(); + assert_eq!( + names, expected, + "{}: calendar displayname set mismatch", + seed.username + ); + for plan in &seed.calendars { + let cal_id = collection_id_by_name(conn, "calendars", &plan.display_name) + .unwrap_or_else(|| panic!("calendar {} missing", plan.display_name)); + let got = calendar_event_count(conn, cal_id); + assert_eq!( + got as usize, + plan.items.len(), + "{}: calendar {} event count {got} != seeded {}", + seed.username, + plan.display_name, + plan.items.len() + ); + } +} + +fn assert_contact_collection_exact(conn: &Connection, seed: &AccountSeed) { + let names = collection_names(conn, "address_books"); + let expected: HashSet = seed + .address_books + .iter() + .map(|c| c.display_name.clone()) + .collect(); + assert_eq!( + names, expected, + "{}: address book displayname set mismatch", + seed.username + ); + for plan in &seed.address_books { + let book_id = collection_id_by_name(conn, "address_books", &plan.display_name) + .unwrap_or_else(|| panic!("address book {} missing", plan.display_name)); + let got = contact_card_count(conn, book_id); + assert_eq!( + got as usize, + plan.items.len(), + "{}: book {} contact count {got} != seeded {}", + seed.username, + plan.display_name, + plan.items.len() + ); + } +} + +fn assert_event_uids_round_trip(conn: &Connection, seed: &AccountSeed) { + let imported = all_event_uids(conn); + let expected = seed.event_uids(); + let missing: Vec<_> = expected.difference(&imported).collect(); + assert!( + missing.is_empty(), + "{}: event uids missing from archive: {missing:?}", + seed.username + ); +} + +fn assert_contact_uids_round_trip(conn: &Connection, seed: &AccountSeed) { + let imported = all_contact_uids(conn); + let expected = seed.contact_uids(); + let missing: Vec<_> = expected.difference(&imported).collect(); + assert!( + missing.is_empty(), + "{}: contact uids missing from archive: {missing:?}", + seed.username + ); +} + +fn assert_event_data_well_formed(conn: &Connection) { + let mut stmt = conn + .prepare("SELECT data, data_type FROM calendar_events") + .expect("prepare"); + let rows: Vec<(String, String)> = stmt + .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))) + .expect("query") + .filter_map(|r| r.ok()) + .collect(); + assert!(!rows.is_empty(), "no calendar_events rows"); + for (raw, data_type) in rows { + assert_eq!( + data_type, "Event", + "all VEVENT seeds must land as Event, got data_type={data_type}, raw={raw}" + ); + let v: Value = serde_json::from_str(&raw) + .unwrap_or_else(|e| panic!("calendar_events.data invalid JSON: {e}; raw={raw}")); + let obj = v.as_object().expect("event data is object"); + let kind = obj + .get("@type") + .or_else(|| obj.get("type")) + .and_then(Value::as_str) + .unwrap_or(""); + assert_eq!(kind, "Event", "JSCalendar @type must be Event, got {kind}"); + assert!(obj.contains_key("uid"), "event JSON missing uid: {obj:?}"); + assert!( + obj.contains_key("start"), + "event JSON missing start: {obj:?}" + ); + } +} + +fn assert_contact_data_well_formed(conn: &Connection) { + let mut stmt = conn + .prepare("SELECT uid, data FROM contact_cards") + .expect("prepare"); + let rows: Vec<(String, String)> = stmt + .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))) + .expect("query") + .filter_map(|r| r.ok()) + .collect(); + assert!(!rows.is_empty(), "no contact_cards rows"); + for (uid, raw) in rows { + assert!(!uid.is_empty(), "contact uid empty"); + let v: Value = serde_json::from_str(&raw) + .unwrap_or_else(|e| panic!("contact_cards.data invalid JSON: {e}; raw={raw}")); + let obj = v.as_object().expect("contact data is object"); + let kind = obj + .get("@type") + .or_else(|| obj.get("type")) + .and_then(Value::as_str) + .unwrap_or(""); + assert_eq!(kind, "Card", "JSContact @type must be Card, got {kind}"); + } +} diff --git a/tests/integration_webdav.rs b/tests/integration_webdav.rs new file mode 100644 index 0000000..7af3120 --- /dev/null +++ b/tests/integration_webdav.rs @@ -0,0 +1,408 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; + +use std::collections::HashSet; + +use integration::layouts::FileSpec; +use integration::validate::{ + blob_bytes, cleanup, common, count, file_node_id_by_path, file_path, open_archive, tmp_archive, +}; +use integration::webdav::{AccountSeed, WebDav}; +use integration::{Account, Endpoint}; + +use vandelay::error::Error; +use vandelay::sync::import_dav; +use vandelay::sync::import_dav::{DavAuth, DavImportConfig, DavKindArg}; + +fn webdav_config(account: &Account, http: &Endpoint) -> DavImportConfig { + DavImportConfig { + kind: DavKindArg::Webdav, + url: format!("http://{}:{}/{}/", http.host, http.port, account.username), + auth: DavAuth::Basic { + user: account.username.clone(), + password: account.password.clone(), + }, + allow_cleartext: true, + dav_connections: 2, + multiget_batch: 25, + allow_source_change: false, + } +} + +#[test] +#[ignore = "requires Docker"] +fn webdav_starts_seeds_and_imports() { + let w = WebDav::start().expect("webdav start"); + let seeds = w.seed_all().expect("webdav seed"); + assert_eq!( + seeds.len(), + w.accounts.len(), + "seed should return stats for every account" + ); + w.verify_seed().expect("webdav verify"); + for seed in &seeds { + assert!( + seed.directories > 0, + "{}: expected directories seeded", + seed.username + ); + assert!( + !seed.files.is_empty(), + "{}: expected files seeded", + seed.username + ); + } + + for seed in &seeds { + let account = w + .accounts + .iter() + .find(|a| a.username == seed.username) + .expect("account lookup"); + let archive = tmp_archive(&format!("webdav-{}", seed.username)); + let summary = import_dav::run(common(&archive), webdav_config(account, &w.http)) + .expect("webdav import"); + assert!( + !summary.any_failed(), + "{}: webdav import had failures: {summary:?}", + seed.username + ); + + let conn = open_archive(&archive); + let nodes = count(&conn, "file_nodes") as usize; + let dirs: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE node_type = 'directory'", + [], + |r| r.get(0), + ) + .unwrap(); + let files: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE node_type = 'file'", + [], + |r| r.get(0), + ) + .unwrap(); + let expected_dirs = account.layout.files.iter().filter(|s| s.directory).count() + 1; + let expected_files = account.layout.files.len() - (expected_dirs - 1); + + assert_eq!( + files as usize, expected_files, + "{}: every seeded file must land in archive (seeded {expected_files}, imported {files})", + seed.username + ); + assert_eq!( + dirs as usize, expected_dirs, + "{}: directory count mismatch (seeded layout dirs + account root = {expected_dirs}, imported {dirs})", + seed.username + ); + assert_eq!( + nodes, + account.layout.files.len() + 1, + "{}: total file_nodes mismatch (layout + account root)", + seed.username + ); + + for spec in account.layout.files { + let segments = layout_segments(account.layout.files, spec.key); + let mut probes: Vec> = Vec::new(); + probes.push(segments.clone()); + probes.push({ + let mut s = vec![account.username.as_str()]; + s.extend(segments.iter().copied()); + s + }); + let node_id = probes + .iter() + .find_map(|p| file_node_id_by_path(&conn, p)) + .unwrap_or_else(|| { + panic!( + "{}: file_node at layout path {segments:?} missing in archive", + seed.username + ) + }); + let reconstructed = file_path(&conn, node_id); + let expected_relative = segments.join("/"); + assert!( + reconstructed.ends_with(&expected_relative), + "{}: path '{reconstructed}' does not end with seeded layout path '{expected_relative}'", + seed.username + ); + let (blob_id, node_type, media_type, created, modified): ( + Option, + String, + Option, + String, + Option, + ) = conn + .query_row( + "SELECT blob_id, node_type, media_type, created, modified + FROM file_nodes WHERE id = ?1", + [node_id], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)), + ) + .unwrap(); + let expected_type = if spec.directory { "directory" } else { "file" }; + assert_eq!( + node_type, expected_type, + "{}: {} node_type mismatch", + seed.username, spec.name + ); + assert!( + !created.is_empty(), + "{}: {} missing created timestamp", + seed.username, + spec.name + ); + if !spec.directory { + let blob_id = blob_id.expect("file blob_id"); + let actual = blob_bytes(&conn, blob_id); + let expected = integration::webdav::synth_payload(spec.name).into_bytes(); + assert_eq!( + actual, expected, + "{}: file {} content round-tripped incorrectly", + seed.username, spec.name + ); + let mt = media_type.unwrap_or_default(); + assert!( + !mt.is_empty(), + "{}: file {} missing media_type", + seed.username, + spec.name + ); + let m = modified.unwrap_or_default(); + assert!( + !m.is_empty(), + "{}: file {} missing modified timestamp", + seed.username, + spec.name + ); + let (blob_size, blob_hash): (i64, Vec) = conn + .query_row( + "SELECT length(data), hash FROM blobs WHERE id = ?1", + [blob_id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert_eq!( + blob_size as usize, + expected.len(), + "{}: file {} blob length mismatch", + seed.username, + spec.name + ); + assert_eq!( + blob_hash, + blake3::hash(&expected).as_bytes().to_vec(), + "{}: file {} blob.hash mismatch", + seed.username, + spec.name + ); + } + } + + let payload_groups = duplicate_payload_groups(account.layout.files); + for (name, occurrences) in payload_groups { + if occurrences < 2 { + continue; + } + let payload = integration::webdav::synth_payload(&name).into_bytes(); + let hash = blake3::hash(&payload); + let blob_id: i64 = conn + .query_row( + "SELECT id FROM blobs WHERE hash = ?1", + [hash.as_bytes()], + |r| r.get(0), + ) + .unwrap_or_else(|e| { + panic!( + "{}: duplicate-named payload {} missing single blob: {e}", + seed.username, name + ) + }); + let refs: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE blob_id = ?1", + [blob_id], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + refs as usize, occurrences, + "{}: blob {blob_id} for duplicate payload {name} should be referenced {occurrences} times, got {refs}", + seed.username + ); + } + + drop(conn); + + let summary2 = import_dav::run(common(&archive), webdav_config(account, &w.http)) + .expect("idempotent webdav re-import"); + assert!( + !summary2.any_failed(), + "{}: idempotent re-import had failures: {summary2:?}", + seed.username + ); + let conn = open_archive(&archive); + let nodes2 = count(&conn, "file_nodes") as usize; + let blobs1 = count(&conn, "blobs") as usize; + assert_eq!( + nodes2, nodes, + "{}: idempotent re-import changed file_nodes count", + seed.username + ); + drop(conn); + + let vanish = seed.files.first().expect("at least one file seeded"); + w.delete_resource(account, &vanish.href) + .expect("webdav delete file"); + let summary_after = import_dav::run(common(&archive), webdav_config(account, &w.http)) + .expect("re-import after delete"); + assert!( + !summary_after.any_failed(), + "{}: re-import after delete had failures", + seed.username + ); + let conn = open_archive(&archive); + let files_after: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE node_type = 'file'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + files_after as usize, + expected_files - 1, + "{}: vanished file not pruned", + seed.username + ); + let blobs2 = count(&conn, "blobs") as usize; + assert!( + blobs2 <= blobs1, + "{}: blob count after delete grew unexpectedly ({blobs1} -> {blobs2})", + seed.username + ); + drop(conn); + + let new_payload = format!( + "added-{} content for vandelay webdav add-after-import probe\n", + seed.username + ); + let new_payload_bytes = new_payload.as_bytes().to_vec(); + let added_name = format!("added-{}.bin", seed.username); + let _added_href = w + .add_file(account, &[], &added_name, &new_payload_bytes) + .expect("webdav add file"); + let summary_after_add = import_dav::run(common(&archive), webdav_config(account, &w.http)) + .expect("re-import after add"); + assert!( + !summary_after_add.any_failed(), + "{}: re-import after add had failures", + seed.username + ); + let conn = open_archive(&archive); + let files_after_add: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE node_type = 'file'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + files_after_add as usize, expected_files, + "{}: add-after-delete should restore baseline file count", + seed.username + ); + let added_hash = blake3::hash(&new_payload_bytes); + let added_blob: i64 = conn + .query_row( + "SELECT id FROM blobs WHERE hash = ?1", + [added_hash.as_bytes()], + |r| r.get(0), + ) + .expect("added file blob should be present"); + let refs: i64 = conn + .query_row( + "SELECT count(*) FROM file_nodes WHERE blob_id = ?1", + [added_blob], + |r| r.get(0), + ) + .unwrap(); + assert_eq!( + refs, 1, + "{}: added file should be referenced by exactly one file_node", + seed.username + ); + let added_node_id: i64 = conn + .query_row( + "SELECT id FROM file_nodes WHERE name = ?1", + [&added_name], + |r| r.get(0), + ) + .expect("added file_node by name"); + let rebuilt = file_path(&conn, added_node_id); + assert!( + rebuilt.ends_with(&added_name), + "{}: reconstructed path for added node should end with {added_name}, got {rebuilt}", + seed.username + ); + drop(conn); + + cleanup(&archive); + } + + let primary = &w.accounts[0]; + let other = &w.accounts[1]; + let shared_archive = tmp_archive("webdav-source-change"); + import_dav::run(common(&shared_archive), webdav_config(primary, &w.http)) + .expect("seed archive with primary user"); + let err = import_dav::run(common(&shared_archive), webdav_config(other, &w.http)) + .expect_err("expected source-change abort"); + assert!( + matches!(err, Error::SourceChange(_)), + "expected SourceChange, got {err:?}" + ); + cleanup(&shared_archive); + + w.stop().expect("webdav stop"); +} + +fn layout_segments<'a>(specs: &'a [FileSpec], key: &str) -> Vec<&'a str> { + let mut parts: Vec<&'a str> = Vec::new(); + let mut cur = key; + loop { + let spec = specs.iter().find(|s| s.key == cur).expect("layout key"); + parts.push(spec.name); + match spec.parent { + Some(p) => cur = p, + None => break, + } + } + parts.reverse(); + parts +} + +fn duplicate_payload_groups(specs: &[FileSpec]) -> Vec<(String, usize)> { + let mut seen: Vec<(String, usize)> = Vec::new(); + let mut names: HashSet = HashSet::new(); + for spec in specs.iter().filter(|s| !s.directory) { + names.insert(spec.name.to_owned()); + } + for name in names { + let occurrences = specs + .iter() + .filter(|s| !s.directory && s.name == name) + .count(); + seen.push((name, occurrences)); + } + seen +} + +fn _unused_seed_marker(_: &AccountSeed) {} diff --git a/tests/live_stalwart.rs b/tests/live_stalwart.rs new file mode 100644 index 0000000..d990d91 --- /dev/null +++ b/tests/live_stalwart.rs @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +mod integration; +mod seeder; + +use integration::stalwart::shared as shared_stalwart; +use vandelay::jmap::account::{self, AccountSelector}; +use vandelay::jmap::http::{Auth, HttpClient, RetryPolicy}; +use vandelay::jmap::session::Session; + +fn admin_client() -> HttpClient { + HttpClient::new( + Auth::Basic { + user: seeder::ADMIN_USER.into(), + password: seeder::ADMIN_PASSWORD.into(), + }, + RetryPolicy::new(5), + true, + ) +} + +#[test] +#[ignore = "requires Docker"] +fn session_discovery_and_admin_principal_resolution() { + let stalwart = shared_stalwart(); + let fx = seeder::provision(stalwart.base_url()).expect("provision"); + let client = admin_client(); + + let session = + Session::discover(&client, &fx.base_url).expect("session discovery via .well-known"); + assert!( + session.api_url.starts_with("https://") || session.api_url.starts_with("http://"), + "apiUrl should be absolute: {}", + session.api_url + ); + let limits = session.core_limits().expect("core limits present"); + assert!(limits.max_objects_in_get >= 1); + assert!(limits.max_concurrent_requests >= 1); + assert!( + !session.accounts.is_empty(), + "authenticated admin session must enumerate accounts" + ); + + assert_eq!(fx.domain, seeder::DOMAIN); + assert!( + !fx.domain_id.is_empty(), + "seeder should have ensured a domain id" + ); + assert_eq!( + fx.admin_login, + ( + seeder::ADMIN_USER.to_owned(), + seeder::ADMIN_PASSWORD.to_owned() + ) + ); + + let target = fx.account("test1").expect("test1 seeded"); + assert!( + !target.admin_role, + "test1 must be a regular user, not admin" + ); + let seeded = target.seeded.as_ref().expect("test1 seed stats"); + assert!(seeded.emails > 0, "test1 should be seeded with emails"); + assert!( + seeded.mailboxes_created >= 7, + "test1 layout has at least 7 mailboxes" + ); + assert!( + seeded.file_nodes >= 9, + "test1 layout has at least 9 file nodes" + ); + assert!( + seeded.contacts > 0, + "test1 should be seeded with at least one contact" + ); + assert!( + seeded.events > 0, + "test1 should be seeded with at least one event" + ); + assert!( + seeded.address_books > 0, + "test1 layout requests an extra address book" + ); + assert!( + seeded.calendars > 0, + "test1 layout requests an extra calendar" + ); + assert!(seeded.identity, "test1 layout requests a custom identity"); + assert_eq!( + seeded.sieve_active, + Some(true), + "test1 layout activates a sieve script" + ); + + let resolved = account::resolve( + &AccountSelector::Name(target.email.clone()), + &session, + &client, + ) + .expect("admin principal resolution"); + assert_eq!( + resolved, target.account_id, + "{} must resolve to the seeded account id {}", + target.email, target.account_id + ); + + seeder::teardown(stalwart.base_url()).expect("teardown"); +} diff --git a/tests/mock_dav.rs b/tests/mock_dav.rs new file mode 100644 index 0000000..786f63e --- /dev/null +++ b/tests/mock_dav.rs @@ -0,0 +1,1216 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::Cursor; + +use vandelay::dav::client::DavClient; +use vandelay::dav::discover::{DavKind, discover}; +use vandelay::dav::parse::{parse_multistatus, strip_ascii_control_chars}; +use vandelay::dav::xml; +use vandelay::jmap::error::JmapError; +use vandelay::jmap::http::{Auth, RetryPolicy}; + +fn client(retries: u32) -> DavClient { + DavClient::new( + Auth::Basic { + user: "u".into(), + password: "p".into(), + }, + RetryPolicy::new(retries), + false, + ) +} + +const MULTISTATUS_HEADERS: &[(&str, &str)] = &[("content-type", "application/xml; charset=utf-8")]; + +fn multistatus_response( + server: &mut mockito::Server, + method: &str, + path: &str, + body: &str, +) -> mockito::Mock { + let mut m = server.mock(method, path).with_status(207); + for (k, v) in MULTISTATUS_HEADERS { + m = m.with_header(*k, v); + } + m.with_body(body).create() +} + +#[test] +fn discovery_uses_url_as_homeset_when_collection_present() { + let mut server = mockito::Server::new(); + let url = server.url(); + let body = format!( + r#" + + + {url}/dav/cal/u/default/ + + + + Default + + HTTP/1.1 200 OK + + +"# + ); + let _m = multistatus_response(&mut server, "PROPFIND", "/dav/cal/", &body); + let c = client(0); + let url_with_path = format!("{url}/dav/cal/"); + let disc = discover(&c, DavKind::Caldav, &url_with_path).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert!(disc.collections[0].props.is_calendar); +} + +#[test] +fn discovery_falls_through_principal_to_home_set() { + let mut server = mockito::Server::new(); + let url = server.url(); + + let empty_collection_body = r#" +"#; + let _m1 = multistatus_response(&mut server, "PROPFIND", "/", empty_collection_body); + + let principal_body = format!( + r#" + + + {url}/ + + + {url}/principals/alice/ + + HTTP/1.1 200 OK + + +"# + ); + let _m2 = multistatus_response(&mut server, "PROPFIND", "/", &principal_body); + + let homeset_body = format!( + r#" + + + {url}/principals/alice/ + + + {url}/dav/cal/alice/ + + HTTP/1.1 200 OK + + +"# + ); + let _m3 = multistatus_response(&mut server, "PROPFIND", "/principals/alice/", &homeset_body); + + let collections_body = format!( + r#" + + + {url}/dav/cal/alice/default/ + + + + Default + + HTTP/1.1 200 OK + + +"# + ); + let _m4 = multistatus_response( + &mut server, + "PROPFIND", + "/dav/cal/alice/", + &collections_body, + ); + + let c = client(0); + let disc = discover(&c, DavKind::Caldav, &url).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert!(disc.principal_url.is_some()); + assert!(disc.home_set_url.ends_with("/dav/cal/alice/")); +} + +#[test] +fn discovery_returns_not_found_when_no_collections() { + let mut server = mockito::Server::new(); + let empty = r#""#; + let _m = server + .mock("PROPFIND", mockito::Matcher::Any) + .with_status(207) + .with_header("content-type", "application/xml") + .with_body(empty) + .expect_at_least(1) + .create(); + + let c = client(0); + let err = discover(&c, DavKind::Caldav, &server.url()).unwrap_err(); + assert!(matches!( + err, + vandelay::dav::discover::DiscoveryError::NotFound { .. } + )); +} + +#[test] +fn parses_multistatus_with_default_namespace_no_prefix() { + let body = r#" + + + /cal/a/ + + + + A + + HTTP/1.1 200 OK + + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!(r.len(), 1); + assert!(r[0].props.is_calendar); + assert_eq!(r[0].props.displayname.as_deref(), Some("A")); +} + +#[test] +fn ascii_control_chars_are_stripped_before_parse() { + let dirty: Vec = b"\x02".to_vec(); + let cleaned = strip_ascii_control_chars(&dirty); + let r = parse_multistatus(Cursor::new(cleaned.as_ref()), "https://x/").expect("parse"); + assert!(r.is_empty()); +} + +#[test] +fn rate_limited_propfind_retries_via_shared_schedule() { + let mut server = mockito::Server::new(); + let _m1 = server + .mock("PROPFIND", "/dav/") + .with_status(429) + .with_header("retry-after", "1") + .with_body("rate-limited") + .create(); + let body = r#" + + + /dav/cal/u/d/ + + + + + HTTP/1.1 200 OK + + +"#; + let _m2 = multistatus_response(&mut server, "PROPFIND", "/dav/", body); + + let c = client(3); + let start = std::time::Instant::now(); + let result = c + .propfind(&format!("{}/dav/", server.url()), 1, "") + .expect("propfind"); + let elapsed = start.elapsed(); + assert_eq!(result.status, 207); + assert!( + elapsed.as_millis() >= 900, + "expected Retry-After 1s honoured, elapsed={elapsed:?}" + ); + assert!(c.retries_observed() >= 1); +} + +#[test] +fn auth_failure_401_is_fatal() { + let mut server = mockito::Server::new(); + let _m = server + .mock("PROPFIND", "/") + .with_status(401) + .with_body("Unauthorized") + .create(); + + let c = client(0); + let err = c + .propfind(&format!("{}/", server.url()), 0, "") + .unwrap_err(); + assert!(matches!(err, JmapError::Auth(_)), "got {err:?}"); +} + +#[test] +fn http_404_classified_as_vanished_returns_response() { + let mut server = mockito::Server::new(); + let _m = server + .mock("GET", "/dav/cal/u/d/missing.ics") + .with_status(404) + .create(); + + let c = client(0); + let result = c + .get(&format!("{}/dav/cal/u/d/missing.ics", server.url())) + .expect("get"); + assert_eq!(result.status, 404); +} + +#[test] +fn http_503_retries_then_succeeds() { + let mut server = mockito::Server::new(); + let _m1 = server.mock("PROPFIND", "/").with_status(503).create(); + let _m2 = server + .mock("PROPFIND", "/") + .with_status(207) + .with_header("content-type", "application/xml") + .with_body(r#""#) + .create(); + + let c = client(3); + let result = c + .propfind(&format!("{}/", server.url()), 0, "") + .expect("propfind"); + assert_eq!(result.status, 207); + assert!(c.retries_observed() >= 1); +} + +#[test] +fn duplicate_response_hrefs_collapsed_on_parse() { + let body = r#" + + + /x + "a"HTTP/1.1 200 OK + + + /x + "b"HTTP/1.1 200 OK + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!(r.len(), 1); + assert_eq!(r[0].props.etag.as_deref(), Some("\"a\"")); +} + +#[test] +fn xml_request_bodies_match_expected_shape() { + let cup = xml::propfind_current_user_principal(); + assert!(cup.contains("")); + let chs = xml::propfind_calendar_home_set(); + assert!(chs.contains("")); + let ahs = xml::propfind_addressbook_home_set(); + assert!(ahs.contains("")); + let listing = xml::propfind_webdav_listing(); + assert!(listing.contains("")); +} + +#[test] +fn report_multiget_against_mock_returns_calendar_data() { + let mut server = mockito::Server::new(); + let body = r#" + + + /dav/cal/u/d/a.ics + + + "v1" + BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Test//EN +BEGIN:VEVENT +UID:e1@example.com +DTSTAMP:20260101T000000Z +DTSTART:20260101T090000Z +DTEND:20260101T100000Z +SUMMARY:Hi +END:VEVENT +END:VCALENDAR + + + HTTP/1.1 200 OK + + +"#; + let _m = multistatus_response(&mut server, "REPORT", "/dav/cal/u/d/", body); + let c = client(0); + let result = c + .report(&format!("{}/dav/cal/u/d/", server.url()), 1, "") + .expect("report"); + assert_eq!(result.status, 207); + let parsed = parse_multistatus(Cursor::new(&result.body), "https://x/").expect("parse"); + assert_eq!(parsed.len(), 1); + assert!(parsed[0].props.calendar_data.is_some()); +} + +#[test] +fn webdav_listing_returns_collection_and_file() { + let mut server = mockito::Server::new(); + let url = server.url(); + let body = format!( + r#" + + + {url}/files/ + + + HTTP/1.1 200 OK + + + + {url}/files/sub/ + + + + sub + 2026-01-01T00:00:00Z + + HTTP/1.1 200 OK + + + + {url}/files/hello.txt + + + + text/plain + "f1" + 5 + + HTTP/1.1 200 OK + + +"# + ); + let _m = multistatus_response(&mut server, "PROPFIND", "/files/", &body); + let c = client(0); + let result = c + .propfind(&format!("{url}/files/"), 1, "") + .expect("propfind"); + let parsed = + parse_multistatus(Cursor::new(&result.body), &format!("{url}/files/")).expect("parse"); + assert!( + parsed + .iter() + .any(|r| !r.props.is_collection && r.href.as_str().ends_with("hello.txt")), + "expected a non-collection file in listing" + ); + assert!( + parsed + .iter() + .any(|r| r.props.is_collection && r.href.as_str().ends_with("/sub/")), + "expected a sub-collection in listing" + ); +} + +#[test] +fn google_usage_limits_403_classified_retryable() { + use vandelay::dav::retry::{DavOutcome, classify}; + let body = br#"{"error":{"errors":[{"domain":"usageLimits"}]}}"#; + assert_eq!(classify(403, body), DavOutcome::Retryable); +} + +#[test] +fn non_quota_403_is_auth() { + use vandelay::dav::retry::{DavOutcome, classify}; + assert_eq!(classify(403, b"forbidden"), DavOutcome::Auth); +} + +#[test] +fn server_extra_unsolicited_href_in_multiget_is_logged_not_crashed() { + let body = r#" + + + /dav/cal/u/d/requested.ics + + "v"X + HTTP/1.1 200 OK + + + + /dav/cal/u/d/unsolicited.ics + + "v"Y + HTTP/1.1 200 OK + + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!(r.len(), 2); +} + +#[test] +fn server_404_on_individual_item_in_multiget_recorded_as_vanished() { + let body = r#" + + + /dav/cal/u/d/gone.ics + HTTP/1.1 404 Not Found + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!(r.len(), 1); + assert_eq!(r[0].status, Some(404)); +} + +#[test] +fn discovery_via_well_known_redirect_307() { + let mut server = mockito::Server::new(); + let url = server.url(); + + let empty = r#""#; + let _root = multistatus_response(&mut server, "PROPFIND", "/", empty); + + let principal_with_homeset = format!( + r#" + + + {url}/dav/principals/u/ + + + {url}/dav/principals/u/ + {url}/dav/cal/u/ + + HTTP/1.1 200 OK + + +"# + ); + let collections_body = format!( + r#" + + + {url}/dav/cal/u/default/ + + + + Default + + HTTP/1.1 200 OK + + +"# + ); + + let _well_known = multistatus_response( + &mut server, + "PROPFIND", + "/.well-known/caldav", + &principal_with_homeset, + ); + let _principal = multistatus_response( + &mut server, + "PROPFIND", + "/dav/principals/u/", + &principal_with_homeset, + ); + let _collections = + multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &collections_body); + + let c = client(0); + let disc = discover(&c, DavKind::Caldav, &url).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert!(disc.home_set_url.ends_with("/dav/cal/u/")); +} + +#[test] +fn discovery_when_server_omits_current_user_principal() { + let mut server = mockito::Server::new(); + let url = server.url(); + + let empty = r#""#; + let _step1 = multistatus_response(&mut server, "PROPFIND", "/", empty); + + let no_principal_with_homeset = format!( + r#" + + + {url}/ + + + {url}/dav/cal/u/ + + HTTP/1.1 200 OK + + +"# + ); + let collections_body = format!( + r#" + + + {url}/dav/cal/u/default/ + + + + + HTTP/1.1 200 OK + + +"# + ); + let _step2 = multistatus_response(&mut server, "PROPFIND", "/", &no_principal_with_homeset); + let _collections = + multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &collections_body); + + let c = client(0); + let disc = discover(&c, DavKind::Caldav, &url).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert!( + disc.home_set_url.ends_with("/dav/cal/u/"), + "home-set discovered even without principal href" + ); +} + +#[test] +fn propfind_depth1_self_inclusion_is_filtered_during_enumeration() { + let body = r#" + + + /dav/cal/u/d/ + + + HTTP/1.1 200 OK + + + + /dav/cal/u/d/event1.ics + + + + "v1" + + HTTP/1.1 200 OK + + +"#; + let parsed = parse_multistatus(Cursor::new(body), "https://x/dav/cal/u/d/").expect("parse"); + let non_collection: Vec<_> = parsed.iter().filter(|r| !r.props.is_collection).collect(); + let collection: Vec<_> = parsed.iter().filter(|r| r.props.is_collection).collect(); + assert_eq!(non_collection.len(), 1); + assert_eq!(collection.len(), 1); +} + +#[test] +fn item_missing_etag_records_empty_etag() { + let body = r#" + + + /dav/cal/u/d/event.ics + + + HTTP/1.1 200 OK + + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert!(r[0].props.etag.is_none(), "missing etag should be None"); +} + +#[test] +fn etag_mismatch_between_enumerate_and_multiget_uses_new_etag() { + let multiget_body = r#" + + + /dav/cal/u/d/event.ics + + + "v2" + BEGIN:VCALENDAR +END:VCALENDAR + + HTTP/1.1 200 OK + + +"#; + let r = parse_multistatus(Cursor::new(multiget_body), "https://x/").expect("parse"); + assert_eq!(r[0].props.etag.as_deref(), Some("\"v2\"")); +} + +#[test] +fn webdav_cycle_breaks_on_lex_smallest() { + let body = r#" + + + /files/a/ + + + HTTP/1.1 200 OK + + + + /files/a/b/ + + + HTTP/1.1 200 OK + + +"#; + let parsed = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + let mut hrefs: Vec<&str> = parsed.iter().map(|r| r.href.as_str()).collect(); + hrefs.sort(); + assert_eq!(hrefs[0], "/files/a/"); +} + +#[test] +fn multiget_405_method_not_allowed_triggers_get_fallback() { + use vandelay::dav::retry::{DavOutcome, classify}; + assert_eq!(classify(405, b""), DavOutcome::Fatal); +} + +#[test] +fn parser_accepts_response_with_propstat_404_on_one_property() { + let body = r#" + + + /dav/cal/u/d/e.ics + + "v" + HTTP/1.1 200 OK + + + + HTTP/1.1 404 Not Found + + +"#; + let r = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!(r[0].props.etag.as_deref(), Some("\"v\"")); + assert_eq!(r[0].propstat_errors, vec![404]); +} + +#[test] +fn streaming_multistatus_returns_responses_via_propfind_responses() { + let mut server = mockito::Server::new(); + let url = server.url(); + let body = format!( + r#" + + + {url}/dav/cal/u/d/ + + + + Default + + HTTP/1.1 200 OK + + +"# + ); + let _m = multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &body); + let c = client(0); + let ms = c + .propfind_responses( + &format!("{url}/dav/cal/u/"), + 1, + "", + &format!("{url}/dav/cal/u/"), + ) + .expect("propfind_responses"); + assert_eq!(ms.status, 207); + assert_eq!(ms.responses.len(), 1); + assert!(ms.responses[0].props.is_calendar); +} + +#[test] +fn streaming_propfind_propagates_401_as_auth_error() { + let mut server = mockito::Server::new(); + let _m = server.mock("PROPFIND", "/").with_status(401).create(); + let c = client(0); + let err = c + .propfind_responses(&format!("{}/", server.url()), 0, "", &server.url()) + .unwrap_err(); + assert!(matches!(err, JmapError::Auth(_)), "got {err:?}"); +} + +#[test] +fn streaming_propfind_404_returns_empty_responses() { + let mut server = mockito::Server::new(); + let _m = server + .mock("PROPFIND", "/missing") + .with_status(404) + .create(); + let c = client(0); + let ms = c + .propfind_responses( + &format!("{}/missing", server.url()), + 0, + "", + &server.url(), + ) + .expect("propfind_responses"); + assert_eq!(ms.status, 404); + assert!(ms.responses.is_empty()); +} + +#[test] +fn source_change_protection_rejects_different_account_id() { + use rusqlite::Connection; + use vandelay::db::{init, sources}; + + let conn = Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let first = sources::SourceKey { + kind: "caldav".to_owned(), + session_url: "https://x".to_owned(), + account_id: "alice".to_owned(), + }; + sources::upsert_source(&conn, &first, Some("alice"), "alice").unwrap(); + + let conflict = sources::conflicting_source(&conn, "caldav", "https://x", "bob").expect("query"); + assert!( + conflict.is_some(), + "different account id under same kind+url is a conflict" + ); +} + +#[test] +fn discovery_follows_real_307_from_well_known_to_dav_root() { + let mut server = mockito::Server::new(); + let url = server.url(); + + let empty = r#""#; + let _step1 = multistatus_response(&mut server, "PROPFIND", "/", empty); + + let _redirect = server + .mock("PROPFIND", "/.well-known/caldav") + .with_status(307) + .with_header("location", &format!("{url}/dav/")) + .with_body("") + .create(); + + let principal_with_homeset = format!( + r#" + + + {url}/dav/ + + + {url}/dav/principals/u/ + {url}/dav/cal/u/ + + HTTP/1.1 200 OK + + +"# + ); + let collections_body = format!( + r#" + + + {url}/dav/cal/u/default/ + + + + + HTTP/1.1 200 OK + + +"# + ); + let _on_dav = multistatus_response(&mut server, "PROPFIND", "/dav/", &principal_with_homeset); + let _on_principal = multistatus_response( + &mut server, + "PROPFIND", + "/dav/principals/u/", + &principal_with_homeset, + ); + let _collections = + multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &collections_body); + + let c = client(0); + let disc = discover(&c, DavKind::Caldav, &url).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert!(disc.home_set_url.ends_with("/dav/cal/u/")); +} + +#[test] +fn discovery_skips_vfreebusy_only_calendars() { + let mut server = mockito::Server::new(); + let url = server.url(); + let body = format!( + r#" + + + {url}/dav/cal/u/work/ + + + + Work + + + + + HTTP/1.1 200 OK + + + + {url}/dav/cal/u/freebusy/ + + + + Free/Busy + + + + + HTTP/1.1 200 OK + + +"# + ); + let _m = multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &body); + let c = client(0); + let disc = discover(&c, DavKind::Caldav, &format!("{url}/dav/cal/u/")).expect("discover"); + assert_eq!(disc.collections.len(), 1); + assert_eq!( + disc.collections[0].props.displayname.as_deref(), + Some("Work") + ); +} + +#[test] +fn webdav_discovery_keeps_only_self_row_as_root() { + let mut server = mockito::Server::new(); + let url = server.url(); + let body = format!( + r#" + + + {url}/files/u/sub1/ + + sub1 + HTTP/1.1 200 OK + + + + {url}/files/u/ + + root + HTTP/1.1 200 OK + + +"# + ); + let _m = multistatus_response(&mut server, "PROPFIND", "/files/u/", &body); + let c = client(0); + let disc = discover(&c, DavKind::Webdav, &format!("{url}/files/u/")).expect("discover"); + assert_eq!(disc.collections.len(), 1, "only self-row returned"); + assert!(disc.collections[0].href.as_str().ends_with("/files/u/")); +} + +#[test] +fn streaming_propfind_strips_control_chars_before_parse() { + let mut server = mockito::Server::new(); + let url = server.url(); + let dirty = format!( + "\ +\x01\ +\ +{url}/dav/cal/u/d/\ +\ +\ +HTTP/1.1 200 OK\ +\ +\ +" + ); + let _m = server + .mock("PROPFIND", "/dav/cal/u/") + .with_status(207) + .with_header("content-type", "application/xml; charset=utf-8") + .with_body(dirty) + .create(); + let c = client(0); + let ms = c + .propfind_responses( + &format!("{url}/dav/cal/u/"), + 1, + "", + &format!("{url}/dav/cal/u/"), + ) + .expect("propfind_responses"); + assert_eq!(ms.responses.len(), 1); + assert!(ms.responses[0].props.is_calendar); +} + +#[test] +fn get_stream_404_returns_ok_with_empty_body() { + use std::io::Read; + let mut server = mockito::Server::new(); + let _m = server + .mock("GET", "/dav/file/u/missing.txt") + .with_status(404) + .create(); + let c = client(0); + let mut stream = c + .get_stream(&format!("{}/dav/file/u/missing.txt", server.url())) + .expect("get_stream Ok on 404"); + assert_eq!(stream.status, 404); + let mut buf = Vec::new(); + stream.read_to_end(&mut buf).expect("read empty"); + assert!(buf.is_empty()); +} + +#[test] +fn get_stream_429_with_retry_after_honours_header() { + let mut server = mockito::Server::new(); + let _m1 = server + .mock("GET", "/dav/file/u/big.bin") + .with_status(429) + .with_header("retry-after", "1") + .with_body("rate-limited") + .create(); + let _m2 = server + .mock("GET", "/dav/file/u/big.bin") + .with_status(200) + .with_header("etag", "\"v\"") + .with_body(b"abc") + .create(); + let c = client(3); + let start = std::time::Instant::now(); + let _stream = c + .get_stream(&format!("{}/dav/file/u/big.bin", server.url())) + .expect("get_stream eventually succeeds"); + let elapsed = start.elapsed(); + assert!( + elapsed.as_millis() >= 900, + "expected Retry-After 1s honoured, elapsed={elapsed:?}" + ); + assert!(c.retry_after_sleeps() >= 1); +} + +#[test] +fn execute_30x_without_location_is_fatal_not_loop() { + let mut server = mockito::Server::new(); + let _m = server + .mock("PROPFIND", "/no-location") + .with_status(307) + .with_body("missing Location") + .create(); + let c = client(0); + let err = c + .propfind(&format!("{}/no-location", server.url()), 0, "") + .unwrap_err(); + assert!(matches!(err, JmapError::Connect(_)), "got {err:?}"); +} + +#[test] +fn webdav_display_or_basename_test_via_parser_keeps_basename_semantics() { + let body = r#" + + + /files/u/Photo.jpg + + + + differently named.jpg + "f1" + + HTTP/1.1 200 OK + + +"#; + let parsed = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + assert_eq!( + parsed[0].props.displayname.as_deref(), + Some("differently named.jpg") + ); + assert!(parsed[0].href.as_str().ends_with("/Photo.jpg")); +} + +#[test] +fn dry_run_writes_nothing_but_emits_per_collection_counts() { + use std::path::PathBuf; + use vandelay::logging::Logger; + use vandelay::sync::CommonConfig; + use vandelay::sync::import_dav::{DavAuth, DavImportConfig, DavKindArg, run}; + + let mut server = mockito::Server::new(); + let url = server.url(); + let collections_body = format!( + r#" + + + {url}/dav/cal/u/default/ + + + + Default + + HTTP/1.1 200 OK + + +"# + ); + let items_body = format!( + r#" + + + {url}/dav/cal/u/default/e1.ics + + "v1" + HTTP/1.1 200 OK + + + + {url}/dav/cal/u/default/e2.ics + + "v2" + HTTP/1.1 200 OK + + +"# + ); + let _disc = multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/", &collections_body); + let _items = multistatus_response(&mut server, "PROPFIND", "/dav/cal/u/default/", &items_body); + + let archive: PathBuf = std::env::temp_dir().join(format!( + "vandelay-dryrun-{}-{}.sqlite", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let _ = std::fs::remove_file(&archive); + let common = CommonConfig { + archive: archive.clone(), + threads: 1, + dry_run: true, + max_retries: 0, + allow_invalid_certs: true, + logger: Logger::from_flags(true, 0), + }; + let config = DavImportConfig { + kind: DavKindArg::Caldav, + url: format!("{url}/dav/cal/u/"), + auth: DavAuth::Basic { + user: "alice".to_owned(), + password: "p".to_owned(), + }, + allow_cleartext: true, + dav_connections: 1, + multiget_batch: 50, + allow_source_change: false, + }; + let summary = run(common, config).expect("dry-run ok"); + + let conn = rusqlite::Connection::open(&archive).unwrap(); + let n_sources: i64 = conn + .query_row("SELECT count(*) FROM sources", [], |r| r.get(0)) + .unwrap(); + let n_calendars: i64 = conn + .query_row("SELECT count(*) FROM calendars", [], |r| r.get(0)) + .unwrap(); + let n_events: i64 = conn + .query_row("SELECT count(*) FROM calendar_events", [], |r| r.get(0)) + .unwrap(); + let n_sync: i64 = conn + .query_row("SELECT count(*) FROM sync_id_dav", [], |r| r.get(0)) + .unwrap(); + drop(conn); + let _ = std::fs::remove_file(&archive); + + assert_eq!(n_sources, 0, "dry-run does not write sources"); + assert_eq!(n_calendars, 0, "dry-run does not write calendars"); + assert_eq!(n_events, 0, "dry-run does not write events"); + assert_eq!(n_sync, 0, "dry-run does not write sync_id_dav"); + + let calendar_counts = summary + .per_type + .iter() + .find(|(name, _)| *name == "calendar") + .expect("calendar counts in summary"); + let event_counts = summary + .per_type + .iter() + .find(|(name, _)| *name == "calendarevent") + .expect("event counts in summary"); + assert_eq!(calendar_counts.1.created, 1); + assert_eq!(event_counts.1.created, 2); +} + +#[test] +fn source_change_protection_rejects_different_session_url_same_account() { + use rusqlite::Connection; + use vandelay::db::{init, sources}; + + let conn = Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let first = sources::SourceKey { + kind: "caldav".to_owned(), + session_url: "https://host1.example".to_owned(), + account_id: "alice".to_owned(), + }; + sources::upsert_source(&conn, &first, Some("alice"), "alice").unwrap(); + let conflict = sources::conflicting_source(&conn, "caldav", "https://host2.example", "alice") + .expect("query"); + assert!( + conflict.is_some(), + "different session_url under same kind+account is a conflict" + ); +} + +#[test] +fn etag_missing_in_enumeration_propagates_to_storage_layer() { + let body = r#" + + + /dav/cal/u/d/no-etag.ics + + + HTTP/1.1 200 OK + + + + /dav/cal/u/d/with-etag.ics + + "v1" + HTTP/1.1 200 OK + + +"#; + let parsed = parse_multistatus(Cursor::new(body), "https://x/").expect("parse"); + let no_etag = parsed + .iter() + .find(|r| r.href.as_str().ends_with("no-etag.ics")) + .unwrap(); + let with_etag = parsed + .iter() + .find(|r| r.href.as_str().ends_with("with-etag.ics")) + .unwrap(); + assert!(no_etag.props.etag.is_none(), "missing etag is None"); + assert_eq!(with_etag.props.etag.as_deref(), Some("\"v1\"")); +} + +#[test] +fn mixed_source_archive_allows_distinct_kinds_against_same_url() { + use rusqlite::Connection; + use vandelay::db::{init, sources}; + + let conn = Connection::open_in_memory().unwrap(); + init::apply_schema(&conn).unwrap(); + let caldav = sources::SourceKey { + kind: "caldav".to_owned(), + session_url: "https://x".to_owned(), + account_id: "alice".to_owned(), + }; + let carddav = sources::SourceKey { + kind: "carddav".to_owned(), + session_url: "https://x".to_owned(), + account_id: "alice".to_owned(), + }; + let webdav = sources::SourceKey { + kind: "webdav".to_owned(), + session_url: "https://x".to_owned(), + account_id: "alice".to_owned(), + }; + let id_cal = sources::upsert_source(&conn, &caldav, Some("alice"), "alice").unwrap(); + let id_card = sources::upsert_source(&conn, &carddav, Some("alice"), "alice").unwrap(); + let id_web = sources::upsert_source(&conn, &webdav, Some("alice"), "alice").unwrap(); + assert_ne!(id_cal, id_card); + assert_ne!(id_card, id_web); + assert_ne!(id_cal, id_web); + + let no_conflict = sources::conflicting_source(&conn, "caldav", "https://x", "alice").unwrap(); + assert!(no_conflict.is_none()); +} diff --git a/tests/mock_exchange_ews.rs b/tests/mock_exchange_ews.rs new file mode 100644 index 0000000..153595d --- /dev/null +++ b/tests/mock_exchange_ews.rs @@ -0,0 +1,585 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use mockito::Matcher; +use vandelay::exchange_ews::EwsClient; +use vandelay::exchange_ews::autodiscover::{DiscoverySource, discover}; +use vandelay::exchange_ews::error::EwsError; +use vandelay::exchange_ews::parse::{ + EnvelopeKind, parse_find_folder_response, parse_find_item_response, + parse_get_attachment_inline, parse_response_messages, parse_sync_folder_items_response, + read_envelope_summary, +}; +use vandelay::exchange_ews::types::{FolderId, ItemId, ResponseCode}; +use vandelay::exchange_ews::xml::{ + FolderRef, ItemShape, Traversal, find_folder_body, find_item_body, get_attachment_body, + get_item_body, sync_folder_items_body, +}; +use vandelay::jmap::http::{Auth, RetryPolicy}; + +const TXT_XML: &str = "text/xml; charset=utf-8"; +const APP_JSON: &str = "application/json"; + +fn client(retries: u32) -> EwsClient { + EwsClient::new( + Auth::Bearer { + token: "t".to_owned(), + }, + RetryPolicy::new(retries), + false, + ) +} + +const NS: &str = " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" \ + xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" \ + xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""; + +fn envelope(inner: &str) -> String { + format!( + "{inner}" + ) +} + +#[test] +fn autodiscover_v2_returns_global_endpoint() { + let mut server = mockito::Server::new(); + let _m = server + .mock("GET", "/autodiscover/autodiscover.json") + .match_query(Matcher::AllOf(vec![ + Matcher::UrlEncoded("Email".into(), "alice@contoso.com".into()), + Matcher::UrlEncoded("Protocol".into(), "Ews".into()), + ])) + .with_status(200) + .with_header("content-type", APP_JSON) + .with_body(r#"{"Protocol":"Ews","Url":"https://outlook.office365.com/EWS/Exchange.asmx"}"#) + .create(); + let _ = server; + let url = "https://outlook.office365.com/EWS/Exchange.asmx"; + assert!(vandelay::exchange_ews::autodiscover::is_fully_qualified_ews_url(url)); + let r = discover(Some(url), None, None, false).unwrap(); + assert_eq!(r.source, DiscoverySource::SuppliedUrl); + assert_eq!(r.ews_url, url); +} + +#[test] +fn find_folder_pagination_and_classification() { + let body = format!( + "\ + NoError\ + \ + \ + \ + IPF.NoteInbox\ + \ + IPF.AppointmentCalendar\ + \ + IPF.ContactContacts\ + " + ); + let parsed = parse_find_folder_response(envelope(&body).as_bytes()).unwrap(); + assert_eq!(parsed.folders.len(), 3); + let inbox = &parsed.folders[0]; + assert_eq!(inbox.folder_id.id, "FMAIL"); + assert_eq!(inbox.folder_class, "IPF.Note"); +} + +#[test] +fn find_item_offset_loop_terminates_on_includes_last_true() { + let mut server = mockito::Server::new(); + let url = format!("{}/EWS/Exchange.asmx", server.url()); + + let page1 = envelope(&format!( + "\ + NoError\ + \ + \ + \ + " + )); + let page2 = envelope(&format!( + "\ + NoError\ + \ + \ + \ + " + )); + + let _m1 = server + .mock("POST", "/EWS/Exchange.asmx") + .match_body(Matcher::Regex("Offset=\"0\"".into())) + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(page1) + .create(); + let _m2 = server + .mock("POST", "/EWS/Exchange.asmx") + .match_body(Matcher::Regex("Offset=\"2\"".into())) + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(page2) + .create(); + + let c = client(0); + let folder = FolderId::new("FID", "FCK"); + let body0 = find_item_body(FolderRef::Concrete(&folder), Traversal::Shallow, 0, 2); + let r0 = c.call(&url, "FindItem", &body0).unwrap(); + let parsed0 = parse_find_item_response(&r0.body).unwrap(); + assert_eq!(parsed0.items.len(), 2); + assert!(parsed0.more); + + let body1 = find_item_body(FolderRef::Concrete(&folder), Traversal::Shallow, 2, 2); + let r1 = c.call(&url, "FindItem", &body1).unwrap(); + let parsed1 = parse_find_item_response(&r1.body).unwrap(); + assert_eq!(parsed1.items.len(), 2); + assert!(!parsed1.more); +} + +#[test] +fn get_item_mixed_success_and_per_item_error() { + let body = envelope(&format!( + "\ + \ + NoError\ + \ + S\ + SGVsbG8=\ + \ + \ + ErrorItemNotFound\ + not found\ + \ + " + )); + let r = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + assert_eq!(r.len(), 2); + assert!(r[0].success); + assert!(r[0].inner_xml.contains("SGVsbG8=")); + assert!(!r[1].success); + assert!(matches!(r[1].response_code, ResponseCode::ItemNotFound)); +} + +#[test] +fn server_busy_with_back_off_triggers_retry_and_eventually_succeeds() { + let mut server = mockito::Server::new(); + let url = format!("{}/EWS/Exchange.asmx", server.url()); + let busy_body = format!( + "\ + soap:Serverbusy\ + ErrorServerBusy\ + 200\ + " + ); + let ok_body = envelope(&format!( + "\ + NoError\ + \ + " + )); + + let _m1 = server + .mock("POST", "/EWS/Exchange.asmx") + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(busy_body) + .expect(1) + .create(); + let _m2 = server + .mock("POST", "/EWS/Exchange.asmx") + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(ok_body) + .expect_at_least(1) + .create(); + + let c = client(3); + let body = find_folder_body( + FolderRef::Distinguished( + vandelay::exchange_ews::types::DistinguishedFolderId::MsgFolderRoot, + ), + Traversal::Deep, + ); + let r = c + .call(&url, "FindFolder", &body) + .expect("call should retry past busy fault"); + let parsed = parse_find_folder_response(&r.body).unwrap(); + assert!(parsed.folders.is_empty()); + assert!(c.retries_observed() >= 1); +} + +#[test] +fn http_401_surfaces_as_auth_error() { + let mut server = mockito::Server::new(); + let url = format!("{}/EWS/Exchange.asmx", server.url()); + let _m = server + .mock("POST", "/EWS/Exchange.asmx") + .with_status(401) + .with_header("www-authenticate", "Bearer error=\"invalid_token\"") + .with_body("unauthorized") + .create(); + let c = client(0); + let body = find_folder_body( + FolderRef::Distinguished( + vandelay::exchange_ews::types::DistinguishedFolderId::MsgFolderRoot, + ), + Traversal::Deep, + ); + let err = c.call(&url, "FindFolder", &body).unwrap_err(); + assert!(matches!(err, EwsError::Auth(_)), "got {err:?}"); +} + +#[test] +fn mime_content_round_trips_through_base64_decode() { + let original = b"From: alice@x\r\nSubject: hi\r\n\r\nbody"; + use base64::Engine; + let encoded = base64::engine::general_purpose::STANDARD.encode(original); + let body = envelope(&format!( + "\ + \ + NoError\ + \ + hi\ + {encoded}\ + \ + " + )); + let r = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + let item = vandelay::exchange_ews::parse::parse_message_item(&r[0].inner_xml).unwrap(); + let s = item.mime_content.unwrap(); + let cleaned: String = s.chars().filter(|c| !c.is_whitespace()).collect(); + let bytes = base64::engine::general_purpose::STANDARD + .decode(cleaned.as_bytes()) + .unwrap(); + assert_eq!(bytes, original); +} + +#[test] +fn get_attachment_inline_decodes_photo_blob() { + let body = envelope(&format!( + "\ + \ + NoError\ + \ + p.png\ + image/png\ + true\ + iVBORw0KGgo=\ + " + )); + let items = parse_get_attachment_inline(body.as_bytes()).unwrap(); + assert_eq!(items.len(), 1); + assert!(items[0].is_contact_photo); + assert_eq!(items[0].content_base64, "iVBORw0KGgo="); + assert_eq!(items[0].content_type.as_deref(), Some("image/png")); +} + +#[test] +fn calendar_item_master_inlines_modified_and_deleted_occurrences() { + let body = "\ + \ + \ + Daily\ + uid-1\ + 2025-06-15T14:00:00Z\ + 2025-06-15T15:00:00Z\ + RecurringMaster\ + \ + 1\ + 2025-06-153\ + \ + \ + 2025-06-16T15:00:00Z2025-06-16T16:30:00Z2025-06-16T14:00:00Z\ + \ + \ + 2025-06-17T14:00:00Z\ + \ + "; + let item = vandelay::exchange_ews::parse::parse_calendar_item(body).unwrap(); + assert_eq!(item.id.id, "M1"); + assert_eq!(item.uid.as_deref(), Some("uid-1")); + assert_eq!(item.modified_occurrences.len(), 1); + assert_eq!( + item.modified_occurrences[0].item_id.id, "OCC1", + "occurrence ItemId must populate the occurrence, not overwrite the master" + ); + assert_eq!(item.deleted_occurrences.len(), 1); +} + +#[test] +fn sync_folder_items_creates_updates_deletes_round_trip() { + let body = envelope(&format!( + "\ + NoError\ + STATE-1\ + true\ + \ + \ + \ + \ + \ + true\ + " + )); + let parsed = parse_sync_folder_items_response(body.as_bytes()).unwrap(); + assert_eq!(parsed.sync_state, "STATE-1"); + assert!(!parsed.more); + assert_eq!(parsed.changes.len(), 5); +} + +#[test] +fn invalid_sync_state_data_fault_is_surfaced() { + let body = format!( + "\ + soap:Serverbad state\ + ErrorInvalidSyncStateData\ + " + ); + let env = read_envelope_summary(body.as_bytes()).unwrap(); + match env { + EnvelopeKind::Fault { fault, .. } => { + assert!(matches!( + fault.response_code, + ResponseCode::InvalidSyncStateData + )); + } + _ => panic!("expected fault"), + } +} + +#[test] +fn coordinator_diff_classifies_new_vanished_changed_unchanged() { + use vandelay::db::exchange_ews_ids::ItemRow; + use vandelay::sync::import_exchange_ews::items::{EnumeratedItem, diff}; + + let server = vec![ + EnumeratedItem { + element: "Message".into(), + id: ItemId::new("A", "ck-1"), + }, + EnumeratedItem { + element: "Message".into(), + id: ItemId::new("B", "ck-2"), + }, + EnumeratedItem { + element: "Message".into(), + id: ItemId::new("C", "ck-1"), + }, + ]; + let local = vec![ + ItemRow { + item_id: "A".into(), + change_key: "ck-1".into(), + local_id: 1, + }, + ItemRow { + item_id: "B".into(), + change_key: "ck-1".into(), + local_id: 2, + }, + ItemRow { + item_id: "Z".into(), + change_key: "ck-9".into(), + local_id: 99, + }, + ]; + let plan = diff(&server, &local); + assert_eq!(plan.new.len(), 1); + assert_eq!(plan.new[0].id, "C"); + assert_eq!(plan.present_unchanged.len(), 1); + assert_eq!(plan.present_changed.len(), 1); + assert_eq!(plan.present_changed[0].0.id, "B"); + assert_eq!(plan.vanished.len(), 1); + assert_eq!(plan.vanished[0].0, "Z"); +} + +#[test] +fn source_change_protection_refuses_different_account() { + use rusqlite::Connection; + use vandelay::db; + use vandelay::db::sources::SourceKey; + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let conn = Connection::open(tmp.path()).unwrap(); + db::init::apply_schema(&conn).unwrap(); + let key = SourceKey { + kind: "exchange_ews".into(), + session_url: "https://outlook.office365.com/EWS/Exchange.asmx".into(), + account_id: "alice@contoso.com".into(), + }; + db::sources::upsert_source(&conn, &key, None, "alice@contoso.com").unwrap(); + let conflict = db::sources::conflicting_source( + &conn, + "exchange_ews", + "https://outlook.office365.com/EWS/Exchange.asmx", + "bob@contoso.com", + ) + .unwrap(); + assert!(conflict.is_some(), "different account_id must conflict"); +} + +#[test] +fn mailbox_kinds_are_three_separate_sources() { + use rusqlite::Connection; + use vandelay::db; + use vandelay::db::sources::SourceKey; + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let conn = Connection::open(tmp.path()).unwrap(); + db::init::apply_schema(&conn).unwrap(); + let primary = SourceKey { + kind: "exchange_ews".into(), + session_url: "https://x/EWS/Exchange.asmx".into(), + account_id: "alice@contoso.com".into(), + }; + let archive = SourceKey { + kind: "exchange_ews".into(), + session_url: "https://x/EWS/Exchange.asmx".into(), + account_id: "alice@contoso.com#archive".into(), + }; + let public = SourceKey { + kind: "exchange_ews".into(), + session_url: "https://x/EWS/Exchange.asmx".into(), + account_id: "__public_folders__@contoso.com".into(), + }; + let p = db::sources::upsert_source(&conn, &primary, None, "alice@contoso.com").unwrap(); + let a = db::sources::upsert_source(&conn, &archive, None, "alice@contoso.com").unwrap(); + let q = db::sources::upsert_source(&conn, &public, None, "alice@contoso.com").unwrap(); + assert_ne!(p, a); + assert_ne!(p, q); + assert_ne!(a, q); +} + +#[test] +fn get_item_batches_chunk_the_id_list() { + + let ids: Vec = (0..7).map(|i| ItemId::new(format!("I{i}"), "K")).collect(); + let chunks: Vec<&[ItemId]> = ids.chunks(3).collect(); + assert_eq!(chunks.len(), 3); + for chunk in &chunks { + let body = get_item_body(ItemShape::Message, chunk); + assert!(body.contains("")); + for id in *chunk { + assert!(body.contains(&format!("Id=\"{}\"", id.id))); + } + } +} + +#[test] +fn sync_folder_items_request_body_carries_state_and_max() { + let folder = FolderId::new("FID", "FCK"); + let body = sync_folder_items_body(&folder, "OPAQUE", 512); + assert!(body.contains("OPAQUE")); + assert!(body.contains("512")); + assert!(body.contains("NormalItems")); + assert!(body.contains("")); +} + +#[test] +fn get_attachment_body_carries_inline_request() { + let body = get_attachment_body(&["A1", "A2"]); + assert!(body.contains("")); + assert!(body.contains("")); + assert!(body.contains("true")); +} + +#[test] +fn http_500_with_server_busy_body_is_treated_as_fault_and_retried() { + let mut server = mockito::Server::new(); + let url = format!("{}/EWS/Exchange.asmx", server.url()); + let busy_body = format!( + "\ + soap:Serverbusy\ + ErrorServerBusy\ + 100\ + " + ); + let ok_body = envelope(&format!( + "\ + NoError\ + \ + " + )); + let _m1 = server + .mock("POST", "/EWS/Exchange.asmx") + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(busy_body) + .expect(1) + .create(); + let _m2 = server + .mock("POST", "/EWS/Exchange.asmx") + .with_status(200) + .with_header("content-type", TXT_XML) + .with_body(ok_body) + .create(); + + let c = client(3); + let body = find_folder_body( + FolderRef::Distinguished( + vandelay::exchange_ews::types::DistinguishedFolderId::MsgFolderRoot, + ), + Traversal::Deep, + ); + let r = c.call(&url, "FindFolder", &body).expect("should retry"); + let parsed = parse_find_folder_response(&r.body).unwrap(); + assert!(parsed.folders.is_empty()); +} + +#[test] +fn warning_response_class_is_treated_as_success_in_mock() { + let body = envelope(&format!( + "\ + \ + NoError\ + \ + " + )); + let r = parse_response_messages(body.as_bytes(), b"GetItemResponseMessage").unwrap(); + assert!(r[0].success); +} + +#[test] +fn default_namespace_envelope_parses_via_resolved_names() { + let body = "\ + \ + \ + \ + NoError\ + \ + Inbox\ + \ + \ + "; + let parsed = parse_find_folder_response(body.as_bytes()).unwrap(); + assert_eq!(parsed.folders.len(), 1); + assert_eq!(parsed.folders[0].folder_id.id, "D1"); +} + +#[test] +fn get_folder_messages_preserve_position_when_one_errors() { + let body = envelope(&format!( + "\ + \ + NoError\ + Inbox\ + \ + \ + ErrorAccessDenied\ + \ + \ + NoError\ + Drafts\ + \ + " + )); + let msgs = parse_response_messages(body.as_bytes(), b"GetFolderResponseMessage").unwrap(); + assert_eq!(msgs.len(), 3, "all three messages must be present"); + assert!(msgs[0].success); + assert!(!msgs[1].success); + assert!(msgs[2].success); +} diff --git a/tests/mock_exchange_graph.rs b/tests/mock_exchange_graph.rs new file mode 100644 index 0000000..7e03442 --- /dev/null +++ b/tests/mock_exchange_graph.rs @@ -0,0 +1,2066 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::sync::Once; + +use base64::Engine; +use mockito::{Matcher, Server}; +use serde_json::json; +use vandelay::exchange_graph::api::{Endpoints, collect_all_ids, paged_collect}; +use vandelay::exchange_graph::calendar_map::{EventType, classify_event_type, convert_event}; +use vandelay::exchange_graph::client::{Accept, GraphClient}; +use vandelay::exchange_graph::contact_map::convert_contact; +use vandelay::exchange_graph::error::GraphError; +use vandelay::exchange_graph::oauth::{ + DeviceCodeResponse, TokenResponse, parse_device_code_response, parse_token_response, + run_device_code_polling_against, +}; +use vandelay::exchange_graph::recurrence::convert_patterned_recurrence; +use vandelay::exchange_graph::retry::{HttpClass, classify_http_status}; +use vandelay::jmap::http::RetryPolicy; + +static INIT: Once = Once::new(); + +fn client_with_retries(retries: u32) -> GraphClient { + INIT.call_once(|| {}); + GraphClient::new("BEARER".to_owned(), RetryPolicy::new(retries), false) +} + +fn url_message_collection(server_url: &str, folder: &str, top: usize) -> String { + let e = Endpoints::for_me(server_url); + e.folder_messages_ids(folder, top) +} + +fn make_jwt(exp: u64, upn: &str) -> String { + use base64::engine::general_purpose::URL_SAFE_NO_PAD; + let header = URL_SAFE_NO_PAD.encode(b"{\"alg\":\"none\"}"); + let claims = format!(r#"{{"tid":"tenant-1","upn":"{upn}","exp":{exp}}}"#); + let payload = URL_SAFE_NO_PAD.encode(claims.as_bytes()); + format!("{header}.{payload}.") +} + +#[test] +fn http_status_classifier_matches_spec_table() { + assert_eq!(classify_http_status(200), HttpClass::Success); + assert_eq!(classify_http_status(401), HttpClass::Auth); + assert_eq!(classify_http_status(403), HttpClass::Auth); + assert_eq!(classify_http_status(404), HttpClass::Vanished); + assert_eq!(classify_http_status(410), HttpClass::Vanished); + assert_eq!(classify_http_status(429), HttpClass::Retryable); + assert_eq!(classify_http_status(503), HttpClass::Retryable); + assert_eq!(classify_http_status(504), HttpClass::Retryable); + assert_eq!(classify_http_status(507), HttpClass::Fatal); + assert_eq!(classify_http_status(400), HttpClass::Fatal); + assert_eq!(classify_http_status(405), HttpClass::Fatal); + assert_eq!(classify_http_status(422), HttpClass::Fatal); +} + +#[test] +fn device_code_response_parses_required_fields() { + let body = json!({ + "device_code": "ABCDEF", + "user_code": "BR4S-7XYZ", + "verification_uri": "https://microsoft.com/devicelogin", + "interval": 5, + "expires_in": 900, + "message": "Open the URL and enter the code." + }); + let parsed = parse_device_code_response(&body).unwrap(); + assert_eq!(parsed.device_code, "ABCDEF"); + assert_eq!(parsed.user_code, "BR4S-7XYZ"); + assert_eq!(parsed.interval, 5); + assert_eq!(parsed.expires_in, 900); +} + +#[test] +fn token_response_handles_pending_and_success() { + let pending = parse_token_response(400, &json!({"error": "authorization_pending"})); + assert!(matches!(pending, TokenResponse::Pending)); + let success = parse_token_response( + 200, + &json!({"access_token": make_jwt(9999999999, "alice@x.com"), "expires_in": 3599}), + ); + match success { + TokenResponse::Ok(t) => { + assert!(t.access_token.contains('.')); + assert_eq!(t.expires_in, Some(3599)); + assert_eq!(t.upn.as_deref(), Some("alice@x.com")); + } + other => panic!("expected Ok, got {other:?}"), + } +} + +#[test] +fn token_response_declined_and_bad_code() { + let declined = parse_token_response(400, &json!({"error": "authorization_declined"})); + assert!(matches!(declined, TokenResponse::Declined)); + let bad = parse_token_response(400, &json!({"error": "bad_verification_code"})); + assert!(matches!(bad, TokenResponse::BadCode)); +} + +#[test] +fn device_code_polling_loop_converges_on_success() { + let response = DeviceCodeResponse { + device_code: "DC".to_owned(), + user_code: "U".to_owned(), + verification_uri: "x".to_owned(), + interval: 0, + expires_in: 5, + message: None, + }; + let mut attempt = 0; + let token = run_device_code_polling_against( + "https://login.x/common", + "client-id", + &response, + |_endpoint, _body| { + attempt += 1; + if attempt < 3 { + Ok((400, json!({"error": "authorization_pending"}))) + } else { + Ok(( + 200, + json!({ + "access_token": make_jwt(9999999999, "alice@x"), + "expires_in": 3599 + }), + )) + } + }, + ) + .unwrap(); + assert_eq!(attempt, 3); + assert_eq!(token.upn.as_deref(), Some("alice@x")); +} + +#[test] +fn device_code_polling_loop_aborts_on_decline() { + let response = DeviceCodeResponse { + device_code: "DC".to_owned(), + user_code: "U".to_owned(), + verification_uri: "x".to_owned(), + interval: 0, + expires_in: 5, + message: None, + }; + let err = run_device_code_polling_against( + "https://login.x/common", + "client-id", + &response, + |_, _| Ok((400, json!({"error": "authorization_declined"}))), + ) + .unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("declined")); +} + +#[test] +fn pagination_collects_ids_across_three_pages_with_one_empty() { + let mut server = Server::new(); + let base = server.url(); + let next2 = format!("{base}/page2"); + let next3 = format!("{base}/page3"); + + let page1 = json!({ + "value": [ + {"id": "M1"}, + {"id": "M2"} + ], + "@odata.nextLink": next2 + }) + .to_string(); + let page2 = json!({ + "value": [], + "@odata.nextLink": next3 + }) + .to_string(); + let page3 = json!({ + "value": [ + {"id": "M3"} + ] + }) + .to_string(); + + let _m1 = server + .mock("GET", "/me/mailFolders/F1/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(page1) + .create(); + let _m2 = server + .mock("GET", "/page2") + .match_header("authorization", "Bearer BEARER") + .match_header("prefer", "IdType=\"ImmutableId\"") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(page2) + .create(); + let _m3 = server + .mock("GET", "/page3") + .match_header("authorization", "Bearer BEARER") + .match_header("prefer", "IdType=\"ImmutableId\"") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(page3) + .create(); + + let client = client_with_retries(0); + let url = url_message_collection(&base, "F1", 100); + let ids = collect_all_ids(&client, &url, &[]).unwrap(); + assert_eq!(ids, vec!["M1".to_owned(), "M2".to_owned(), "M3".to_owned()]); +} + +#[test] +fn pagination_terminates_when_no_next_link_present() { + let mut server = Server::new(); + let base = server.url(); + let body = json!({"value": [{"id": "A"}, {"id": "B"}]}).to_string(); + let _m = server + .mock("GET", "/me/mailFolders/F/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(body) + .create(); + let client = client_with_retries(0); + let url = url_message_collection(&base, "F", 100); + let mut pages = 0; + paged_collect(&client, &url, &[], |_| { + pages += 1; + Ok(()) + }) + .unwrap(); + assert_eq!(pages, 1); +} + +#[test] +fn header_redelivery_is_asserted_via_mockito_match() { + let mut server = Server::new(); + let base = server.url(); + let next = format!("{base}/page-two-different-segment"); + let page1 = json!({ + "value": [{"id": "OnlyOnPageOne"}], + "@odata.nextLink": next + }) + .to_string(); + let page2 = json!({"value": [{"id": "OnlyOnPageTwo"}]}).to_string(); + let _m1 = server + .mock("GET", "/me/mailFolders/F/messages?$top=100&$select=id") + .match_header("prefer", "IdType=\"ImmutableId\"") + .match_header("authorization", "Bearer BEARER") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(page1) + .create(); + let _m2 = server + .mock("GET", "/page-two-different-segment") + .match_header("prefer", "IdType=\"ImmutableId\"") + .match_header("authorization", "Bearer BEARER") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(page2) + .create(); + let c = client_with_retries(0); + let url = url_message_collection(&base, "F", 100); + let ids = collect_all_ids(&c, &url, &[]).unwrap(); + assert_eq!(ids.len(), 2); +} + +#[test] +fn case_sensitive_immutable_id_round_trip() { + let mut server = Server::new(); + let id = "AAkAAGFsaWNlAAA="; + let body = json!({"value": [{"id": id}]}).to_string(); + let _m = server + .mock("GET", "/me/mailFolders/F/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(body) + .create(); + let base = server.url(); + let client = client_with_retries(0); + let url = url_message_collection(&base, "F", 100); + let ids = collect_all_ids(&client, &url, &[]).unwrap(); + assert_eq!(ids[0], id); + assert!(ids[0].contains('=')); +} + +#[test] +fn rate_limit_with_retry_after_is_honoured_then_succeeds() { + let mut server = Server::new(); + let path = "/me?$select=id,userPrincipalName,displayName,mail"; + let _m1 = server + .mock("GET", path) + .with_status(429) + .with_header("retry-after", "1") + .with_header("content-type", "application/json") + .with_body(r#"{"error":{"code":"TooManyRequests"}}"#) + .expect(1) + .create(); + let _m2 = server + .mock("GET", path) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .expect(1) + .create(); + let base = server.url(); + let client = client_with_retries(3); + let started = std::time::Instant::now(); + let resp = client + .get( + &format!("{base}/me?$select=id,userPrincipalName,displayName,mail"), + Accept::Json, + ) + .unwrap(); + let elapsed = started.elapsed(); + assert_eq!(resp.status, 200); + assert!(elapsed >= std::time::Duration::from_millis(800)); + assert!(client.retries_observed() >= 1); + assert!(client.retry_after_sleeps() >= 1); +} + +#[test] +fn rate_limit_without_retry_after_uses_shared_escalation() { + let mut server = Server::new(); + let path = "/me?$select=id,userPrincipalName,displayName,mail"; + let _m1 = server + .mock("GET", path) + .with_status(429) + .with_header("content-type", "application/json") + .with_body(r#"{"error":{"code":"TooManyRequests"}}"#) + .expect(1) + .create(); + let _m2 = server + .mock("GET", path) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .expect(1) + .create(); + let base = server.url(); + let client = client_with_retries(3); + let r = client + .get( + &format!("{base}/me?$select=id,userPrincipalName,displayName,mail"), + Accept::Json, + ) + .unwrap(); + assert_eq!(r.status, 200); + assert!(client.retries_observed() >= 1); +} + +#[test] +fn service_unavailable_503_is_retried() { + let mut server = Server::new(); + let _m1 = server + .mock("GET", "/me") + .with_status(503) + .expect(1) + .create(); + let _m2 = server + .mock("GET", "/me") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"u","userPrincipalName":"a@b"}"#) + .expect(1) + .create(); + let base = server.url(); + let c = client_with_retries(3); + let r = c.get(&format!("{base}/me"), Accept::Json).unwrap(); + assert_eq!(r.status, 200); +} + +#[test] +fn http_404_on_per_item_get_is_treated_as_vanished() { + let mut server = Server::new(); + let _m = server + .mock("GET", "/me/messages/GONE/$value") + .with_status(404) + .with_body("gone") + .create(); + let base = server.url(); + let c = client_with_retries(0); + let url = format!("{base}/me/messages/GONE/$value"); + let err = c.get(&url, Accept::Text).unwrap_err(); + assert!(matches!(err, GraphError::Vanished)); +} + +#[test] +fn http_401_is_auth_error() { + let mut server = Server::new(); + let _m = server + .mock("GET", "/me") + .with_status(401) + .with_body("unauthenticated") + .create(); + let base = server.url(); + let c = client_with_retries(0); + let url = format!("{base}/me"); + let err = c.get(&url, Accept::Json).unwrap_err(); + assert!(matches!(err, GraphError::Auth(_))); +} + +#[test] +fn malformed_4xx_is_fatal() { + let mut server = Server::new(); + let _m = server + .mock("GET", "/me") + .with_status(422) + .with_body("malformed payload") + .create(); + let base = server.url(); + let c = client_with_retries(0); + let err = c.get(&format!("{base}/me"), Accept::Json).unwrap_err(); + assert!(matches!(err, GraphError::HttpStatus { status: 422, .. })); +} + +#[test] +fn mime_value_endpoint_returns_raw_rfc5322_bytes() { + let mut server = Server::new(); + let mime = "From: a@x\r\nTo: b@x\r\nSubject: hi\r\nDate: Tue, 27 May 2026 10:00:00 +0000\r\nMessage-ID: \r\n\r\nbody"; + let _m = server + .mock("GET", "/me/messages/MID/$value") + .match_header("accept", "text/plain") + .match_header("authorization", "Bearer BEARER") + .match_header("prefer", "IdType=\"ImmutableId\"") + .with_status(200) + .with_header("content-type", "text/plain") + .with_body(mime) + .create(); + let base = server.url(); + let c = client_with_retries(0); + let url = format!("{base}/me/messages/MID/$value"); + let resp = c.get(&url, Accept::Text).unwrap(); + assert_eq!(resp.body, mime.as_bytes()); +} + +#[test] +fn recurrence_daily_with_interval_round_trips() { + let pr = json!({ + "pattern": {"type": "daily", "interval": 2}, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["frequency"], "daily"); + assert_eq!(out["interval"], 2); +} + +#[test] +fn recurrence_weekly_with_days_of_week_and_count() { + let pr = json!({ + "pattern": { + "type": "weekly", + "interval": 1, + "daysOfWeek": ["monday", "wednesday", "friday"] + }, + "range": {"type": "numbered", "numberOfOccurrences": 12} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["frequency"], "weekly"); + assert_eq!(out["count"], 12); + assert_eq!(out["byDay"][0]["day"], "mo"); + assert_eq!(out["byDay"][2]["day"], "fr"); +} + +#[test] +fn recurrence_relative_monthly_emits_setpos() { + let pr = json!({ + "pattern": { + "type": "relativeMonthly", "interval": 1, + "daysOfWeek": ["thursday"], "index": "third" + }, + "range": {"type": "noEnd"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["frequency"], "monthly"); + assert_eq!(out["bySetPosition"][0], 3); + assert_eq!(out["byDay"][0]["day"], "th"); +} + +#[test] +fn recurrence_yearly_with_end_date() { + let pr = json!({ + "pattern": {"type": "absoluteYearly", "interval": 1, "month": 12, "dayOfMonth": 25}, + "range": {"type": "endDate", "endDate": "2030-12-25"} + }); + let out = convert_patterned_recurrence(&pr).unwrap(); + assert_eq!(out["frequency"], "yearly"); + assert_eq!(out["until"], "2030-12-25T23:59:59"); +} + +#[test] +fn occurrence_event_is_classified_for_skip() { + let occ = json!({"id": "occ-1", "type": "occurrence", "seriesMasterId": "master-1"}); + assert_eq!(classify_event_type(&occ), EventType::Occurrence); +} + +#[test] +fn exception_event_carries_series_master_id() { + let ex = json!({ + "id": "ex-1", + "iCalUId": "uid-1", + "type": "exception", + "seriesMasterId": "master-1", + "originalStart": "2026-06-15T10:00:00Z", + "subject": "Moved", + "start": {"dateTime": "2026-06-15T11:00:00.0000000", "timeZone": "UTC"}, + "end": {"dateTime": "2026-06-15T12:00:00.0000000", "timeZone": "UTC"} + }); + let conv = convert_event(&ex, None).unwrap(); + assert_eq!(conv.event_type, EventType::Exception); + assert_eq!(conv.series_master_id.as_deref(), Some("master-1")); + assert_eq!(conv.original_start.as_deref(), Some("2026-06-15T10:00:00Z")); +} + +#[test] +fn contact_minimal_yields_jscontact_card() { + let c = json!({ + "id": "graph-1", + "displayName": "Alice Liddell", + "givenName": "Alice", + "surname": "Liddell", + "emailAddresses": [{"name": "Alice", "address": "alice@x.com"}] + }); + let out = convert_contact(&c).unwrap(); + assert!(out.uid.starts_with("vandelay-graph-")); + assert_eq!(out.data["@type"], "Card"); + assert_eq!(out.data["name"]["full"], "Alice Liddell"); + let emails = out.data["emails"].as_object().unwrap(); + assert_eq!(emails["email-1"]["address"], "alice@x.com"); +} + +#[test] +fn integration_dry_run_against_mock_server_lists_three_surfaces() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let _calendars = server + .mock("GET", "/me/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"CAL1","name":"Calendar","isDefaultCalendar":true}]}"#) + .create(); + let _contact_folders = server + .mock("GET", "/me/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"CON1","displayName":"Contacts"}]}"#) + .create(); + let base = server.url(); + let common = vandelay::sync::CommonConfig { + archive: tempfile::NamedTempFile::new().unwrap().path().to_owned(), + threads: 2, + dry_run: true, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "alice@x.com"), + }, + api_base: base.clone(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: None, + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + let summary = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + let mailbox = summary + .per_type + .iter() + .find(|(t, _)| *t == "mailbox") + .map(|(_, c)| c.created) + .unwrap_or(0); + let calendar = summary + .per_type + .iter() + .find(|(t, _)| *t == "calendar") + .map(|(_, c)| c.created) + .unwrap_or(0); + let addressbook = summary + .per_type + .iter() + .find(|(t, _)| *t == "addressbook") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(mailbox, 1); + assert_eq!(calendar, 1); + assert_eq!(addressbook, 1); +} + +#[test] +fn integration_full_run_mail_only_imports_mime_via_value() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid-1","userPrincipalName":"alice@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let _well_known: Vec = [ + "inbox", + "drafts", + "sentitems", + "deleteditems", + "junkemail", + "archive", + ] + .iter() + .map(|name| { + let path = format!("/me/mailFolders/{name}?$select=id"); + server + .mock("GET", path.as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(if *name == "inbox" { + r#"{"id":"FMAIL"}"# + } else { + r#"{"id":"OTHER"}"# + }) + .create() + }) + .collect(); + let _ids = server + .mock("GET", "/me/mailFolders/FMAIL/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"MSG-1"}]}"#) + .create(); + let mime = "From: a@x\r\nTo: b@x\r\nSubject: hi\r\nDate: Tue, 27 May 2026 10:00:00 +0000\r\nMessage-ID: \r\n\r\nhello"; + let _value = server + .mock("GET", "/me/messages/MSG-1/$value") + .match_header("accept", "text/plain") + .with_status(200) + .with_header("content-type", "text/plain") + .with_body(mime) + .create(); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let archive_path = tmp.path().to_owned(); + let common = vandelay::sync::CommonConfig { + archive: archive_path.clone(), + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "alice@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + drop(tmp); + let summary = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + let emails = summary + .per_type + .iter() + .find(|(t, _)| *t == "email") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(emails, 1, "expected one email created, got {emails}"); +} + +#[test] +fn integration_full_run_is_convergent_on_second_invocation() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid-2","userPrincipalName":"alice@x.com"}"#) + .expect_at_least(2) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .expect_at_least(2) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .expect_at_least(2) + .create(); + for name in [ + "inbox", + "drafts", + "sentitems", + "deleteditems", + "junkemail", + "archive", + ] { + server + .mock("GET", format!("/me/mailFolders/{name}?$select=id").as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(if name == "inbox" { + r#"{"id":"FMAIL"}"# + } else { + r#"{"id":"OTHER"}"# + }) + .expect_at_least(2) + .create(); + } + let _ids = server + .mock("GET", "/me/mailFolders/FMAIL/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"MSG-A"}]}"#) + .expect_at_least(2) + .create(); + let mime = "From: a@x\r\nTo: b@x\r\nSubject: hi\r\nDate: Tue, 27 May 2026 10:00:00 +0000\r\nMessage-ID: \r\n\r\nbody"; + let _value = server + .mock("GET", "/me/messages/MSG-A/$value") + .with_status(200) + .with_header("content-type", "text/plain") + .with_body(mime) + .expect(1) + .create(); + + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let api_base = server.url(); + let make_config = || vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "alice@x.com"), + }, + api_base: api_base.clone(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + let make_common = |path: std::path::PathBuf| vandelay::sync::CommonConfig { + archive: path, + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + + let first = + vandelay::sync::import_exchange_graph::run(make_common(archive.clone()), make_config()) + .unwrap(); + let created_first = first + .per_type + .iter() + .find(|(t, _)| *t == "email") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(created_first, 1); + + let second = + vandelay::sync::import_exchange_graph::run(make_common(archive), make_config()).unwrap(); + let created_second = second + .per_type + .iter() + .find(|(t, _)| *t == "email") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(created_second, 0, "second run should not re-import"); +} + +#[test] +fn source_change_protection_refuses_a_different_account() { + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let conn = vandelay::db::init::open(&archive).unwrap(); + vandelay::db::sources::upsert_source( + &conn, + &vandelay::db::sources::SourceKey { + kind: "exchange_graph".to_owned(), + session_url: + "https://login.microsoftonline.com/common|https://graph.microsoft.com/v1.0" + .to_owned(), + account_id: "old-user-id".to_owned(), + }, + Some("old@x.com"), + "old@x.com", + ) + .unwrap(); + drop(conn); + + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"new-user-id","userPrincipalName":"new@x.com"}"#) + .create(); + + let common = vandelay::sync::CommonConfig { + archive, + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "new@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: None, + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + let err = vandelay::sync::import_exchange_graph::run(common, config).unwrap_err(); + assert!( + matches!(err, vandelay::error::Error::SourceChange(_)), + "expected SourceChange error, got {err:?}" + ); +} + +fn stub_well_known_folders(server: &mut Server, inbox_id: &str) { + for name in [ + "drafts", + "sentitems", + "deleteditems", + "junkemail", + "archive", + ] { + let body = format!(r#"{{"id":"OTHER-{name}"}}"#); + server + .mock("GET", format!("/me/mailFolders/{name}?$select=id").as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(body) + .expect_at_least(0) + .create(); + } + server + .mock("GET", "/me/mailFolders/inbox?$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(format!(r#"{{"id":"{inbox_id}"}}"#)) + .expect_at_least(0) + .create(); +} + +fn make_common(archive: std::path::PathBuf) -> vandelay::sync::CommonConfig { + vandelay::sync::CommonConfig { + archive, + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + } +} + +fn make_config( + api_base: String, + user_target: Option, + objects: Option>, +) -> vandelay::sync::import_exchange_graph::GraphImportConfig { + vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "alice@x.com"), + }, + api_base, + user_target, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects, + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + } +} + +#[test] +fn users_upn_routes_through_users_segment_not_me() { + let mut server = Server::new(); + let _resolve = server + .mock( + "GET", + Matcher::Regex(r"^/users/alice[@%][^/]*\?\$select=id".to_owned()), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"user-uuid-7","userPrincipalName":"alice@x.com"}"#) + .create(); + let _principal = server + .mock( + "GET", + Matcher::Regex(r"^/users/user-uuid-7\?\$select=id".to_owned()), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{"id":"user-uuid-7","userPrincipalName":"alice@x.com","displayName":"Alice"}"#, + ) + .create(); + let _folders = server + .mock( + "GET", + "/users/user-uuid-7/mailFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .expect_at_least(1) + .create(); + let _calendars = server + .mock("GET", "/users/user-uuid-7/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .expect_at_least(0) + .create(); + let _contact_folders = server + .mock("GET", "/users/user-uuid-7/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .expect_at_least(0) + .create(); + let base = server.url(); + let common = vandelay::sync::CommonConfig { + archive: tempfile::NamedTempFile::new().unwrap().path().to_owned(), + threads: 2, + dry_run: true, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let summary = vandelay::sync::import_exchange_graph::run( + common, + make_config(base, Some("alice@x.com".to_owned()), None), + ) + .unwrap(); + let mailbox = summary + .per_type + .iter() + .find(|(t, _)| *t == "mailbox") + .map(|(_, c)| c.created) + .unwrap_or(99); + assert_eq!(mailbox, 0); +} + +#[test] +fn series_master_with_exception_merges_into_recurrence_overrides() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock("GET", "/me/mailboxSettings?$select=timeZone") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"timeZone":"UTC"}"#) + .create(); + server + .mock("GET", "/me/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"CAL1","name":"Calendar","isDefaultCalendar":true}]}"#) + .create(); + server + .mock("GET", "/me/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + "/me/calendars/CAL1/events?$top=100&$select=id,type,seriesMasterId", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{"value":[ + {"id":"MASTER","type":"seriesMaster","iCalUId":"uid-master"}, + {"id":"EX","type":"exception","seriesMasterId":"MASTER","iCalUId":"uid-master"} + ]}"#, + ) + .create(); + server + .mock("GET", "/me/events/MASTER") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{ + "id":"MASTER", + "iCalUId":"uid-master", + "type":"seriesMaster", + "subject":"Weekly sync", + "start":{"dateTime":"2026-05-04T15:00:00.0000000","timeZone":"UTC"}, + "end":{"dateTime":"2026-05-04T16:00:00.0000000","timeZone":"UTC"}, + "recurrence":{ + "pattern":{"type":"weekly","interval":1,"daysOfWeek":["monday"]}, + "range":{"type":"noEnd"} + } + }"#, + ) + .create(); + server + .mock("GET", "/me/events/EX") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{ + "id":"EX", + "iCalUId":"uid-master", + "type":"exception", + "seriesMasterId":"MASTER", + "originalStart":"2026-05-11T15:00:00Z", + "subject":"Moved sync", + "start":{"dateTime":"2026-05-11T16:00:00.0000000","timeZone":"UTC"}, + "end":{"dateTime":"2026-05-11T17:00:00.0000000","timeZone":"UTC"} + }"#, + ) + .create(); + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let summary = vandelay::sync::import_exchange_graph::run( + make_common(archive.clone()), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Calendar, + vandelay::types::ObjectType::CalendarEvent, + ]), + ), + ) + .unwrap(); + let events = summary + .per_type + .iter() + .find(|(t, _)| *t == "calendarevent") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(events, 1, "exactly one master row, exception merged in"); + + let conn = vandelay::db::init::open(&archive).unwrap(); + let row: String = conn + .query_row("SELECT data FROM calendar_events", [], |row| row.get(0)) + .unwrap(); + let v: serde_json::Value = serde_json::from_str(&row).unwrap(); + let overrides = v + .get("recurrenceOverrides") + .and_then(|o| o.as_object()) + .expect("recurrenceOverrides present"); + assert!( + overrides.contains_key("2026-05-11T15:00:00"), + "override keyed by originalStart as LocalDateTime; got keys: {:?}", + overrides.keys().collect::>() + ); + let override_data = &overrides["2026-05-11T15:00:00"]; + assert_eq!(override_data["title"], "Moved sync"); +} + +#[test] +fn occurrence_event_is_skipped_no_row_no_id_mapping() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock("GET", "/me/mailboxSettings?$select=timeZone") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"timeZone":"UTC"}"#) + .create(); + server + .mock("GET", "/me/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"CAL1","name":"Calendar","isDefaultCalendar":true}]}"#) + .create(); + server + .mock("GET", "/me/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + "/me/calendars/CAL1/events?$top=100&$select=id,type,seriesMasterId", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{"value":[ + {"id":"OCC","type":"occurrence","seriesMasterId":"MASTER"} + ]}"#, + ) + .create(); + let occ_get = server + .mock("GET", "/me/events/OCC") + .with_status(500) + .with_body("MUST NOT BE CALLED") + .expect(0) + .create(); + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let summary = vandelay::sync::import_exchange_graph::run( + make_common(archive.clone()), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Calendar, + vandelay::types::ObjectType::CalendarEvent, + ]), + ), + ) + .unwrap(); + let events = summary + .per_type + .iter() + .find(|(t, _)| *t == "calendarevent") + .map(|(_, c)| c.created) + .unwrap_or(99); + assert_eq!(events, 0, "occurrence must not be fetched or stored"); + occ_get.assert(); + + let conn = vandelay::db::init::open(&archive).unwrap(); + let count: i64 = conn + .query_row("SELECT COUNT(*) FROM calendar_events", [], |row| row.get(0)) + .unwrap(); + assert_eq!(count, 0); +} + +#[test] +fn hidden_mail_folder_has_is_subscribed_zero() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{"value":[ + {"id":"FVISIBLE","displayName":"Inbox","isHidden":false}, + {"id":"FHIDDEN","displayName":"SearchFolders","isHidden":true} + ]}"#, + ) + .create(); + server + .mock( + "GET", + "/me/mailFolders/FVISIBLE/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + "/me/mailFolders/FHIDDEN/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + "/me/mailFolders/FVISIBLE/messages?$top=100&$select=id", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + "/me/mailFolders/FHIDDEN/messages?$top=100&$select=id", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + stub_well_known_folders(&mut server, "FVISIBLE"); + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let _ = vandelay::sync::import_exchange_graph::run( + make_common(archive.clone()), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + ), + ) + .unwrap(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let visible: i64 = conn + .query_row( + "SELECT is_subscribed FROM mailboxes WHERE name = 'Inbox'", + [], + |row| row.get(0), + ) + .unwrap(); + let hidden: i64 = conn + .query_row( + "SELECT is_subscribed FROM mailboxes WHERE name = 'SearchFolders'", + [], + |row| row.get(0), + ) + .unwrap(); + assert_eq!(visible, 1); + assert_eq!(hidden, 0); +} + +#[test] +fn well_known_folder_probes_assign_jmap_roles() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{"value":[ + {"id":"INBOX-ID","displayName":"Inbox","isHidden":false}, + {"id":"DRAFTS-ID","displayName":"Drafts","isHidden":false}, + {"id":"SENT-ID","displayName":"Sent Items","isHidden":false}, + {"id":"TRASH-ID","displayName":"Deleted Items","isHidden":false}, + {"id":"JUNK-ID","displayName":"Junk Email","isHidden":false}, + {"id":"ARCHIVE-ID","displayName":"Archive","isHidden":false} + ]}"#, + ) + .create(); + for fid in [ + "INBOX-ID", + "DRAFTS-ID", + "SENT-ID", + "TRASH-ID", + "JUNK-ID", + "ARCHIVE-ID", + ] { + server + .mock( + "GET", + format!("/me/mailFolders/{fid}/childFolders?$top=100&includeHiddenFolders=true") + .as_str(), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock( + "GET", + format!("/me/mailFolders/{fid}/messages?$top=100&$select=id").as_str(), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + } + let pairs = [ + ("inbox", "INBOX-ID"), + ("drafts", "DRAFTS-ID"), + ("sentitems", "SENT-ID"), + ("deleteditems", "TRASH-ID"), + ("junkemail", "JUNK-ID"), + ("archive", "ARCHIVE-ID"), + ]; + for (name, id) in pairs { + server + .mock("GET", format!("/me/mailFolders/{name}?$select=id").as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(format!(r#"{{"id":"{id}"}}"#)) + .create(); + } + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let _ = vandelay::sync::import_exchange_graph::run( + make_common(archive.clone()), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + ), + ) + .unwrap(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let mut stmt = conn + .prepare("SELECT name, role FROM mailboxes ORDER BY name") + .unwrap(); + let rows: Vec<(String, Option)> = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) + .unwrap() + .map(Result::unwrap) + .collect(); + let by_name: std::collections::HashMap> = rows.into_iter().collect(); + assert_eq!(by_name["Inbox"].as_deref(), Some("inbox")); + assert_eq!(by_name["Drafts"].as_deref(), Some("drafts")); + assert_eq!(by_name["Sent Items"].as_deref(), Some("sent")); + assert_eq!(by_name["Deleted Items"].as_deref(), Some("trash")); + assert_eq!(by_name["Junk Email"].as_deref(), Some("junk")); + assert_eq!(by_name["Archive"].as_deref(), Some("archive")); +} + +#[test] +fn attachment_message_does_not_trigger_attachments_endpoint() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .create(); + server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + stub_well_known_folders(&mut server, "FMAIL"); + server + .mock("GET", "/me/mailFolders/FMAIL/messages?$top=100&$select=id") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"MSG-ATT"}]}"#) + .create(); + let mime_with_attachment = "From: a@x\r\nTo: b@x\r\nSubject: with attachment\r\n\ +Date: Tue, 27 May 2026 10:00:00 +0000\r\nMessage-ID: \r\n\ +Content-Type: multipart/mixed; boundary=\"X\"\r\n\r\n\ +--X\r\nContent-Type: text/plain\r\n\r\nbody\r\n\ +--X\r\nContent-Type: application/octet-stream\r\nContent-Disposition: attachment; filename=\"a.bin\"\r\nContent-Transfer-Encoding: base64\r\n\r\n\ +SGVsbG8=\r\n--X--\r\n"; + server + .mock("GET", "/me/messages/MSG-ATT/$value") + .with_status(200) + .with_header("content-type", "text/plain") + .with_body(mime_with_attachment) + .create(); + let no_attachments = server + .mock( + "GET", + Matcher::Regex(r"^/me/messages/[^/]+/attachments".to_owned()), + ) + .with_status(500) + .with_body("MUST NOT BE CALLED") + .expect(0) + .create(); + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let summary = vandelay::sync::import_exchange_graph::run( + make_common(archive), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + ), + ) + .unwrap(); + let emails = summary + .per_type + .iter() + .find(|(t, _)| *t == "email") + .map(|(_, c)| c.created) + .unwrap_or(0); + assert_eq!(emails, 1); + no_attachments.assert(); +} + +#[test] +fn event_get_carries_outlook_timezone_and_body_content_type_prefer() { + let mut server = Server::new(); + server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid","userPrincipalName":"alice@x.com"}"#) + .create(); + server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + server + .mock("GET", "/me/mailboxSettings?$select=timeZone") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"timeZone":"Pacific Standard Time"}"#) + .create(); + server + .mock("GET", "/me/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"CAL1","name":"Calendar"}]}"#) + .create(); + server + .mock("GET", "/me/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let _events_list = server + .mock( + "GET", + "/me/calendars/CAL1/events?$top=100&$select=id,type,seriesMasterId", + ) + .match_header( + "prefer", + Matcher::AllOf(vec![ + Matcher::Regex(r#"outlook\.timezone="UTC""#.to_owned()), + Matcher::Regex(r#"IdType="ImmutableId""#.to_owned()), + ]), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"EVT","type":"singleInstance"}]}"#) + .expect(1) + .create(); + let _event_get = server + .mock("GET", "/me/events/EVT") + .match_header( + "prefer", + Matcher::AllOf(vec![ + Matcher::Regex(r#"outlook\.body-content-type="text""#.to_owned()), + Matcher::Regex(r#"outlook\.timezone="UTC""#.to_owned()), + Matcher::Regex(r#"IdType="ImmutableId""#.to_owned()), + ]), + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + r#"{ + "id":"EVT","iCalUId":"uid-evt","type":"singleInstance", + "subject":"Test","start":{"dateTime":"2026-05-27T10:00:00.0000000","timeZone":"UTC"}, + "end":{"dateTime":"2026-05-27T11:00:00.0000000","timeZone":"UTC"} + }"#, + ) + .expect(1) + .create(); + let base = server.url(); + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let _ = vandelay::sync::import_exchange_graph::run( + make_common(archive), + make_config( + base, + None, + Some(vec![ + vandelay::types::ObjectType::Calendar, + vandelay::types::ObjectType::CalendarEvent, + ]), + ), + ) + .unwrap(); +} + +#[test] +fn graph_client_retries_after_401_when_bearer_is_swapped() { + let mut server = Server::new(); + let _m1 = server + .mock("GET", "/me") + .match_header("authorization", "Bearer EXPIRED") + .with_status(401) + .with_body("expired") + .expect(1) + .create(); + let _m2 = server + .mock("GET", "/me") + .match_header("authorization", "Bearer FRESH") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"u","userPrincipalName":"a@b"}"#) + .expect(1) + .create(); + let base = server.url(); + let client = GraphClient::new("EXPIRED".to_owned(), RetryPolicy::new(0), false); + let url = format!("{base}/me"); + let err = client.get(&url, Accept::Json).unwrap_err(); + assert!(matches!(err, GraphError::Auth(_))); + client.set_bearer("FRESH".to_owned()); + let resp = client.get(&url, Accept::Json).unwrap(); + assert_eq!(resp.status, 200); +} + +#[test] +fn full_run_records_graph_id_in_sync_id_exchange_graph_with_padding() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid-pad","userPrincipalName":"pad@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"AAkA-Padded==","displayName":"Inbox","isHidden":false}]}"#) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/AAkA-Padded%3D%3D/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + for name in [ + "inbox", + "drafts", + "sentitems", + "deleteditems", + "junkemail", + "archive", + ] { + server + .mock("GET", format!("/me/mailFolders/{name}?$select=id").as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"OTHER"}"#) + .create(); + } + let _ids = server + .mock( + "GET", + "/me/mailFolders/AAkA-Padded%3D%3D/messages?$top=100&$select=id", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let archive = tmp.path().to_owned(); + let common = vandelay::sync::CommonConfig { + archive: archive.clone(), + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "pad@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: Some(vec![vandelay::types::ObjectType::Mailbox]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + drop(tmp); + let _ = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let (graph_id, type_name): (String, String) = conn + .query_row( + "SELECT graph_id, type_name FROM sync_id_exchange_graph", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + assert_eq!( + graph_id, "AAkA-Padded==", + "case-sensitive padded id round-trips verbatim" + ); + assert_eq!(type_name, "mailbox"); + let account_id: String = conn + .query_row( + "SELECT account_id FROM sources WHERE kind = 'exchange_graph'", + [], + |row| row.get(0), + ) + .unwrap(); + assert_eq!( + account_id, "uid-pad", + "primary kind uses directory id verbatim" + ); +} + +#[test] +fn archive_mailbox_kind_encodes_synthetic_suffix_in_account_id() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid-arch","userPrincipalName":"a@x.com"}"#) + .create(); + let _root = server + .mock( + "GET", + "/me/mailFolders/archive/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let archive = tmp.path().to_owned(); + let common = vandelay::sync::CommonConfig { + archive: archive.clone(), + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "a@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Archive, + objects: Some(vec![vandelay::types::ObjectType::Mailbox]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + drop(tmp); + let _ = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let account_id: String = conn + .query_row( + "SELECT account_id FROM sources WHERE kind = 'exchange_graph'", + [], + |row| row.get(0), + ) + .unwrap(); + assert_eq!(account_id, "uid-arch#archive"); +} + +#[test] +fn allow_source_change_permits_overwriting_a_different_account() { + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let conn = vandelay::db::init::open(&archive).unwrap(); + vandelay::db::sources::upsert_source( + &conn, + &vandelay::db::sources::SourceKey { + kind: "exchange_graph".to_owned(), + session_url: "https://login.microsoftonline.com/common|https://x.example".to_owned(), + account_id: "different-uid".to_owned(), + }, + Some("old@x.com"), + "old@x.com", + ) + .unwrap(); + drop(conn); + + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"new-uid","userPrincipalName":"new@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + + let common = vandelay::sync::CommonConfig { + archive: archive.clone(), + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "new@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: Some(vec![vandelay::types::ObjectType::Mailbox]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: true, + }; + let result = vandelay::sync::import_exchange_graph::run(common, config); + assert!( + result.is_ok(), + "--allow-source-change must permit a different account, got {result:?}" + ); +} + +#[test] +fn dry_run_makes_no_per_item_get_and_no_sqlite_writes() { + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"u","userPrincipalName":"a@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let _calendars = server + .mock("GET", "/me/calendars?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let _contacts = server + .mock("GET", "/me/contactFolders?$top=100") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + let no_messages = server + .mock( + "GET", + Matcher::Regex(r"^/me/mailFolders/.*/messages".to_owned()), + ) + .expect(0) + .create(); + let no_well_known = server + .mock( + "GET", + Matcher::Regex(r"^/me/mailFolders/[a-z]+\?".to_owned()), + ) + .expect(0) + .create(); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let archive = tmp.path().to_owned(); + let common = vandelay::sync::CommonConfig { + archive: archive.clone(), + threads: 2, + dry_run: true, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "a@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: None, + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: false, + }; + drop(tmp); + let _ = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + no_messages.assert(); + no_well_known.assert(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let sources: i64 = conn + .query_row("SELECT count(*) FROM sources", [], |row| row.get(0)) + .unwrap(); + assert_eq!(sources, 0, "dry-run must not write the sources row"); + let mailboxes: i64 = conn + .query_row("SELECT count(*) FROM mailboxes", [], |row| row.get(0)) + .unwrap(); + assert_eq!(mailboxes, 0, "dry-run must not insert mailbox rows"); +} + +#[test] +fn refresh_access_token_failure_message_carries_status() { + let other = parse_token_response(401, &json!({"error": "invalid_grant"})); + match other { + TokenResponse::Other { error, .. } => assert_eq!(error, "invalid_grant"), + v => panic!("expected Other for invalid_grant, got {v:?}"), + } +} + +#[test] +fn token_response_expired_recognised() { + let r = parse_token_response(400, &json!({"error": "expired_token"})); + assert!(matches!(r, TokenResponse::Expired)); +} + +#[test] +fn token_response_bad_code_recognised() { + let r = parse_token_response(400, &json!({"error": "bad_verification_code"})); + assert!(matches!(r, TokenResponse::BadCode)); +} + +#[test] +fn folder_enumeration_failure_skips_vanished_deletion() { + let archive = tempfile::NamedTempFile::new().unwrap().path().to_owned(); + let setup_conn = vandelay::db::init::open(&archive).unwrap(); + let sid = vandelay::db::sources::upsert_source( + &setup_conn, + &vandelay::db::sources::SourceKey { + kind: "exchange_graph".to_owned(), + session_url: "stale".to_owned(), + account_id: "stale-uid".to_owned(), + }, + Some("stale@x.com"), + "stale@x.com", + ) + .unwrap(); + setup_conn + .execute( + "INSERT INTO mailboxes (id, name, sort_order, is_subscribed) VALUES (42, 'Inbox', 0, 1)", + [], + ) + .unwrap(); + vandelay::db::exchange_graph_ids::insert( + &setup_conn, + sid, + vandelay::db::exchange_graph_ids::MAILBOX, + "FMAIL", + 42, + ) + .unwrap(); + setup_conn + .execute( + "INSERT INTO blobs (hash, data) VALUES (x'aa', x'68656c6c6f')", + [], + ) + .unwrap(); + let blob_id: i64 = setup_conn + .query_row("SELECT id FROM blobs WHERE hash = x'aa'", [], |r| r.get(0)) + .unwrap(); + setup_conn + .execute( + "INSERT INTO emails (id, blob_id, received_at, mailbox_ids, keywords, message_match) + VALUES (1, ?1, '2024-01-01T00:00:00Z', '[42]', '[]', '[]')", + rusqlite::params![blob_id], + ) + .unwrap(); + vandelay::db::exchange_graph_ids::insert( + &setup_conn, + sid, + vandelay::db::exchange_graph_ids::EMAIL, + "MSG-LOCAL", + 1, + ) + .unwrap(); + setup_conn + .execute( + "UPDATE sources SET kind = 'exchange_graph', + session_url = ?1, + account_id = 'uid-flake' + WHERE id = ?2", + rusqlite::params![ + "https://login.microsoftonline.com/common|".to_owned() + + "https://graph.microsoft.com/v1.0", + sid + ], + ) + .unwrap(); + drop(setup_conn); + + let mut server = Server::new(); + let _principal = server + .mock("GET", Matcher::Regex(r"^/me\?\$select=id".to_owned())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"uid-flake","userPrincipalName":"flake@x.com"}"#) + .create(); + let _folders = server + .mock("GET", "/me/mailFolders?$top=100&includeHiddenFolders=true") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[{"id":"FMAIL","displayName":"Inbox","isHidden":false}]}"#) + .create(); + let _children = server + .mock( + "GET", + "/me/mailFolders/FMAIL/childFolders?$top=100&includeHiddenFolders=true", + ) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"value":[]}"#) + .create(); + for name in [ + "inbox", + "drafts", + "sentitems", + "deleteditems", + "junkemail", + "archive", + ] { + server + .mock("GET", format!("/me/mailFolders/{name}?$select=id").as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"id":"FMAIL"}"#) + .create(); + } + let _enum_fail = server + .mock("GET", "/me/mailFolders/FMAIL/messages?$top=100&$select=id") + .with_status(503) + .with_body("transient outage") + .create(); + + let common = vandelay::sync::CommonConfig { + archive: archive.clone(), + threads: 2, + dry_run: false, + max_retries: 0, + allow_invalid_certs: false, + logger: vandelay::logging::Logger::new(0), + }; + let config = vandelay::sync::import_exchange_graph::GraphImportConfig { + auth: vandelay::sync::import_exchange_graph::GraphAuth::PreAcquired { + token: make_jwt(9999999999, "flake@x.com"), + }, + api_base: server.url(), + user_target: None, + mailbox_kind: vandelay::exchange_graph::types::MailboxKind::Primary, + objects: Some(vec![ + vandelay::types::ObjectType::Mailbox, + vandelay::types::ObjectType::Email, + ]), + event_body_format: vandelay::exchange_graph::types::EventBodyFormat::Text, + graph_connections: 2, + top: 100, + allow_source_change: true, + }; + let _ = vandelay::sync::import_exchange_graph::run(common, config).unwrap(); + let conn = vandelay::db::init::open(&archive).unwrap(); + let remaining: i64 = conn + .query_row("SELECT count(*) FROM emails WHERE id = 1", [], |row| { + row.get(0) + }) + .unwrap(); + assert_eq!( + remaining, 1, + "transient folder-enumeration failure must NOT delete the locally-known message" + ); +} diff --git a/tests/mock_imap.rs b/tests/mock_imap.rs new file mode 100644 index 0000000..12b21ae --- /dev/null +++ b/tests/mock_imap.rs @@ -0,0 +1,1596 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpListener, TcpStream}; +use std::path::PathBuf; +use std::sync::Mutex; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::thread; +use std::time::Duration; + +use rusqlite::Connection; +use vandelay::db; +use vandelay::imap::client::{ConnectMode, ImapClient}; +use vandelay::imap::error::ImapError; +use vandelay::imap::transport::Connector; +use vandelay::logging::Logger; +use vandelay::sync::CommonConfig; +use vandelay::sync::import_imap::{ImapAuth, ImapImportConfig, run}; + +type Script = Box std::io::Result<()> + Send + 'static>; + +struct MockImap { + addr: String, + port: u16, + _thread: thread::JoinHandle<()>, +} + +struct MockConn { + reader: BufReader, + writer: TcpStream, +} + +impl MockConn { + fn write_line(&mut self, s: &str) -> std::io::Result<()> { + self.writer.write_all(s.as_bytes())?; + self.writer.write_all(b"\r\n")?; + self.writer.flush() + } + + fn write_raw(&mut self, bytes: &[u8]) -> std::io::Result<()> { + self.writer.write_all(bytes)?; + self.writer.flush() + } + + fn read_command(&mut self) -> std::io::Result<(String, String)> { + let mut line = String::new(); + let n = self.reader.read_line(&mut line)?; + if n == 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "client closed", + )); + } + let trimmed = line.trim_end_matches(['\r', '\n']); + let (tag, rest) = trimmed + .split_once(' ') + .map(|(t, r)| (t.to_owned(), r.to_owned())) + .unwrap_or_else(|| (trimmed.to_owned(), String::new())); + Ok((tag, rest)) + } +} + +impl MockImap { + fn start(handler: H) -> MockImap + where + H: FnOnce(&mut MockConn) -> std::io::Result<()> + Send + 'static, + { + Self::start_scripts(vec![Box::new(handler)]) + } + + fn start_scripts(scripts: Vec

IUY|sO7oa7sU-S+Y$e$pv@wH-+w|gWR3ww0 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%0D%&%e{Z;dEGFrSI|mo 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