Compare commits

..
250 Commits
Author SHA1 Message Date
Ludovic Marcotte 3894d6f9fa Release preparation 2017-02-14 08:38:39 -05:00
InverseBot 07720563b9 (js/css) Update generated files 2017-02-14 01:16:42 -05:00
Francis Lachapelle 5ceafa5005 (js) Localize mail, phone, url and address types
Fixes #4030
2017-02-13 22:52:03 -05:00
Ludovic Marcotte 24da483e2a Updated NEWS 2017-02-09 14:42:42 -05:00
Ludovic Marcotte 9434e3882e (feat) new sogo-tool checkup command to make sure user's data is sane 2017-02-09 14:39:24 -05:00
Ludovic Marcotte 7d1dd4ec76 Updated NEWS 2017-02-09 08:57:56 -05:00
Ludovic Marcotte 54eedb3363 (fix) moved bcc handling code to the generic mail sending method 2017-02-09 08:56:02 -05:00
InverseBot e9ea104a94 (js/css) Update generated files 2017-02-09 01:29:10 -05:00
InverseBot 99277421a1 (js/css) Update generated files 2017-02-08 01:27:36 -05:00
Ludovic Marcotte 3d5c0be441 Updated NEWS 2017-02-03 15:27:39 -05:00
Ludovic Marcotte dd8cccdafc (fix) correctly handle Canceled appointments 2017-02-03 15:24:42 -05:00
InverseBot dcfed38fc7 (js/css) Update generated files 2017-02-03 01:25:57 -05:00
InverseBot b58ec19625 (js/css) Update generated files 2017-02-02 01:24:16 -05:00
Francis Lachapelle d90533399d (css) Keep the fab button visible 2017-02-01 21:33:17 -05:00
Ludovic Marcotte 4db1f8185a Updated NEWS 2017-02-01 13:06:03 -05:00
Ludovic Marcotte 50d8975f9d (fix) improve EAS parameters parsing (fixes #4003) 2017-02-01 13:03:27 -05:00
Francis Lachapelle b412788e39 Make sure to refer to filenames when executing zip
Fixes #4015
2017-02-01 11:56:44 -05:00
Ludovic Marcotte 15b277fa8e (fix) cache sogo_folder_info records and avoid phantom subscriptions (fixes #4010) 2017-01-31 15:59:58 -05:00
InverseBot d159b191a6 (js/css) Update generated files 2017-01-31 01:12:42 -05:00
Francis Lachapelle 539a6c3beb Fix public URL with special characters
Fixes #3993
2017-01-30 21:56:33 -05:00
Francis Lachapelle abccdd304f (js) Ignore mouse events in scrollbars of Month
Firefox is bubbling the mouse down event on scrollbars so it has to be
ignored for proper drag'n'drop.

Fixes #3990.
2017-01-30 15:52:07 -05:00
Francis Lachapelle 4143a23afd Add Hebrew translation (cont'd) 2017-01-30 07:51:36 -05:00
Francis Lachapelle a020286780 Merge pull request #232 from rfrei/patch-1
Allow write to multiple contacts using 'to', 'cc' or 'bcc'.

Fixes #3548.
2017-01-30 07:46:54 -05:00
Steffen Müller 1b264ef6ba Change Strings according to RFC 5322
correction: for translations the strings in English.lproj should better be equal
2017-01-28 17:53:14 +01:00
Steffen Müller ef4e311a5a Change Strings according to RFC 5322 2017-01-28 17:53:05 +01:00
Steffen Müller 1d8caa4297 Change Strings according to RFC 5322 2017-01-28 17:22:14 +01:00
InverseBot 0500499d91 (js/css) Update generated files 2017-01-28 01:10:29 -05:00
Francis Lachapelle d289a441bb Fix condition when enabling Vacation message 2017-01-27 15:57:43 -05:00
Francis Lachapelle fcfbc50139 Add Hebrew translation (cont'd) 2017-01-27 12:58:31 -05:00
Francis Lachapelle 88bc1294cd (js) Fix saving the preferences with Mail disabled 2017-01-27 12:40:36 -05:00
Francis Lachapelle ac127a375f Improve ACLs handling of inactive users (cont'd) 2017-01-27 12:22:22 -05:00
Steffen Müller f90b2efadb simpler version 2017-01-27 16:52:58 +01:00
Steffen Müller c9a50aa057 write using 'to', 'cc' or 'bcc' with string parameter
write to multiple selected contacts using 'to', 'cc' or 'bcc'
Feauture Request #3548
2017-01-27 16:41:34 +01:00
Steffen Müller 621adb38c2 write using 'to', 'cc' or 'bcc' with string parameter
write to multiple selected contacts using 'to', 'cc' or 'bcc'
Feauture Request #3548
2017-01-27 16:37:13 +01:00
InverseBot 872afaec32 (js/css) Update generated files 2017-01-27 01:10:15 -05:00
Francis Lachapelle 892e6d6eb2 Add Hebrew translation 2017-01-26 21:40:52 -05:00
Steffen Müller ed62670a1a write using 'to', 'cc' or 'bcc'
write to multiple selected contacts using 'to', 'cc' or 'bcc'
Feauture Request #3548
2017-01-26 19:27:13 +01:00
Francis Lachapelle 7a6df34cb6 Update ChangeLog 2017-01-26 13:18:17 -05:00
Francis Lachapelle 556460982d Preparation for release 3.2.6a 2017-01-26 13:17:52 -05:00
Francis Lachapelle 2beb3dca6d (js/css) Update generated files 2017-01-26 13:16:19 -05:00
Francis Lachapelle d14b0b0b5e (js) Improve ACLs handling of inactive users 2017-01-26 12:48:21 -05:00
Steffen Müller 8efd01d55e write using 'to', 'cc' or 'bcc'
write to multiple selected contacts using 'to', 'cc' or 'bcc'
Feauture Request #3548
2017-01-26 18:29:07 +01:00
Francis Lachapelle 831c1bc980 Revert "(fix) properly honor "include in freebusy"
This reverts commit eed5679068.
2017-01-26 11:52:58 -05:00
Francis Lachapelle d277f2fe0f Update ChangeLog 2017-01-23 14:58:02 -05:00
Francis Lachapelle 0b9ca225d0 (css) Update default theme 2017-01-23 14:58:02 -05:00
Francis Lachapelle 04c256ab6c Preparation for release 3.2.6 2017-01-23 14:57:47 -05:00
InverseBot 3cbeba32d9 (js/css) Update generated files 2017-01-24 01:15:53 -05:00
Francis Lachapelle 1825b167dc (css,js) Update md to v1.1.1-master-2c9a5cc 2017-01-23 09:55:44 -05:00
Francis Lachapelle 214ce5ef86 (i18n) Update translations 2017-01-23 09:50:35 -05:00
Ludovic Marcotte 927f0c3220 Updated NEWS 2017-01-23 08:55:33 -05:00
Ludovic Marcotte 227cf582d9 (fix) touch objects when server-side changes occured 2017-01-23 08:53:41 -05:00
InverseBot 8cdc435945 (js/css) Update generated files 2017-01-21 01:28:13 -05:00
Francis Lachapelle 3efe0e8098 Add support for date extension of Sieve
Fixes #1530, #1949
2017-01-20 11:08:24 -05:00
Ludovic Marcotte d38d42b83a (fix) use login in domain 2017-01-20 09:25:02 -05:00
InverseBot 1f3a89b984 (js/css) Update generated files 2017-01-20 01:27:56 -05:00
Francis Lachapelle 0dea9c226d Allow to select multiple files when uploading
Fixes #3999
2017-01-19 15:41:21 -05:00
Francis Lachapelle db1cc24c20 (css) Improve display of HTML CKEditor 2017-01-19 14:47:11 -05:00
Francis Lachapelle d7d88e2620 (js) Compose new message in a popup window 2017-01-19 12:23:55 -05:00
Francis Lachapelle 4fca484ed0 Improve error handling in SOGoWebAppointmentFolder 2017-01-19 10:31:03 -05:00
Francis Lachapelle fcb79dc14b Improve error handling in MSExchangeFreeBusy class 2017-01-19 10:17:17 -05:00
Francis Lachapelle 41dbb88b64 (js) Expandable mail editor
Added "autogrow" plugin for CKEditor.
2017-01-19 07:05:52 -05:00
InverseBot bb97b57ad7 (js/css) Update generated files 2017-01-19 01:28:13 -05:00
Ludovic Marcotte 5300d4185d (fix) improve doc around SOGoTimeZone (fixes #3995) 2017-01-18 16:17:12 -05:00
Francis Lachapelle 26b69d25ee (js) Fix opening message in popup window 2017-01-18 15:03:29 -05:00
Ludovic Marcotte a3c056ea84 (fix) deep-search for text/calendar parts 2017-01-17 15:12:29 -05:00
Ludovic Marcotte e4b2539b04 (fix) strip attendees with no email and don't send IMIP when it's Outlook the EAS client 2017-01-17 12:40:41 -05:00
Francis Lachapelle fcb6542d06 (i18n) Update translations 2017-01-17 11:47:43 -05:00
Francis Lachapelle 174fcd5c31 (i18n) Update translations 2017-01-17 08:53:52 -05:00
InverseBot 2f6c5148aa (js/css) Update generated files 2017-01-17 01:30:41 -05:00
Francis Lachapelle 936b21b8c0 (css) Fix glitches in Month view with Firefox 2017-01-16 16:30:24 -05:00
Francis Lachapelle 5f2a6c53c4 (js) Fix pasting images in CKEditor
Fixes #3986
2017-01-16 15:31:12 -05:00
Francis Lachapelle ba12c14a1d (js) Update CKEditor to version 4.6.2
- Add Upload Image plugin

Fixes #3986
2017-01-13 16:11:10 -05:00
Ludovic Marcotte 4158f7555e Updated NEWS 2017-01-13 13:19:43 -05:00
Ludovic Marcotte 6e0f5b2254 (fix) return if not content is there, to avoid random crash 2017-01-13 13:16:01 -05:00
Francis Lachapelle 4a21a02f98 Fix rendering of forwarded HTML message with img
Fixes #3981 (cont'd)
2017-01-13 12:23:38 -05:00
Ludovic Marcotte 9d7d2575d3 (ix) remove alarms in IMIP responses (fixes #3925) 2017-01-12 15:23:09 -05:00
InverseBot 905859053c (js/css) Update generated files 2017-01-12 01:31:09 -05:00
Francis Lachapelle e5d0b0b0ca Fix rendering of forwarded HTML message with img
Fixes #3981 (partially)
2017-01-11 16:08:56 -05:00
Francis Lachapelle 589827a414 (html) Show locale codes beside language names 2017-01-11 14:20:43 -05:00
Francis Lachapelle 42ee981613 (js) Update Angular to branch 1.6.x 2017-01-11 14:20:43 -05:00
Francis Lachapelle 5985262e0f Improve WebDAV Python lib for integration tests 2017-01-11 14:20:42 -05:00
Francis Lachapelle c36fcd2902 Improve debugging output of recurrence calculator 2017-01-11 14:20:42 -05:00
Francis Lachapelle bce09f413f Update NEWS file 2017-01-11 14:20:42 -05:00
Francis Lachapelle 9477c1af16 (html) Precise EAS beside "Synchronize" option 2017-01-11 14:20:42 -05:00
InverseBot 8011d9093e (js/css) Update generated files 2017-01-11 01:26:52 -05:00
Francis Lachapelle 37334db0e3 Update ChangeLog 2017-01-10 09:42:57 -05:00
Francis Lachapelle 6637bde56d Preparation for release 3.2.5 2017-01-10 09:42:09 -05:00
Francis Lachapelle e27efc4d3a (css) Update default theme 2017-01-10 09:37:05 -05:00
Francis Lachapelle ff7b9a3725 (js/css) Update generated files 2017-01-10 09:33:43 -05:00
Francis Lachapelle 36c7ffcc3c (css,js) Update md to v1.1.1-master-64b2621 2017-01-10 09:16:04 -05:00
InverseBot 3912d2ab3e (js/css) Update generated files 2017-01-10 02:12:03 -05:00
Ludovic Marcotte 5e775ea4ce (fix) some more memory info 2017-01-09 16:09:09 -05:00
Ludovic Marcotte 841fdb96cc (fix) be a bit more verbose about memory usage 2017-01-09 16:03:32 -05:00
Francis Lachapelle 51ff746595 (js) Update CKEditor to version 4.6.1 2017-01-06 20:49:42 -05:00
Francis Lachapelle ef1f5a948f (i18n) Update translations 2017-01-06 20:34:37 -05:00
Francis Lachapelle 3560f0486e (js) Fix hotkeys with an active dialog in Mailer
Fixes #3983
2017-01-06 18:26:12 -05:00
Francis Lachapelle 327ebf00a4 Fix public URLs to Calendars
Fixes #3974
2017-01-06 16:49:00 -05:00
InverseBot abb1e1b447 (js/css) Update generated files 2017-01-07 01:28:44 -05:00
Francis Lachapelle 818233535f Improve error messages around draft attachments
(cont'd)
2017-01-06 16:17:05 -05:00
Francis Lachapelle 65e3fe978c Minor code cleanup 2017-01-06 16:15:26 -05:00
Francis Lachapelle 0216f19e55 Improve Installation Guide 2017-01-06 16:14:44 -05:00
Francis Lachapelle 8404de758b Improve error messages around draft attachments 2017-01-06 16:12:42 -05:00
Francis Lachapelle 5ec7ad54bb Fix error handling when draft is too big 2017-01-06 15:45:30 -05:00
Ludovic Marcotte be8e1373ea Updated NEWS 2017-01-06 16:01:52 -05:00
Ludovic Marcotte 9fbc84158a (fix) handle sha512-crypt with rounds 2017-01-06 15:52:19 -05:00
Ludovic Marcotte 071c1032c1 Updated NEWS 2017-01-06 14:34:43 -05:00
Ludovic Marcotte af8baf41cb (fix) correctly handle all identities during IMIP status update 2017-01-06 14:32:45 -05:00
Ludovic Marcotte 0b7ddf7ca4 (fix) small optimization over previous commit 2017-01-06 14:29:41 -05:00
Ludovic Marcotte 942ddcabe4 (fix) handle all identities during IMIP REPLY processing 2017-01-06 14:23:52 -05:00
Ludovic Marcotte bdd8254e02 (fix) make sure we compare the organizer's email only 2017-01-06 11:03:59 -05:00
Ludovic Marcotte ce0575517d (fix) make sure to bump LAST-MODIFIED to trigger all auto-scheduling code 2017-01-06 09:54:02 -05:00
Francis Lachapelle ea7934a73c Fix saving of inactive calendars
Fixes #3862, #3980
2017-01-06 06:42:12 -05:00
InverseBot 02fc147624 (js/css) Update generated files 2017-01-06 02:11:11 -05:00
Francis Lachapelle 02a1ad48fc (js) Fix computation of week number
Fixes #3973, 3976
2017-01-05 12:00:55 -05:00
Ludovic Marcotte e7e1283fb3 (fix) disabled bias computation for now 2017-01-05 09:27:16 -05:00
InverseBot d5e04be1df (js/css) Update generated files 2017-01-01 01:20:37 -05:00
Ludovic Marcotte e2ec8af0db Updated NEWS 2016-12-30 12:29:09 -05:00
Ludovic Marcotte 477c06a7ff (fix) fix events in floating time during CalDAV's PUT operation (fixes #2865) 2016-12-30 12:26:31 -05:00
Ludovic Marcotte 479b534857 (fix) properly save email alarms in the db (fixes #3949) 2016-12-30 10:31:29 -05:00
Ludovic Marcotte f6bcf26793 Updated NEWS 2016-12-30 10:00:58 -05:00
Ludovic Marcotte cdf6aaa53e (fix) provide correct error response to EAS client when sending big mails (fixes #3956) 2016-12-30 09:58:51 -05:00
Ludovic Marcotte 1a9b20519e Updated NEWS 2016-12-29 16:23:07 -05:00
Ludovic Marcotte 4f211678e9 (fix) handle semicolon in value during LDIF import (fixes #1760) 2016-12-29 16:21:30 -05:00
Ludovic Marcotte e578787acd Updated NEWS 2016-12-29 14:37:04 -05:00
Ludovic Marcotte 239dffa391 (fix) handle URI in vCard photos (fixes #2683) 2016-12-29 14:35:06 -05:00
Ludovic Marcotte 6cd3d90410 Updated NEWS 2016-12-29 14:14:39 -05:00
Ludovic Marcotte 93dfbc5a94 (feat) added photo support for LDIF import (fixes #1084) 2016-12-29 14:12:45 -05:00
Ludovic Marcotte 896f57f634 Updated NEWS 2016-12-29 10:53:44 -05:00
Ludovic Marcotte f48ab310d6 (fix) add support for "other" addresses (fixes #3966) 2016-12-29 10:52:32 -05:00
Ludovic Marcotte 5bc21e4f53 (fix) avoid trying to parse empty request (fixes #3969) 2016-12-29 09:55:20 -05:00
Ludovic Marcotte 8296123650 Updated NEWS 2016-12-28 10:25:31 -05:00
Ludovic Marcotte 2cebee42b7 (feat) added new SOGoMaximumMessageSizeLimit config parameter (fixes #3510)
Conflicts:

	SoObjects/Mailer/SOGoDraftObject.m
2016-12-28 10:23:19 -05:00
Ludovic Marcotte 7c7440c988 Updated NEWS 2016-12-23 11:38:39 -05:00
Ludovic Marcotte eed5679068 (fix) properly honor "include in freebusy" (fixes #3354) 2016-12-23 11:36:44 -05:00
Ludovic Marcotte f1dbb88303 Updated NEWS 2016-12-23 10:22:07 -05:00
Ludovic Marcotte 9cb6772ae8 (fix) do not decode from hex the event's UID (fixes #3965) 2016-12-23 10:19:46 -05:00
Ludovic Marcotte dceab8fd2a Updated NEWS 2016-12-23 10:06:15 -05:00
Ludovic Marcotte 7197b7eb41 (fix) handle delegation scenario with no SENT-BY set (fixes #3368) 2016-12-23 10:04:43 -05:00
InverseBot 18b502c39c (js/css) Update generated files 2016-12-23 02:10:39 -05:00
Ludovic Marcotte 94fc5d17db (fix) also use the filter when doing DN lookups (fixes #2253) 2016-12-22 14:26:27 -05:00
Ludovic Marcotte 8c075d2e1b (fix) comment out vars too 2016-12-22 09:41:39 -05:00
Ludovic Marcotte 278aeef864 (fix) disabled IMIP force sending for Lightning (fixes #3274) 2016-12-22 08:55:12 -05:00
Ludovic Marcotte ef69344a59 Updated NEWS 2016-12-21 11:45:41 -05:00
Ludovic Marcotte 457fccd5ac (fix) append .ics extension if not present when importing events (fixes #2308) 2016-12-21 11:43:08 -05:00
Ludovic Marcotte 1da36a3ef9 (fix) always force save events creation over EAS (fixes #3958) 2016-12-21 10:47:33 -05:00
Francis Lachapelle 7327ec21b2 Avoid duplicate emails in LDAP-based address book
Fixes #3941
2016-12-21 10:37:04 -05:00
Ludovic Marcotte 94c522ea5c Updated NEWS 2016-12-21 08:50:38 -05:00
Ludovic Marcotte 5c0229466e (feat) handle alarms for tasks (fixes #3964) 2016-12-21 08:49:05 -05:00
Ludovic Marcotte eb6d234c4b Updated NEWS 2016-12-20 13:17:19 -05:00
Ludovic Marcotte 06903f8376 (fix) use the source's domain if none is defined (fixes #3523) 2016-12-20 13:15:15 -05:00
Francis Lachapelle 596c4bb4c2 (js) Fix attached links in task viewer
Fixes #3963
2016-12-20 10:44:43 -05:00
Ludovic Marcotte 3d16283d3c (fix) avoid double encoding (fixes #3960) 2016-12-20 08:56:13 -05:00
InverseBot 31d2db51ad (js/css) Update generated files 2016-12-20 01:24:11 -05:00
Ludovic Marcotte abd0c0a744 (fix) sage-guard against empty rrule 2016-12-19 15:01:31 -05:00
Francis Lachapelle 1375237697 (js) Don't allow to create a list in a remote AB 2016-12-19 12:07:17 -05:00
Francis Lachapelle 4c78d81bf7 Formatting & Comments 2016-12-19 11:15:35 -05:00
Francis Lachapelle 2e9168080c (js) Fix freebusy initialization in event editor 2016-12-19 11:05:48 -05:00
Francis Lachapelle 1eb378c956 (js) Fix display of auto-reply (vacation) message
Fixes #3940
2016-12-19 10:25:04 -05:00
Francis Lachapelle e739ed46a3 (js) Fix saving monthly BYDAY recurrence
Fixes #3948
2016-12-19 09:51:09 -05:00
Francis Lachapelle 93f559a0df Remove unnecessary div in UIxTopnavToolbar.wox 2016-12-19 09:46:58 -05:00
InverseBot 13a4398c4d (js/css) Update generated files 2016-12-17 01:24:00 -05:00
Ludovic Marcotte 462d274497 Updated NEWS 2016-12-16 15:53:12 -05:00
Ludovic Marcotte ab1c699bc7 (feat) better support for RFC 6638 (schedule-agent) (fixes #2599) 2016-12-16 15:51:19 -05:00
Ludovic Marcotte 9882052e81 Updated NEWS 2016-12-16 10:12:40 -05:00
Ludovic Marcotte 3adbef5aa4 (fix) 30 mins offset in FB 2016-12-16 10:08:04 -05:00
Ludovic Marcotte 5ecf917e6b (fix) return a 404 for public user 2016-12-16 09:07:55 -05:00
InverseBot a02642cad9 (js/css) Update generated files 2016-12-16 01:09:03 -05:00
Ludovic Marcotte 92ba421dd1 Updated NEWS 2016-12-15 14:24:08 -05:00
Ludovic Marcotte ea940e645e (fix) return login page for unknown users (fixes #2135) 2016-12-15 14:22:26 -05:00
Francis Lachapelle 651c0fdf14 Load photos of LDAP-based address books in listing
Fixes #3942
2016-12-15 10:19:47 -05:00
InverseBot 15a3bf48d9 (js/css) Update generated files 2016-12-15 01:22:53 -05:00
Ludovic Marcotte 8493701408 Updated NEWS 2016-12-14 21:22:45 -05:00
Ludovic Marcotte f0085ef43e (fix) correctly block if fail count is within interval (fixes #2850) 2016-12-14 21:20:29 -05:00
Francis Lachapelle e0108088ee (js) Rename buttons of alarm toast
Fixes #3945
2016-12-14 16:39:21 -05:00
Francis Lachapelle 5591b9d423 (js) Switch on "remember username" when necessary 2016-12-14 16:33:21 -05:00
Ludovic Marcotte ef96c21334 Updated NEWS 2016-12-14 13:46:27 -05:00
Ludovic Marcotte d8eae96d63 (fix) properly skip folders we don't want (fixes #3943) 2016-12-14 13:42:22 -05:00
InverseBot f6acc3e984 (js/css) Update generated files 2016-12-11 01:19:01 -05:00
Ludovic Marcotte 340ddf0ae6 (fix) make sure we safely escape all chars 2016-12-09 10:45:44 -05:00
Ludovic Marcotte b0fcaeef86 (fix) removed old code 2016-12-09 10:32:42 -05:00
Ludovic Marcotte ec19a1290d (fix) use the organizer's alarm by default when accepting IMIP messages (fixes #3934) 2016-12-09 09:47:48 -05:00
InverseBot 384f1cb53f (js/css) Update generated files 2016-12-09 01:17:34 -05:00
Francis Lachapelle d0e0d5300a (js) Fix ordering of calendars
Fixes #3931
2016-12-08 10:17:25 -05:00
InverseBot 4b2b3693c5 (js/css) Update generated files 2016-12-08 01:18:39 -05:00
Francis Lachapelle 011fae8a65 Download attachments of a message as a zip archive 2016-12-07 14:38:33 -05:00
Ludovic Marcotte 2a2ebd553e (test) handle sanitization before passing data to libxml 2016-12-06 14:05:27 -05:00
Francis Lachapelle 0038e4bc60 (js) Disable HTML entities in CKEditor 2016-12-06 10:08:02 -05:00
InverseBot e053f12e78 (js/css) Update generated files 2016-12-06 01:17:41 -05:00
Francis Lachapelle c663f7ab4c Prevent using localhost on auxiliary IMAP accounts 2016-12-05 16:12:16 -05:00
Ludovic Marcotte 68ddcd7045 (fix) prevent using localhost on additional IMAP accounts 2016-12-05 14:51:33 -05:00
InverseBot ffd69337f6 (js/css) Update generated files 2016-12-03 02:04:11 -05:00
Francis Lachapelle ba5bd65ea3 Fix support for SOGoSieveFolderEncoding defaults
Cont'd. Fixes #3904
2016-12-02 17:38:33 -05:00
Francis Lachapelle 6ce64a9a27 (js) Fix sorting after renaming an AddressBook 2016-12-02 14:48:59 -05:00
Ludovic Marcotte 41701cc94c (fix) properly set interval for monthly recurring events using EAS 2016-12-02 09:21:18 -05:00
Ludovic Marcotte 56d7f634a0 (fix) avoid compiler warning 2016-12-02 08:48:26 -05:00
InverseBot 80f67785c3 (js/css) Update generated files 2016-12-02 01:29:36 -05:00
Francis Lachapelle 29dfadfe75 (css) Update default theme 2016-12-01 17:47:29 -05:00
Francis Lachapelle fb743d6381 Update Developer's Guide 2016-12-01 16:01:45 -05:00
Francis Lachapelle 203dcf1d05 (js) Improve sgAvatarImage (cont'd) 2016-12-01 15:42:28 -05:00
Francis Lachapelle 0d95267fe5 Fix ACLs management of folders in Administration
The active (super) user was not able to add herself to the ACLs of a
different user because we were removing the active user from the users
search instead of removing the folder's owner.
2016-12-01 15:40:38 -05:00
Francis Lachapelle e997923c31 (html) Fix sidenav breakpoint in Admin module 2016-12-01 15:32:24 -05:00
Francis Lachapelle 277c6e901e Update ChangeLog 2016-12-01 12:02:23 -05:00
Francis Lachapelle 2c24901803 (js/css) Update generated files 2016-12-01 12:01:50 -05:00
Francis Lachapelle 8f90aa96fe (css,js) Update md to v1.1.1-master-b8357dc 2016-12-01 12:00:00 -05:00
Francis Lachapelle ee5b60c544 Preparation for release 3.2.4 2016-12-01 11:59:16 -05:00
Francis Lachapelle d2b94eba09 (js) Fix detection of session expiration 2016-12-01 11:56:07 -05:00
Francis Lachapelle c9467e1ce3 Validate custom subject for vacation auto-reply 2016-12-01 11:21:59 -05:00
Francis Lachapelle b4e9b7b60c (css) Fix sidenav background color of Preferences 2016-12-01 11:14:38 -05:00
Francis Lachapelle 66710f1b17 (i18n) Update translations 2016-12-01 11:07:28 -05:00
Francis Lachapelle a61ea24cdb (obj-c) Indentation 2016-12-01 10:28:26 -05:00
Francis Lachapelle 3e448d2c2c New sogo-tool cleanup user feature (cont'd) 2016-12-01 10:27:22 -05:00
Ludovic Marcotte 9aa00d049d Updated NEWS 2016-12-01 09:42:03 -05:00
Ludovic Marcotte fdea54f5e7 (fix) properly escape all GAL responses (fixes #3923) 2016-12-01 09:40:24 -05:00
InverseBot 3c098ceca5 (js/css) Update generated files 2016-12-01 01:18:40 -05:00
Francis Lachapelle f7dd835f92 Fix mail attribute when importing an LDIF file
Fixes #3878
2016-11-30 16:06:09 -05:00
Francis Lachapelle 7705c11cf7 New sogo-tool cleanup user feature 2016-11-30 15:32:29 -05:00
Francis Lachapelle 52cdb71d1f Add sort by start date for the tasks list
Fixes #3840
2016-11-30 12:13:48 -05:00
Francis Lachapelle a1746cd386 (css) Fix display of tabs with oversized content 2016-11-30 12:09:11 -05:00
InverseBot cd08ab81f1 (js/css) Update generated files 2016-11-30 02:47:47 -05:00
Francis Lachapelle d293fe79ed Add handling of BYSETPOS for BYDAY in repeat rule 2016-11-29 17:47:14 -05:00
Francis Lachapelle 6f732ba535 Fix MSExchangeFreeBusySOAPRequest.wod 2016-11-29 16:14:42 -05:00
Francis Lachapelle b27c868271 Fix freebusy data fetching of MS Exchange contacts 2016-11-29 15:48:15 -05:00
InverseBot e0b0a1d783 (js/css) Update generated files 2016-11-29 03:13:53 -05:00
Francis Lachapelle e059a6043e (js) Fix possible cross-origin exception
When SOGo is launched from an external link, either using a different
"target" or using window.open(), the Mail composer could throw a
JavaScript exception when trying to save or send a message that would
completely stop the operation.

Fixes #3900
2016-11-28 15:36:03 -05:00
Francis Lachapelle e253f5735d (js) Don't test pristine state of Message composer 2016-11-28 11:45:09 -05:00
Francis Lachapelle 328d67ec3a Improve sort validation in Contacts module 2016-11-28 10:33:38 -05:00
Francis Lachapelle c3376a4ddf Fix some compilation warnings 2016-11-28 10:14:23 -05:00
Francis Lachapelle 220fb3d607 (js) Update alarms after dragging an event 2016-11-28 09:37:31 -05:00
InverseBot fb873fcae9 (js/css) Update generated files 2016-11-26 02:52:49 -05:00
Ludovic Marcotte d2e03386d2 (fix) use GMT + adjust bias when querying Exchange's FB info 2016-11-25 15:14:24 -05:00
Francis Lachapelle 35a3a8ef1d Update ChangeLog 2016-11-25 10:57:26 -05:00
Francis Lachapelle 309846c894 (js/css) Update generated files 2016-11-25 10:52:36 -05:00
Francis Lachapelle f84d0e5f30 Preparation for release 3.2.3 2016-11-25 10:45:07 -05:00
InverseBot 9bb22bd67a (js/css) Update generated files 2016-11-25 02:43:18 -05:00
Francis Lachapelle dc7cb6792a (js) Improve alarms dialog
Fixes #3909
2016-11-24 19:14:02 -05:00
Ludovic Marcotte a76f8dca04 Updated NEWS 2016-11-24 16:07:47 -05:00
Ludovic Marcotte 06e92bbde5 Updated NEWS 2016-11-24 15:54:34 -05:00
Ludovic Marcotte 022c4bac98 (fix) wrong check for delete permission 2016-11-24 15:49:10 -05:00
Ludovic Marcotte 0e82802082 (fix) also filter out recurrence-id 2016-11-24 15:43:37 -05:00
Francis Lachapelle 0a4fa9aada (css) Update default theme 2016-11-24 15:09:59 -05:00
Ludovic Marcotte 71ca4a7b47 (fix) don't check Dovecot conditions if we're using IMAP annotations 2016-11-24 11:39:05 -05:00
Francis Lachapelle 2878665af4 Merge pull request #227 from andryyy/patch-5
Update SOGoACLGermanModificationAdvisory.wox
2016-11-24 06:34:27 -05:00
Francis Lachapelle 9f5a6707bb Merge pull request #226 from andryyy/patch-4
Update SOGoACLGermanRemovalAdvisory.wox
2016-11-24 06:33:52 -05:00
InverseBot 0e07844108 (js/css) Update generated files 2016-11-24 03:11:27 -05:00
André Peters 7c9d8b19b9 Update SOGoACLGermanModificationAdvisory.wox
Fix umlauts in messages like "... Zugang zu seinem Order "Persönlicher Kalender".
2016-11-24 08:50:43 +01:00
André Peters 1f9ef6fea7 Update SOGoACLGermanRemovalAdvisory.wox
Fix umlauts in messages like "... Zugang zu seinem Order "Persönlicher Kalender".
2016-11-24 08:50:42 +01:00
Ludovic Marcotte 836fdb1722 (feat) added photo support for LDAP-based address books 2016-11-23 16:30:41 -05:00
Francis Lachapelle cb960fae02 Restore attributes of base64-encoded img tags
Fixes #3814
2016-11-23 14:57:24 -05:00
Francis Lachapelle b8df16cd8c Improve SOGoDefaults.plist to avoid warning 2016-11-23 14:52:10 -05:00
Francis Lachapelle 84e591cdb9 (js) Update CKEditor to version 4.6.0 2016-11-23 13:57:49 -05:00
416 changed files with 16865 additions and 6900 deletions

No files matched your search

+11
View File
@@ -15,6 +15,7 @@ trans.es_ES = UI/MailerUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailerUI/Basque.lproj/Localizable.strings
trans.fi = UI/MailerUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MailerUI/French.lproj/Localizable.strings
trans.he = UI/MailerUI/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/MailerUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MailerUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MailerUI/Icelandic.lproj/Localizable.strings
@@ -50,6 +51,7 @@ trans.es_ES = UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/PreferencesUI/Basque.lproj/Localizable.strings
trans.fi = UI/PreferencesUI/Finnish.lproj/Localizable.strings
trans.fr = UI/PreferencesUI/French.lproj/Localizable.strings
trans.he = UI/PreferencesUI/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/PreferencesUI/Croatian.lproj/Localizable.strings
trans.hu = UI/PreferencesUI/Hungarian.lproj/Localizable.strings
trans.is = UI/PreferencesUI/Icelandic.lproj/Localizable.strings
@@ -85,6 +87,7 @@ trans.es_ES = UI/Scheduler/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Scheduler/Basque.lproj/Localizable.strings
trans.fi = UI/Scheduler/Finnish.lproj/Localizable.strings
trans.fr = UI/Scheduler/French.lproj/Localizable.strings
trans.he = UI/Scheduler/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/Scheduler/Croatian.lproj/Localizable.strings
trans.hu = UI/Scheduler/Hungarian.lproj/Localizable.strings
trans.is = UI/Scheduler/Icelandic.lproj/Localizable.strings
@@ -120,6 +123,7 @@ trans.es_ES = UI/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Contacts/Basque.lproj/Localizable.strings
trans.fi = UI/Contacts/Finnish.lproj/Localizable.strings
trans.fr = UI/Contacts/French.lproj/Localizable.strings
trans.he = UI/Contacts/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/Contacts/Croatian.lproj/Localizable.strings
trans.hu = UI/Contacts/Hungarian.lproj/Localizable.strings
trans.is = UI/Contacts/Icelandic.lproj/Localizable.strings
@@ -155,6 +159,7 @@ trans.es_ES = UI/MainUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MainUI/Basque.lproj/Localizable.strings
trans.fi = UI/MainUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MainUI/French.lproj/Localizable.strings
trans.he = UI/MainUI/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/MainUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MainUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MainUI/Icelandic.lproj/Localizable.strings
@@ -190,6 +195,7 @@ trans.es_ES = UI/Common/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Common/Basque.lproj/Localizable.strings
trans.fi = UI/Common/Finnish.lproj/Localizable.strings
trans.fr = UI/Common/French.lproj/Localizable.strings
trans.he = UI/Common/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/Common/Croatian.lproj/Localizable.strings
trans.hu = UI/Common/Hungarian.lproj/Localizable.strings
trans.is = UI/Common/Icelandic.lproj/Localizable.strings
@@ -225,6 +231,7 @@ trans.es_ES = UI/AdministrationUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/AdministrationUI/Basque.lproj/Localizable.strings
trans.fi = UI/AdministrationUI/Finnish.lproj/Localizable.strings
trans.fr = UI/AdministrationUI/French.lproj/Localizable.strings
trans.he = UI/AdministrationUI/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/AdministrationUI/Croatian.lproj/Localizable.strings
trans.hu = UI/AdministrationUI/Hungarian.lproj/Localizable.strings
trans.is = UI/AdministrationUI/Icelandic.lproj/Localizable.strings
@@ -260,6 +267,7 @@ trans.es_ES = SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Appointments/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Appointments/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Appointments/French.lproj/Localizable.strings
trans.he = SoObjects/Appointments/Hebrew.lproj/Localizable.strings
trans.hr_HR = SoObjects/Appointments/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Appointments/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Appointments/Icelandic.lproj/Localizable.strings
@@ -295,6 +303,7 @@ trans.es_ES = SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Contacts/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Contacts/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Contacts/French.lproj/Localizable.strings
trans.he = SoObjects/Contacts/Hebrew.lproj/Localizable.strings
trans.hr_HR = SoObjects/Contacts/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Contacts/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Contacts/Icelandic.lproj/Localizable.strings
@@ -330,6 +339,7 @@ trans.es_ES = SoObjects/Mailer/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Mailer/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Mailer/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Mailer/French.lproj/Localizable.strings
trans.he = SoObjects/Mailer/Hebrew.lproj/Localizable.strings
trans.hr_HR = SoObjects/Mailer/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Mailer/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Mailer/Icelandic.lproj/Localizable.strings
@@ -365,6 +375,7 @@ trans.es_ES = UI/MailPartViewers/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailPartViewers/Basque.lproj/Localizable.strings
trans.fi = UI/MailPartViewers/Finnish.lproj/Localizable.strings
trans.fr = UI/MailPartViewers/French.lproj/Localizable.strings
trans.he = UI/MailPartViewers/Hebrew.lproj/Localizable.strings
trans.hr_HR = UI/MailPartViewers/Croatian.lproj/Localizable.strings
trans.hu = UI/MailPartViewers/Hungarian.lproj/Localizable.strings
trans.is = UI/MailPartViewers/Icelandic.lproj/Localizable.strings
+95 -4
View File
@@ -83,7 +83,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr;
NSMutableArray *other_addresses;
CardElement *n, *homeAdr, *workAdr, *otherAdr;
NSMutableString *s, *a;
NSString *url;
id o;
@@ -186,7 +187,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", a];
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
@@ -217,7 +218,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", a];
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
@@ -232,6 +233,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<BusinessCountry xmlns=\"Contacts:\">%@</BusinessCountry>", [o activeSyncRepresentationInContext: context]];
}
// Other Address
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
if ([other_addresses count])
{
otherAdr = [other_addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [otherAdr flattenedValueAtIndex: 2 forKey: @""]))
[a appendString: [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<OtherStreet xmlns=\"Contacts:\">%@</OtherStreet>", a];
if ((o = [otherAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<OtherCity xmlns=\"Contacts:\">%@</OtherCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<OtherState xmlns=\"Contacts:\">%@</OtherState>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<OtherPostalCode xmlns=\"Contacts:\">%@</OtherPostalCode>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 6 forKey: @""]))
[s appendFormat: @"<OtherCountry xmlns=\"Contacts:\">%@</OtherCountry>", [o activeSyncRepresentationInContext: context]];
}
// Other, less important fields
if ((o = [self birthday]))
{
@@ -267,7 +301,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inContext: (WOContext *) context
{
CardElement *element;
NSMutableArray *addressLines;
NSMutableArray *addressLines, *other_addresses;
id o;
// Contact's note
@@ -398,6 +432,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
atIndex: 6 forKey: @""];
}
// OtherCountry
//
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
if ([other_addresses count])
element = [other_addresses objectAtIndex: 0];
else
{
element = [CardElement elementWithTag: @"adr"];
[self addChild: element];
}
if ((o = [theValues objectForKey: @"OtherStreet"]) || ![self _isGhosted: @"OtherStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"OtherCity"]) || ![self _isGhosted: @"OtherCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherState"]) || ![self _isGhosted: @"OtherState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherPostalCode"]) || ![self _isGhosted: @"OtherPostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherPostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherCountry"]) || ![self _isGhosted: @"OtherCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherCountry"]
atIndex: 6 forKey: @""];
}
// Company's name
if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil];
+9
View File
@@ -148,6 +148,10 @@ static NSArray *easCommandParameters = nil;
const char* qs_bytes;
queryString = [[components objectAtIndex: 0] dataByDecodingBase64];
if (![queryString length])
return nil;
qs_bytes = (const char*)[queryString bytes];
return [NSString stringWithFormat:@"%.1f", (float)((uint8_t)qs_bytes[0]/10)];
@@ -196,6 +200,11 @@ static NSArray *easCommandParameters = nil;
// Command code, 1 byte, ie.: cmd=
cmd_code = qs_bytes[1];
// Check whether the command code is within the known range.
if (cmd_code < 0 || cmd_code > 22)
return nil;
[components addObject: [NSString stringWithFormat: @"cmd=%@", [easCommandCodes objectAtIndex: cmd_code]]];
// Device ID length and Device ID (variable)
+40 -6
View File
@@ -269,6 +269,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncAddCommand: (id <DOMElement>) theDocumentElement
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
objectsToTouch: (NSMutableArray *) objectsToTouch
inBuffer: (NSMutableString *) theBuffer
{
NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues;
@@ -280,6 +281,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
int i;
additions = (id)[theDocumentElement getElementsByTagName: @"Add"];
if ([additions count])
{
for (i = 0; i < [additions count]; i++)
@@ -367,9 +369,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[o takeActiveSyncValues: allValues inContext: context];
[sogoObject setIsNew: is_new];
[sogoObject saveComponent: o];
if (theFolderType == ActiveSyncEventFolder)
[sogoObject saveComponent: o force: YES];
else
[sogoObject saveComponent: o];
}
if ([sogoObject isKindOfClass: [SOGoAppointmentObject class]] && [sogoObject resourceHasAutoAccepted])
[objectsToTouch addObject: sogoObject];
// Update syncCache
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
@@ -421,7 +431,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", easId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Add>"];
}
}
}
@@ -465,7 +474,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncChangeCommand: (id <DOMElement>) theDocumentElement
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
inBuffer: (NSMutableString *) theBuffer
objectsToTouch: (NSMutableArray *) objectsToTouch
inBuffer: (NSMutableString *) theBuffer
{
NSDictionary *allChanges;
NSString *serverId, *easId, *origServerId, *mergedFolder;
@@ -505,6 +515,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncChangeCommand: theDocumentElement
inCollection: [self collectionFromId: mergedFolder type: theFolderType]
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
continue;
@@ -588,7 +599,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
{
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
if (theFolderType == ActiveSyncEventFolder)
{
[sogoObject saveComponent: o force: YES];
if ([sogoObject resourceHasAutoAccepted])
[objectsToTouch addObject: sogoObject];
}
else
[sogoObject saveComponent: o];
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
@@ -748,7 +767,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
roles = [theCollection aclsForUser:[[context activeUser] login]];
// We check ACLs on the collection and not on the SOGo object itself, as the add/delete rights are on the collection itself
if (![roles containsObject: SOGoRole_ObjectEraser] || ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
if (![roles containsObject: SOGoRole_ObjectEraser] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
{
// This will trigger an add-command to re-add the component to the client
[sogoObject touch];
@@ -1637,6 +1656,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
inBuffer: (NSMutableString *) theBuffer
objectsToTouch: (NSMutableArray *) objectsToTouch
processed: (BOOL *) processed
{
id <DOMElement> aCommand, element;
@@ -1665,6 +1685,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncAddCommand: element
inCollection: theCollection
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
*processed = YES;
}
@@ -1674,6 +1695,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncChangeCommand: element
inCollection: theCollection
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
*processed = YES;
}
@@ -1711,7 +1733,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *filterType, *lastServerKey, *syncKeyInCache, *folderKey;
NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableArray *supportedElements, *supportedElementNames;
NSMutableArray *supportedElements, *supportedElementNames, *objectsToTouch;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
@@ -1941,6 +1963,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// We process the commands from the request
//
objectsToTouch = [NSMutableArray array];
if (!first_sync)
{
NSMutableString *s;
@@ -1953,6 +1977,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inCollection: collection
withType: folderType
inBuffer: s
objectsToTouch: objectsToTouch
processed: &processed];
// Windows phones don't like empty Responses tags - such as: <Responses></Responses>.
@@ -2271,6 +2296,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendString: changeBuffer];
[theBuffer appendString: @"</Collection>"];
// We touch objects (likely only SOGoAppointmentObjects for now) that might have been changed
// by the server and that we want the EAS client to re-pull. For example, that's the case for
// auto-accepted events by resources
for (i = 0; i < [objectsToTouch count]; i++)
{
sleep(1);
[[objectsToTouch objectAtIndex: i] touch];
}
}
//
+99 -38
View File
@@ -43,6 +43,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGObjWeb/WOCoreApplication.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
#import <NGExtensions/NGBase64Coding.h>
@@ -59,8 +61,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeFileData.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGConcreteMimeType.h>
#import <NGMail/NGMimeMessageParser.h>
#import <NGMail/NGMimeMessageGenerator.h>
@@ -947,6 +950,7 @@ void handle_eas_terminate(int signum)
command_count++;
}
[o destroy];
continue;
}
// Remove the folder from device if it is a contact folder and we have no SOGoRole_ObjectViewer.
@@ -1097,6 +1101,7 @@ void handle_eas_terminate(int signum)
![currentFolder synchronize])
{
[folders removeObjectAtIndex: count];
continue;
}
// Remove the folder from the device if it is a contact folder and we have no SOGoRole_ObjectViewer access right.
@@ -1815,7 +1820,7 @@ void handle_eas_terminate(int signum)
{
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
inContainer: collection];
[appointmentObject saveComponent: event];
[appointmentObject saveComponent: event force: YES];
}
if (uidCache && [calendarId length] > 64)
@@ -2651,6 +2656,7 @@ void handle_eas_terminate(int signum)
NSData *d;
unsigned int startdate, enddate, increments;
NGCalendarDateRange *r1, *r2;
char c;
startDate = [[[(id)[theDocumentElement getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate];
@@ -2697,17 +2703,13 @@ void handle_eas_terminate(int signum)
folder = [user personalCalendarFolderInContext: context];
freebusy = [folder fetchFreeBusyInfosFrom: startDate to: endDate];
NGCalendarDateRange *r1, *r2;
for (j = 1; j <= increments; j++)
for (j = 0; j < increments; j++)
{
c = '0';
r1 = [NGCalendarDateRange calendarDateRangeWithStartDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60)]
endDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60 + 30)]];
for (k = 0; k < [freebusy count]; k++)
{
@@ -2725,11 +2727,8 @@ void handle_eas_terminate(int signum)
[s appendFormat: @"%c", c];
}
[s appendString: @"</MergedFreeBusy>"];
[s appendString: @"</Availability>"];
[s appendString: @"</Recipient>"];
[s appendString: @"</Response>"];
}
@@ -2826,31 +2825,31 @@ void handle_eas_terminate(int signum)
[s appendString: @"<Properties>"];
if ((o = [contact objectForKey: @"displayname"]))
[s appendFormat: @"<DisplayName xmlns=\"Gal:\">%@</DisplayName>", o];
[s appendFormat: @"<DisplayName xmlns=\"Gal:\">%@</DisplayName>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"title"]))
[s appendFormat: @"<Title xmlns=\"Gal:\">%@</Title>", o];
[s appendFormat: @"<Title xmlns=\"Gal:\">%@</Title>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"givenname"]))
[s appendFormat: @"<FirstName xmlns=\"Gal:\">%@</FirstName>", o];
[s appendFormat: @"<FirstName xmlns=\"Gal:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"sn"]))
[s appendFormat: @"<LastName xmlns=\"Gal:\">%@</LastName>", o];
[s appendFormat: @"<LastName xmlns=\"Gal:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ([current_mail length] > 0)
[s appendFormat: @"<EmailAddress xmlns=\"Gal:\">%@</EmailAddress>", current_mail];
if ((o = [contact objectForKey: @"telephonenumber"]))
[s appendFormat: @"<Phone xmlns=\"Gal:\">%@</Phone>", o];
[s appendFormat: @"<Phone xmlns=\"Gal:\">%@</Phone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"homephone"]))
[s appendFormat: @"<HomePhone xmlns=\"Gal:\">%@</HomePhone>", o];
[s appendFormat: @"<HomePhone xmlns=\"Gal:\">%@</HomePhone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"mobile"]))
[s appendFormat: @"<MobilePhone xmlns=\"Gal:\">%@</MobilePhone>", o];
[s appendFormat: @"<MobilePhone xmlns=\"Gal:\">%@</MobilePhone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"o"]))
[s appendFormat: @"<Company xmlns=\"Gal:\">%@</Company>", o];
[s appendFormat: @"<Company xmlns=\"Gal:\">%@</Company>", [o activeSyncRepresentationInContext: context]];
[s appendString: @"</Properties>"];
[s appendString: @"</Result>"];
@@ -3167,6 +3166,28 @@ void handle_eas_terminate(int signum)
return nil;
}
//
// See https://msdn.microsoft.com/en-us/library/ee218647(v=exchg.80).aspx
// for valid status codes.
//
- (NSData *) _sendMailErrorResponseWithStatus: (int) status
{
NSMutableString *s;
NSData *d;
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
[s appendFormat: @"<Status>%d</Status>", status];
[s appendString: @"</SendMail>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
return d;
}
//
//
//
@@ -3180,6 +3201,7 @@ void handle_eas_terminate(int signum)
NSData *new_from_header;
NSDictionary *identity;
NSString *fullName, *email;
NGMimeType *contentType;
NSArray *from;
const char *bytes;
@@ -3195,6 +3217,40 @@ void handle_eas_terminate(int signum)
message = [parser parsePartFromData: data];
RELEASE(parser);
// If an EAS client is trying to send an invitation email (request or response), we make sure to
// remove all attendees that have NO email addresses. Outlook 2016 (and likely other EAS clients)
// do that when sending IMIP only to "newly added or deleted attendees" - existing attendees have
// their email addresses stripped, while keeping the display name value.
contentType = [message contentType];
if ([contentType isKindOfClass: [NGConcreteTextMimeType class]] &&
[[[message contentType] subType] caseInsensitiveCompare: @"calendar"] == NSOrderedSame &&
([[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"request"] == NSOrderedSame ||
[[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"reply"] == NSOrderedSame))
{
NGMimeMessageGenerator *generator;
iCalCalendar *calendar;
iCalPerson *attendee;
NSArray *attendees;
iCalEvent *event;
calendar = [iCalCalendar parseSingleFromSource: [message body]];
event = [[calendar events] lastObject];
attendees = [event attendees];
for (i = [attendees count]-1; i >= 0; i--)
{
attendee = [attendees objectAtIndex: i];
if (![attendee rfc822Email] || [[attendee rfc822Email] caseInsensitiveCompare: @"nomail"] == NSOrderedSame)
[event removeFromAttendees: attendee];
}
// We regenerate the data to use
[message setBody: [[calendar versitString] dataUsingEncoding: NSUTF8StringEncoding]];
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [NSMutableData dataWithData: [generator generateMimeFromPart: message]];
}
from = [message headersForKey: @"from"];
if (![from count] || ![self _isEMailValid: [[from objectAtIndex: 0] pureEMailAddress]] ||
@@ -3296,20 +3352,7 @@ void handle_eas_terminate(int signum)
{
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.0)
{
NSMutableString *s;
NSData *d;
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
[s appendString: @"<Status>120</Status>"];
[s appendString: @"</SendMail>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 120]];
}
else
{
@@ -3937,6 +3980,14 @@ void handle_eas_terminate(int signum)
// Get the device ID, device type and "stash" them
deviceId = [[theRequest uri] deviceId];
if ([deviceId isEqualToString: @"Unknown"])
{
[(WOResponse *)theResponse setStatus: 500];
[self logWithFormat: @"EAS - No device id provided, ignoring request."];
return nil;
}
[context setObject: deviceId forKey: @"DeviceId"];
[context setObject: [[theRequest uri] deviceType] forKey: @"DeviceType"];
[context setObject: [[theRequest uri] attachmentName] forKey: @"AttachmentName"];
@@ -3993,23 +4044,32 @@ void handle_eas_terminate(int signum)
}
xml = [NSString stringWithFormat: @"<?xml version=\"1.0\"?><!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\"><%@ xmlns=\"ComposeMail:\"><SaveInSentItems/><MIME>%@</MIME></%@>", cmdName, [s stringByEncodingBase64], cmdName];
d = [xml dataUsingEncoding: NSASCIIStringEncoding];
}
else
{
d = [[theRequest content] wbxml2xml];
// Handle empty Ping request, no need to try decoding the WBXML blob here
if ([[theRequest content] length])
d = [[theRequest content] wbxml2xml];
else
d = nil;
}
documentElement = nil;
if (!d)
{
// If we got no data in the SendMail request, that means SOPE rejected it because of the WOMaxUploadSize.
// We generate here the proper failed response for SendMail
if ([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame)
{
[theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"];
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 122]];
goto return_response;
}
// We check if it's a Ping command with no body.
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
else if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
{
RELEASE(context);
RELEASE(pool);
@@ -4047,6 +4107,7 @@ void handle_eas_terminate(int signum)
[self performSelector: aSelector withObject: documentElement withObject: theResponse];
return_response:
[theResponse setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"];
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
+46 -36
View File
@@ -163,7 +163,7 @@ struct GlobalObjectId {
const char *bytes;
uid = [event uid];
uidAsASCII = [uid decodeFromHexidecimal];
uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding];
newGlobalId = (struct GlobalObjectId*)calloc(sizeof(uint8_t), sizeof(struct GlobalObjectId) + 0x0c + [uidAsASCII length]);
prefix = @"040000008200e00074c5b7101a82e008";
@@ -726,12 +726,53 @@ struct GlobalObjectId {
//
//
//
- (iCalCalendar *) calendarFromIMIPMessage
- (iCalCalendar *) _calendarFromParts: (NSArray *) parts
parent: (id) parent
{
NSDictionary *part;
id bodyPart;
int i;
for (i = 0; i < [parts count]; i++)
{
part = [parts objectAtIndex: i];
bodyPart = [parent lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
inContext: self->context];
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
{
if (bodyPart)
{
iCalCalendar *calendar;
NSData *calendarData;
calendarData = [bodyPart fetchBLOBWithPeek: YES];
calendar = nil;
NS_DURING
calendar = [iCalCalendar parseSingleFromSource: calendarData];
NS_HANDLER
calendar = nil;
NS_ENDHANDLER
return calendar;
}
}
else if ([[part objectForKey: @"type"] isEqualToString: @"multipart"])
return [self _calendarFromParts: [part objectForKey: @"parts"] parent: bodyPart];
}
return nil;
}
//
//
//
- (iCalCalendar *) calendarFromIMIPMessage
{
NSString *type, *subtype;
NSArray *parts;
int i;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
@@ -764,38 +805,7 @@ struct GlobalObjectId {
parts = [[self bodyStructure] objectForKey: @"parts"];
if ([parts count] > 1)
{
for (i = 0; i < [parts count]; i++)
{
part = [parts objectAtIndex: i];
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
{
id bodyPart;
bodyPart = [self lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
inContext: self->context];
if (bodyPart)
{
iCalCalendar *calendar;
NSData *calendarData;
calendarData = [bodyPart fetchBLOBWithPeek: YES];
calendar = nil;
NS_DURING
calendar = [iCalCalendar parseSingleFromSource: calendarData];
NS_HANDLER
calendar = nil;
NS_ENDHANDLER
return calendar;
}
}
}
}
return [self _calendarFromParts: parts parent: self];
return nil;
}
@@ -934,7 +944,7 @@ struct GlobalObjectId {
else if ([method isEqualToString: @"COUNTER"])
className = @"IPM.Schedule.Meeting.Resp.Tent";
else if ([method isEqualToString: @"CANCEL"])
className = @"IPM.Schedule.Meeting.Cancelled";
className = @"IPM.Schedule.Meeting.Canceled";
else
className = @"IPM.Appointment";
+48 -28
View File
@@ -36,6 +36,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalTrigger.h>
#import <NGCards/NSCalendarDate+NGCards.h>
#include "NSDate+ActiveSync.h"
@implementation iCalAlarm (ActiveSync)
@@ -53,9 +56,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
nextAlarmDate = [self nextAlarmDate];
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
if ([parent isKindOfClass: [iCalEvent class]])
{
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
else
{
[s appendFormat: @"<ReminderTime xmlns=\"Task:\">%@</ReminderTime>", [nextAlarmDate activeSyncRepresentationInContext: context]];
}
}
return s;
@@ -67,37 +77,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
iCalTrigger *trigger;
id o;
o = [theValues objectForKey: @"Reminder"];
// Outlook: if reminder is set to 0 minutes before start save it as 1 minute since -> 0 minutes in not accepted by SOGo
if ([o isEqualToString: @"0"])
o = @"1";
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DURATION"];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
// 1week = -P1W
// 2weeks > -PxD
// xdays > -PxD
// xhours -> -PTxH
// xmin -> -PTxM
if ([o intValue] == 10080)
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
else
if ((o = [theValues objectForKey: @"Reminder"]))
{
if (([o intValue] % 1440) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
// Outlook: if reminder is set to 0 minutes before starttime, save it as 1 minute since -> 0 minutes in not accepted by SOGo
if ([o isEqualToString: @"0"])
o = @"1";
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DURATION"];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
// 1week = -P1W
// 2weeks > -PxD
// xdays > -PxD
// xhours -> -PTxH
// xmin -> -PTxM
if ([o intValue] == 10080)
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
else
{
if (([o intValue] % 60) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
if (([o intValue] % 1440) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
else
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
{
if (([o intValue] % 60) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
else
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
}
}
}
else if ((o = [theValues objectForKey: @"ReminderTime"]))
{
o = [o calendarDate];
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DATE-TIME"];
[trigger setSingleValue: [NSString stringWithFormat: @"%@Z", [o iCalFormattedDateTimeString]] forKey: @""];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
}
}
@end
+3 -1
View File
@@ -602,7 +602,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
// Recurrence
if ((o = [theValues objectForKey: @"Recurrence"]))
if ((o = [theValues objectForKey: @"Recurrence"]) && [o isKindOfClass: [NSDictionary class]])
{
iCalRecurrenceRule *rule;
@@ -884,6 +884,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self setAttendees: attendees];
}
}
[self setLastModified: [NSCalendarDate calendarDate]];
}
- (void) changeParticipationStatus: (NSDictionary *) theValues
@@ -134,6 +134,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, firstOccurrence];
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
}
else if ([[self byMonthDay] count])
{
@@ -149,6 +150,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
dm = [self byDayMask];
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, 5];
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
}
else
{
+25 -3
View File
@@ -43,9 +43,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h"
#include "iCalAlarm+ActiveSync.h"
@implementation iCalToDo (ActiveSync)
@@ -95,8 +98,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
v = 1;
[s appendFormat: @"<Importance xmlns=\"Tasks:\">%d</Importance>", v];
// Reminder - FIXME
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
// Reminder
if ([self hasAlarms])
{
iCalAlarm *alarm;
alarm = [self firstDisplayOrAudioAlarm];
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 1];
[s appendString: [alarm activeSyncRepresentationInContext: context]];
}
else
{
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
}
// Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"])
@@ -145,7 +159,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: @"</Body>"];
}
}
return s;
}
@@ -249,6 +263,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"ReminderTime"]))
{
iCalAlarm *alarm;
alarm = [[iCalAlarm alloc] init];
[alarm takeActiveSyncValues: theValues inContext: context];
[self removeAllAlarms];
[self addToAlarms: alarm];
RELEASE(alarm);
}
+2817
View File
File diff suppressed because it is too large. Load diff
+12 -6
View File
@@ -118,6 +118,11 @@ http://trochette.github.io/Angular-Design-Patterns-Best-Practices/
* Move business logic into models and share them.
* Keep it simple, separate server interaction and error handling from the model. That way model only handle data processing and code is easier to maintain.
Controllers
^^^^^^^^^^^
* Functions not exposed in the controller must be prefixed with an underscore.
SASS/CSS
~~~~~~~~
@@ -133,11 +138,6 @@ SASS/CSS
A `@mixin` is like a stamp: it creates a duplicated version of the property block (optionally) with arguments provided. An `@extend` appends the element you are extending to the property block. It is your "yes, and ___" statement.
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
git submodule init
git submodule update
* FlexBox compatibility http://caniuse.com/#feat=flexbox
Fonts
@@ -155,10 +155,16 @@ Building frontend
npm install -g grunt-cli
npm install -g bower
* Generate JavaScript files
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
git submodule init
git submodule update
* Generate the JavaScript and CSS files
cd UI/WebServerResources
npm install
bower install
grunt build
Version Control
+8 -2
View File
@@ -391,9 +391,14 @@ internet connection could be killed prematurely.
|Parameter used to set the maximum allowed size for content being
sent to SOGo using a PUT or a POST call. This can also limit the file
attachment size being uploaded to SOGo when composing a mail. The
value is in kilobyte. By default, the value is 0, or disabled so no
value is in kilobytes. By default, the value is 0, or disabled so no
limit will be set.
|S |SOGoMaximumMessageSizeLimit
|Parameter used to set the maximum allowed email message size when
composing a mail. The value is in kilobytes. By default, the value is 0,
or disabled so no limit will be set.
|S |SxVMemLimit
|Parameter used to set the maximum amount of memory (in
megabytes) that a child can use. Reaching that value will force children
@@ -501,7 +506,7 @@ SOGoSAML2LogoutEnabled must be set to YES. If unset, the user will be
redirected to a blank page.
|D |SOGoTimeZone
|Parameter used to set a default time zone for users. The default
|Mandatory parameter used to set a default time zone for users. The default
timezone is set to UTC. The Olson database is a standard database that
takes all the time zones around the world into account and represents
them along with their history. On GNU/Linux systems, time zone
@@ -1232,6 +1237,7 @@ mapping = {
2+h|Other
|Birthday |birthyear-birthmonth-birthday
|Note |description
|Photo |photo
|===
Authenticating using C.A.S.
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 3.2.2 - November 2016</releaseinfo>
<subtitle>for version 3.2.2</subtitle>
<date>2016-11-23</date>
<releaseinfo>Version 3.2.7 - February 2017</releaseinfo>
<subtitle>for version 3.2.7</subtitle>
<date>2017-02-14</date>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
@@ -6,13 +6,13 @@
Authors:
- Inverse inc. <info@inverse.ca>
Copyright (C) 2008-2016 Inverse inc.
Copyright (C) 2008-2017 Inverse inc.
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
////
// TODO have the build system take care of this
:release_version: 3.2.2
:release_version: 3.2.7
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
+10 -3
View File
@@ -27,6 +27,7 @@
#import <GDLContentStore/GCSAlarmsFolder.h>
#import <GDLContentStore/GCSSessionsFolder.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoClassSecurityInfo.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WORequest+So.h>
@@ -114,7 +115,7 @@ static BOOL debugLeaks;
vMemSizeLimit = [defaults vmemLimit];
if (vMemSizeLimit > 0)
[self logWithFormat: @"vmem size check enabled: shutting down app when "
@"vmem > %d MB", vMemSizeLimit];
@"vmem > %d MB. Currently at %d MB", vMemSizeLimit, [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
/* SoClass security declarations */
sInfo = [self soClassSecurityInfo];
@@ -137,6 +138,8 @@ static BOOL debugLeaks;
/* load products */
[[SOGoProductLoader productLoader] loadAllProducts: YES];
if (vMemSizeLimit > 0)
[self logWithFormat: @"All products loaded - current memory usage at %d MB", [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
}
- (id) init
@@ -418,8 +421,12 @@ static BOOL debugLeaks;
"GET" if no method was provided in the query path.
*/
if ([_key length] > 0 && ![_key isEqualToString:@"favicon.ico"])
obj = [self lookupUser: _key inContext: _ctx];
}
{
obj = [self lookupUser: _key inContext: _ctx];
if (!obj && ![_key isEqualToString: @"public"])
obj = [self lookupUser: @"anonymous" inContext: _ctx];
}
}
}
else
obj = nil;
+131 -1
View File
@@ -1,10 +1,140 @@
3.2.7 (2017-02-14)
------------------
New features
- [core] new sogo-tool checkup command to make sure user's data is sane
Enhancements
- [web] added Hebrew (he) translation - thanks to Raz Aidlitz
Bug fixes
- [core] generalized the bcc handling code
- [web] saving the preferences was not possible when Mail module is disabled
- [web] ignore mouse events in scrollbars of Month view (#3990)
- [web] fixed public URL with special characters (#3993)
- [web] keep the fab button visible when the center list is hidden
- [web] localized mail, phone, url and address types (#4030)
- [eas] improved EAS parameters parsing (#4003)
- [eas] properly handle canceled appointments
3.2.6a (2017-01-26)
-------------------
Bug fixes
- [core] fixed "include in freebusy" (reverts #3354)
- [web] improved ACLs handling of inactive users
3.2.6 (2017-01-23)
------------------
Enhancements
- [web] show locale codes beside language names in Preferences module
- [web] fixed visual glitches in Month view with Firefox
- [web] mail editor can now be expanded horizontally and automatically expands vertically
- [web] compose a new message inline or in a popup window
- [web] allow to select multiple files when uploading attachments (#3999)
- [web] use "date" extension of Sieve to enable/disable vacation auto-reply (#1530, #1949)
- [web] updated Angular to version 1.6.1
- [web] updated CKEditor to version 4.6.2
Bug fixes
- [core] remove all alarms before sending IMIP replies (#3925)
- [web] fixed rendering of forwared HTML message with inline images (#3981)
- [web] fixed pasting images in CKEditor using Chrome (#3986)
- [eas] make sure we trigger a download of service-side changed events
- [eas] now strip attendees with no email during MeetingResponse calls
3.2.5 (2017-01-10)
------------------
New features
- [web] download attachments of a message as a zip archive
Enhancements
- [core] improved IMIP handling from Exchange/Outlook clients
- [web] prevent using localhost on additional IMAP accounts
- [web] renamed buttons of alarm toast (#3945)
- [web] load photos of LDAP-based address books in contacts list (#3942)
- [web] added SOGoMaximumMessageSizeLimit to limit webmail message size
- [web] added photo support for LDIF import (#1084)
- [web] updated CKEditor to version 4.6.1
Bug fixes
- [core] honor blocking wrong login attempts within time interval (#2850)
- [core] better support for RFC 6638 (schedule-agent)
- [core] use source's domain when none defined and trying to match users (#3523)
- [core] handle delegation with no SENT-BY set (#3368)
- [core] properly honor the "include in freebusy" setting (#3354)
- [core] properly save next email alarm in the database (#3949)
- [core] fix events in floating time during CalDAV's PUT operation (#2865)
- [core] handle rounds in sha512-crypt password hashes
- [web] fixed confusion between owner and active user in ACLs management of Administration module
- [web] fixed JavaScript exception after renaming an address book
- [web] fixed Sieve folder encoding support (#3904)
- [web] fixed ordering of calendars when renaming or adding a calendar (#3931)
- [web] use the organizer's alarm by default when accepting IMIP messages (#3934)
- [web] switch on "Remember username" when cookie username is set
- [web] return login page for unknown users (#2135)
- [web] fixed saving monthly recurrence rule with "by day" condition (#3948)
- [web] fixed display of message content when enabling auto-reply (#3940)
- [web] don't allow to create lists in a remote address book (not yet supported)
- [web] fixed attached links in task viewer (#3963)
- [web] avoid duplicate mail entries in contact of LDAP-based address book (#3941)
- [web] append ics file extension when importing events (#2308)
- [web] handle URI in vCard photos (#2683)
- [web] handle semicolon in values during LDIF import (#1760)
- [web] fixed computation of week number (#3973, #3976)
- [web] fixed saving of inactive calendars (#3862, #3980)
- [web] fixed public URLs to Calendars (#3974)
- [web] fixed hotkeys in Mail module when a dialog is active (#3983)
- [eas] properly skip folders we don't want to synchronize (#3943)
- [eas] fixed 30 mins freebusy offset with S Planner
- [eas] now correctly handles reminders on tasks (#3964)
- [eas] always force save events creation over EAS (#3958)
- [eas] do not decode from hex the event's UID (#3965)
- [eas] add support for "other addresses" (#3966)
- [eas] provide correct response status when sending too big mails (#3956)
3.2.4 (2016-12-01)
------------------
New features
- [core] new sogo-tool cleanup user feature
Enhancements
- [core] added handling of BYSETPOS for BYDAY in recurrence rules
- [web] added sort by start date for tasks (#3840)
Bug fixes
- [web] fixed JavaScript exception when SOGo is launched from an external link (#3900)
- [web] restored fetching of freebusy information of MS Exchange contacts
- [web] fixed mail attribute when importing an LDIF file (#3878)
- [web] don't save empty custom auto-reply subject
- [web] fixed detection of session expiration
- [eas] properly escape all GAL responses (#3923)
3.2.3 (2016-11-25)
------------------
New features
- [core] added photo support for LDAP-based address books (#747, #2184)
Enhancements
- [web] updated CKEditor to version 4.6.0
- [web] updated Angular to version 1.5.9
Bug fixes
- [web] restore attributes when rewriting base64-encoded img tags (#3814)
- [web] improve alarms dialog (#3909)
- [eas] fixed EAS delete operation
3.2.2 (2016-11-23)
------------------
New features
- [core] support repetitive email alarms on tasks and events (#1053)
- [web] allow to hide center column on large screens (#3861)
- [eas] relaxed permission requirements for subscription synchronizations (#3118 and #3180)
- [eas] relaxed permission requirements for subscription synchronizations (#3118, #3180)
Enhancements
- [core] added sha256-crypt and sha512-crypt password support
+1
View File
@@ -45,6 +45,7 @@ SOGo and its associated components are available in various languages. The follo
* [fi] Finnish - Kari Salmu
* [fr] French - [Inverse](http://www.inverse.ca/)
* [de] German - Alexander Greiner-Baer
* [he] Hebrew - Raz Aidlitz
* [hu] Hungarian - Sándor Kuti
* [is] Icelandic - Anna Jonna Armannsdottir
* [it] Italian - Alessio Fattorini
+2
View File
@@ -123,6 +123,7 @@
- (NSException *) deleteContentWithName: (NSString *) _name;
- (NSException *) deleteAllContent;
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days;
- (NSException *) touchContentWithName: (NSString *) _name;
- (NSException *) deleteFolder;
@@ -144,6 +145,7 @@
- (void) deleteAclWithSpecification: (EOFetchSpecification *) _fs;
- (unsigned int) recordsCountByExcludingDeleted: (BOOL) includeDeleted;
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days;
- (NSCalendarDate *) lastModificationDate;
+162 -70
View File
@@ -68,7 +68,7 @@ static GCSStringFormatter *stringFormatter = nil;
+ (void) initialize
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"];
doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
@@ -91,7 +91,7 @@ static GCSStringFormatter *stringFormatter = nil;
NSEnumerator *fields;
GCSFieldInfo *field;
NSString *fieldName;
if ((self = [super init])) {
folderManager = [_fm retain];
folderInfo = [_ftype retain];
@@ -114,7 +114,7 @@ static GCSStringFormatter *stringFormatter = nil;
if (![fieldName isEqualToString: @"c_name"])
[contentFieldNames addObject: fieldName];
}
folderId = [_folderId copy];
folderName = [[_path lastPathComponent] copy];
path = [_path copy];
@@ -124,7 +124,7 @@ static GCSStringFormatter *stringFormatter = nil;
folderTypeName = [_ftname copy];
ofFlags.requiresFolderSelect = 0;
ofFlags.sameTableForQuick =
ofFlags.sameTableForQuick =
[location isEqualTo:quickLocation] ? 1 : 0;
}
return self;
@@ -133,7 +133,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (id) init
{
return [self initWithPath:nil primaryKey:nil
folderTypeName:nil folderType:nil
folderTypeName:nil folderType:nil
location:nil quickLocation:nil
aclLocation:nil
folderManager:nil];
@@ -271,13 +271,13 @@ static GCSStringFormatter *stringFormatter = nil;
NSDictionary *ui;
ui = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:_base],
[NSNumber numberWithUnsignedInt:_base],
@"GCSExpectedVersion",
[NSNumber numberWithUnsignedInt:_store],
@"GCSStoredVersion",
self, @"GCSFolder",
nil];
return [NSException exceptionWithName:@"GCSVersionMismatch"
reason:@"Transaction conflict during a GCS modification."
userInfo:ui];
@@ -369,7 +369,7 @@ static GCSStringFormatter *stringFormatter = nil;
requirement: (GCSTableRequirement) requirement
{
NSString *selectedFields;
if (requirement == bothTableRequired && [fields containsObject: @"c_name"])
selectedFields = [self _dottedFields: fields];
else
@@ -381,7 +381,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (NSString *) _sqlForQualifier: (EOQualifier *) qualifier
{
NSMutableString *ms;
if (qualifier)
{
ms = [NSMutableString stringWithCapacity:32];
@@ -399,7 +399,7 @@ static GCSStringFormatter *stringFormatter = nil;
if ((count = [_so count]) == 0)
return nil;
sql = [NSMutableString stringWithCapacity:(count * 16)];
for (i = 0; i < count; i++) {
EOSortOrdering *so;
@@ -409,9 +409,9 @@ static GCSStringFormatter *stringFormatter = nil;
so = [_so objectAtIndex:i];
sel = [so selector];
column = [so key];
if (i > 0) [sql appendString:@", "];
if (sel_eq(sel, EOCompareAscending)) {
[sql appendString:column];
[sql appendString:@" ASC"];
@@ -558,21 +558,21 @@ static GCSStringFormatter *stringFormatter = nil;
error = [channel evaluateExpressionX:sql];
if (error)
{
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
results = nil;
}
else
{
/* fetch results */
results = [NSMutableArray arrayWithCapacity: 64];
attrs = [channel describeResults: NO /* do not beautify names */];
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
[results addObject: row];
/* release channels */
}
[self releaseChannel: channel];
// NSLog(@"/running query");
@@ -582,7 +582,7 @@ static GCSStringFormatter *stringFormatter = nil;
[self errorWithFormat:@" could not open storage channel!"];
results = nil;
}
return results;
}
@@ -764,7 +764,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute;
id value;
unsigned i, count;
if (_row == nil || _table == nil)
return nil;
@@ -774,7 +774,7 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@"UPDATE "];
[sql appendString:_table];
[sql appendString:@" SET "];
for (i = 0, count = [keys count]; i < count; i++) {
fieldName = [keys objectAtIndex:i];
@@ -823,7 +823,7 @@ andAttribute: (EOAttribute *)_attribute
{
EOAttribute *attribute;
EOEntity *entity;
entity = AUTORELEASE([[EOEntity alloc] init]);
[entity setName: _name];
[entity setExternalName: _name];
@@ -841,7 +841,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute;
EOEntity *entity;
entity = [self _entityWithName: [self storeTableName]];
entity = [self _entityWithName: [self storeTableName]];
attribute = AUTORELEASE([[EOAttribute alloc] init]);
[attribute setName: @"c_version"];
@@ -873,7 +873,7 @@ andAttribute: (EOAttribute *)_attribute
- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname
isEqualTo:(id)_value
andColumn:(NSString *)_colname2
andColumn:(NSString *)_colname2
isEqualTo:(id)_value2
entity: (EOEntity *)_entity
withAdaptor: (EOAdaptor *)_adaptor
@@ -970,7 +970,7 @@ andAttribute: (EOAttribute *)_attribute
NSArray *rows;
NSException *error;
/* check preconditions */
/* check preconditions */
if (_name)
{
if (_content)
@@ -979,10 +979,10 @@ andAttribute: (EOAttribute *)_attribute
error = nil;
nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
rows = [self fetchFields: [NSArray arrayWithObjects:
@"c_version",
@"c_deleted",
@@ -1035,16 +1035,16 @@ andAttribute: (EOAttribute *)_attribute
if (quickRow)
{
[quickRow setObject:_name forKey:@"c_name"];
if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow];
/* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow];
[contentRow setObject:_name forKey:@"c_name"];
[contentRow setObject:now forKey:@"c_lastmodified"];
if (isNewRecord)
@@ -1058,7 +1058,7 @@ andAttribute: (EOAttribute *)_attribute
[NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"];
[contentRow setObject:_content forKey:@"c_content"];
/* open channels */
storeChannel = [self acquireStoreChannel];
if (storeChannel)
@@ -1080,14 +1080,14 @@ andAttribute: (EOAttribute *)_attribute
/* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll
be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate]
hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[quickChannel adaptorContext] beginTransaction];
[[storeChannel adaptorContext] beginTransaction];
quickTableEntity = [self _quickTableEntity];
storeTableEntity = [self _storeTableEntity];
@@ -1097,10 +1097,10 @@ andAttribute: (EOAttribute *)_attribute
error = (hasInsertDelegate
? [quickChannel insertRowX: quickRow forEntity: quickTableEntity]
: [quickChannel
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
adaptor: [[quickChannel adaptorContext] adaptor]
tableName: [self quickTableName]]]);
if (!error)
error = (hasInsertDelegate
? [storeChannel insertRowX: contentRow forEntity: storeTableEntity]
@@ -1138,7 +1138,7 @@ andAttribute: (EOAttribute *)_attribute
andColumn: (*_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (*_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: *_baseVersion] : (NSNumber *)nil)]]);
}
if (error)
{
[[storeChannel adaptorContext] rollbackTransaction];
@@ -1193,19 +1193,19 @@ andAttribute: (EOAttribute *)_attribute
NSException *error;
NSString *delsql;
NSCalendarDate *nowDate;
/* check preconditions */
if (_name == nil) {
return [NSException exceptionWithName:@"GCSDeleteException"
reason:@"no content filename was provided"
userInfo:nil];
}
if (doLogStore)
[self logWithFormat:@"should delete content: '%@'", _name];
/* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open storage channel!"];
return nil;
@@ -1242,7 +1242,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete content '%@': %@",
@"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
}
else if (!ofFlags.sameTableForQuick) {
@@ -1256,19 +1256,19 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete quick row '%@': %@",
@"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
/*
/*
Note: we now have a "broken" record, needs to be periodically GCed by
a script!
*/
}
}
/* release channels and return */
[adaptorCtx commitTransaction];
[self releaseChannel:storeChannel];
if (!ofFlags.sameTableForQuick) {
[[quickChannel adaptorContext] commitTransaction];
[self releaseChannel:quickChannel];
@@ -1307,7 +1307,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self storeTableName]];
error = [storeChannel evaluateExpressionX:query];
if (error)
[self errorWithFormat: @"%s: cannot delete content '%@': %@",
[self errorWithFormat: @"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, query, error];
else if (!ofFlags.sameTableForQuick) {
/* content row deleted, now delete the quick row */
@@ -1317,7 +1317,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self quickTableName]];
error = [quickChannel evaluateExpressionX: query];
if (error)
[self errorWithFormat: @"%s: cannot delete quick row '%@': %@",
[self errorWithFormat: @"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, query, error];
}
@@ -1333,6 +1333,49 @@ andAttribute: (EOAttribute *)_attribute
return error;
}
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days
{
NSString *delSql, *table;
EOAdaptorContext *adaptorCtx;
EOAdaptorChannel *channel;
NSCalendarDate *nowDate;
unsigned int delta;
channel = [self acquireStoreChannel];
if ((channel = [self acquireStoreChannel]) == nil)
return [NSException exceptionWithName: @"GCSChannelException"
reason: @"could not open storage channel!"
userInfo: nil];
adaptorCtx = [channel adaptorContext];
[adaptorCtx beginTransaction];
table = [self storeTableName];
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta > [nowDate timeIntervalSince1970])
return [NSException exceptionWithName: @"GCSArgumentException"
reason: @"number of days is too high!"
userInfo: nil];
delta = [nowDate timeIntervalSince1970] - delta;
if ([GCSFolderManager singleStoreMode])
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_folder_id = %@ AND c_deleted = 1 AND c_lastmodified < %u",
table, folderId, delta];
else
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_deleted = 1 AND c_lastmodified < %u",
table, delta];
[channel evaluateExpressionX: delSql];
[[channel adaptorContext] commitTransaction];
[self releaseChannel: channel];
return nil;
}
- (NSException *) touchContentWithName: (NSString *) _name
{
NSString *touchSql, *table;
@@ -1389,14 +1432,14 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *channel;
NSString *delsql;
NSString *table;
/* open channels */
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open channel!"];
return nil;
}
/* delete rows */
[[channel adaptorContext] beginTransaction];
table = [self storeTableName];
@@ -1423,7 +1466,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql];
}
[[channel adaptorContext] commitTransaction];
[self releaseChannel:channel];
@@ -1437,7 +1480,7 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *quickChannel;
EOAdaptorContext *adaptorCtx;
NSException *error;
quickChannel = [self acquireQuickChannel];
adaptorCtx = [quickChannel adaptorContext];
[adaptorCtx beginTransaction];
@@ -1500,15 +1543,15 @@ andAttribute: (EOAttribute *)_attribute
NSArray *attrs;
NSMutableArray *results;
NSDictionary *row;
qualifier = [_fs qualifier];
sortOrderings = [_fs sortOrderings];
#if 0
[self logWithFormat:@"FETCH: %@", _flds];
[self logWithFormat:@" MATCH: %@", _q];
#endif
/* generate SQL */
sql = [NSMutableString stringWithCapacity:256];
@@ -1536,34 +1579,34 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@" LIMIT "]; // count
[sql appendString:@" OFFSET "]; // index from 0
#endif
/* open channel */
if ((channel = [self acquireAclChannel]) == nil) {
[self errorWithFormat:@"could not open acl channel!"];
return nil;
}
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
results = [NSMutableArray arrayWithCapacity:64];
attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[results addObject:row];
/* release channels */
[self releaseChannel: channel];
return results;
}
@@ -1602,7 +1645,7 @@ andAttribute: (EOAttribute *)_attribute
NSException *error;
NSMutableString *sql;
NSString *qSql;
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"DELETE FROM "];
[sql appendString:[self aclTableName]];
@@ -1620,16 +1663,16 @@ andAttribute: (EOAttribute *)_attribute
[self errorWithFormat:@"could not open acl channel!"];
return;
}
/* run SQL */
[[channel adaptorContext] beginTransaction];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return;
}
[[channel adaptorContext] commitTransaction];
[self releaseChannel:channel];
}
@@ -1647,11 +1690,11 @@ andAttribute: (EOAttribute *)_attribute
if ([GCSFolderManager singleStoreMode])
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@ WHERE c_folder_id = %@",
@"SELECT COUNT(*) AS cnt FROM %@ WHERE c_folder_id = %@",
[self storeTableName], folderId];
else
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@",
@"SELECT COUNT(*) AS cnt FROM %@",
[self storeTableName]];
if (excludeDeleted)
{
@@ -1666,7 +1709,56 @@ andAttribute: (EOAttribute *)_attribute
{
error = [channel evaluateExpressionX: sqlString];
if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error];
else
{
attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL];
count = [[row objectForKey: @"cnt"] unsignedIntValue];
[channel cancelFetch];
}
[self releaseChannel: channel];
}
return count;
}
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days
{
EOAdaptorChannel *channel;
NSArray *attrs;
NSCalendarDate *nowDate;
NSDictionary *row;
NSException *error;
NSMutableString *sqlString;
unsigned int delta, count;
count = 0;
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta < [nowDate timeIntervalSince1970])
delta = [nowDate timeIntervalSince1970] - delta;
else
delta = 0;
if ([GCSFolderManager singleStoreMode])
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS cnt FROM %@"
@" WHERE c_folder_id = %@ AND c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], folderId, delta];
else
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS cnt FROM %@"
@" WHERE c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], delta];
channel = [self acquireStoreChannel];
if (channel)
{
error = [channel evaluateExpressionX: sqlString];
if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error];
else
{
@@ -1716,7 +1808,7 @@ andAttribute: (EOAttribute *)_attribute
- (NSString *)description {
NSMutableString *ms;
id tmp;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
@@ -1729,7 +1821,7 @@ andAttribute: (EOAttribute *)_attribute
if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
if ((tmp = [self location]))
[ms appendFormat:@" loc=%@", [tmp absoluteString]];
[ms appendString:@">"];
return ms;
}
+1
View File
@@ -77,6 +77,7 @@
- (BOOL)folderExistsAtPath:(NSString *)_path;
- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag;
- (NSDictionary *) recordAtPath: (NSString *) _path;
- (GCSFolder *)folderAtPath:(NSString *)_path;
- (NSException *)createFolderOfType:(NSString *)_type withName:(NSString *)_name atPath:(NSString *)_path;
+8 -3
View File
@@ -784,7 +784,7 @@ static BOOL _singleStoreMode = NO;
return result;
}
- (GCSFolder *) folderAtPath: (NSString *) _path
- (NSDictionary *) recordAtPath: (NSString *) _path
{
NSMutableString *sql;
NSArray *fnames, *records;
@@ -841,8 +841,13 @@ static BOOL _singleStoreMode = NO;
[self debugWithFormat:@"found no record for path: '%@'", _path];
return nil;
}
return [self folderForRecord:record];
return record;
}
- (GCSFolder *) folderAtPath: (NSString *) _path
{
return [self folderForRecord: [self recordAtPath: _path]];
}
- (NSString *) baseTableNameWithUID: (NSString *) _uid
+1 -3
View File
@@ -1,8 +1,6 @@
/* NGVCardPhoto.h - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
+1 -3
View File
@@ -1,8 +1,6 @@
/* NGVCardPhoto.m - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
@@ -61,6 +61,8 @@
long i, count, repeatCount;
unsigned interval;
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
startDate = [_r startDate];
endDate = [_r endDate];
+1 -1
View File
@@ -141,7 +141,7 @@
[updatedProperties addObject:@"accessClass"];
if(!IS_EQUAL([_from sequence], [_to sequence], isEqualToNumber:))
[updatedProperties addObject:@"sequence"];
if(!IS_EQUAL([_from organizer], [_to organizer], isEqual:))
if(!IS_EQUAL([[_from organizer] rfc822Email], [[_to organizer] rfc822Email], isEqual:))
[updatedProperties addObject:@"organizer"];
if(!IS_EQUAL([_from recurrenceRules], [_to recurrenceRules], isEqual:))
[updatedProperties addObject:@"rrule"];
+2 -2
View File
@@ -108,9 +108,9 @@ extern NSString *iCalWeekDayString[];
/* parse complete iCal RRULE */
- (void) setRrule: (NSString *) _rrule; // TODO: weird name? (better: RRule?)
- (void) setRrule: (NSString *) _rrule;
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;;
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;
@end
+32 -1
View File
@@ -466,9 +466,16 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
- (iCalByDayMask *) byDayMask
{
NSArray *setPos;
NSString *day;
if (dayMask == nil && [[self byDay] length])
{
dayMask = [iCalByDayMask byDayMaskWithRuleString: [self byDay]];
day = [self byDay];
setPos = [self bySetPos];
if ([setPos count])
day = [NSString stringWithFormat: @"%@%@", [setPos lastObject], day];
dayMask = [iCalByDayMask byDayMaskWithRuleString: day];
[dayMask retain];
}
@@ -914,4 +921,28 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
return isEqual;
}
- (NSString *) description
{
NSMutableString *str;
str = [NSMutableString stringWithString: [self flattenedValuesForKey: @"freq"]];
if ([self repeatInterval] > 1)
[str appendFormat: @" interval of %i", [self repeatInterval]];
if ([self repeatCount])
[str appendFormat: @" for %i times", [self repeatCount]];
if ([self untilDate])
[str appendFormat: @" until %@", [self untilDate]];
if ([[self valuesAtIndex: 0 forKey: @"bymonthday"] count])
[str appendFormat: @" BYMONTHDAY:%@", [self flattenedValuesForKey: @"bymonthday"]];
if ([[self valuesAtIndex: 0 forKey: @"byday"] count])
[str appendFormat: @" BYDAY:%@", [self flattenedValuesForKey: @"byday"]];
if ([[self valuesAtIndex: 0 forKey: @"bymonth"] count])
[str appendFormat: @" BYMONTH:%@", [self flattenedValuesForKey: @"bymonth"]];
if ([[self valuesAtIndex: 0 forKey: @"bysetpos"] count])
[str appendFormat: @" BYSETPOS:%@", [self flattenedValuesForKey: @"bysetpos"]];
return str;
}
@end /* iCalRecurrenceRule */
@@ -70,7 +70,7 @@
iCalByDayMask *dayMask;
BOOL hasRepeatCount;
[self logWithFormat: @"Weekly %@", rrule];
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
startDate = [_r startDate];
+1 -1
View File
@@ -110,7 +110,7 @@
//SOGoTrustProxyAuthentication = NO;
//SOGoXSRFValidationEnabled = YES;
/* General */
/* General - SOGoTimeZone *MUST* be defined */
//SOGoLanguage = English;
//SOGoTimeZone = America/Montreal;
//SOGoCalendarDefaultRoles = (
@@ -0,0 +1,60 @@
"Inviting the following persons is prohibited:" = "לא ניתן להזמין את אנשי הקשר הבאים: ";
"Personal Calendar" = "לוח שנה אישי";
vevent_class0 = "(אירוע פומבי)";
vevent_class1 = "(אירוע פרטי)";
vevent_class2 = "(אירוע סודי)";
vtodo_class0 = "(משימה פומבית)";
vtodo_class1 = "(משימה פרטית)";
vtodo_class2 = "(משימה סודית)";
/* Receipts */
"The event \"%{Summary}\" was created" = "האירוע \"{Summary}%\" נוצר";
"The event \"%{Summary}\" was deleted" = "האירוע \"{Summary}%\" נמחק";
"The event \"%{Summary}\" was updated" = "האירוע \"{Summary}%\" עודכן";
"The following attendees(s) were notified" = "המשתתפים הבאים קיבלו התראה";
"The following attendees(s) were added" = "המשתתפים הבאים הוספו";
"The following attendees(s) were removed" = "המשתתפים הבאים הוסרו";
/* IMIP messages */
"calendar_label" = "לוח שנה";
"startDate_label" = "התחלה";
"endDate_label" = "סיום";
"due_label" = "תאריך להגשה";
"location_label" = "מיקום";
"summary_label" = "תקציר";
"comment_label" = "הערה";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "הזמנה לאירוע: \"{Summary}%\"";
"(sent by %{SentBy}) " = "(מאת {SentBy}%)";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "{Organizer}% {SentByText}% הוזמנתם ל {Summary}%.\n\nהתחלה: {StartDate}%\nסיום: {EndDate}%\nתאור: {Description}%";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "{Organizer}% {SentByText}% הוזמנתם ל {Summary}%.\n\nהתחלה: {StartDate}% ב {StartTime}%\nסיום: {EndDate}% ב {EndTime}%\nתאור: {Description}%";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "אירוע מבוטל: \"{Summary}%\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "{Organizer}% {SentByText}% ביטלו את האירוע: {Summary}%.\n\nהתחלה: {StartDate}%\nסיום: {EndDate}%\nתאור: {Description}%";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "{Organizer}% {SentByText}% ביטלו את האירוע: {Summary}%.\n\nהתחלה: {StartDate}% ב {StartTime}%\nסיום: {EndDate}% ב {EndTime}%\nתאור: {Description}%";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "הפגישה \"{Summary}%\" עבור {OldStartDate}% שונתה";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "הפגישה \"{Summary}%\" עבור {OldStartDate}% ב {OldStartTime}% השתנתה";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "הפרמטרים הבאים השתנו בפגישה \"{Summary}%\":";
"Please accept or decline those changes."
= "נא לאשר או לבטל את השינויים";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "הזמנה אושרה: \"{Summary}%\"";
"Declined invitation: \"%{Summary}\"" = "הזמנה נדחתה: \"{Summary}%\"";
"Delegated invitation: \"%{Summary}\"" = "הזמנה הואצלה: \"{Summary}%\"";
"Not yet decided on invitation: \"%{Summary}\"" = "טרם הוחלט לגבי הזמנה: \"{Summary}%\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "{Attendee}% {SentByText}% אישר את הזמנתך.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "{Attendee}% {SentByText}% דחה את הזמנתך.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "{Attendee}% {SentByText}% ההזמנה הואצלה ל {Delegate}%.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "{Attendee}% {SentByText}% טרם הוחלט לגבי הזמנתך.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "לא ניתן לגשת למשאב: \"{Cn}% {SystemEmail}%\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "כמות מקסימלית של הזמנות בו זמנית ({NumberOfSimultaneousBookings}%) עבור המשאב \"{Cn}% {SystemEmail}%\". קיימת התנגשות באירוע \"{EventTitle}%\", המתחיל ב {StartDate}%.";
+5 -3
View File
@@ -1,6 +1,6 @@
/* MSExchangeFreeBusy.m - this file is part of SOGo
*
* Copyright (C) 2012-2014 Inverse inc.
* Copyright (C) 2012-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -133,6 +133,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, [rawRequest UTF8String]);
//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
//curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function_freebusy);
@@ -141,8 +142,9 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
// Perform SOAP request
rc = curl_easy_perform(curl);
if (rc != 0)
[self errorWithFormat: @"CURL error while accessing %@ (%d): ", url, rc, [NSString stringWithCString: error]];
if (rc != CURLE_OK)
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", url, rc,
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
curl_easy_cleanup(curl);
curl_slist_free_all(headerlist);
@@ -1,5 +1,5 @@
/*
Copyright (C) 2012 Inverse inc.
Copyright (C) 2012-2016 Inverse inc.
This file is part of SOGo.
@@ -1,5 +1,5 @@
/*
Copyright (C) 2012 Inverse inc.
Copyright (C) 2012-2016 Inverse inc.
This file is part of SOGo.
@@ -28,6 +28,8 @@
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import "MSExchangeFreeBusySOAPRequest.h"
@@ -110,4 +112,19 @@
return [NSString stringWithFormat: @"%i", interval];
}
- (NSString *) bias
{
#if 0
NSTimeZone *userTimeZone;
NSInteger secs;
userTimeZone = [[[context activeUser] userDefaults] timeZone];
secs = [userTimeZone secondsFromGMT];
return [NSString stringWithFormat: @"%i", (secs/60)];
#else
return @"0";
#endif
}
@end
@@ -7,21 +7,21 @@
<GetUserAvailabilityRequest xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<t:TimeZone xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
<Bias>0</Bias>
<StandardTime>
<Bias>0</Bias>
<Time>00:00:00</Time>
<DayOrder>1</DayOrder>
<Month>1</Month>
<DayOfWeek>Sunday</DayOfWeek>
</StandardTime>
<DaylightTime>
<Bias>0</Bias>
<Time>00:00:00</Time>
<DayOrder>1</DayOrder>
<Month>1</Month>
<DayOfWeek>Sunday</DayOfWeek>
</DaylightTime>
<Bias><#bias/></Bias>
<StandardTime>
<Bias>0</Bias>
<Time>02:00:00</Time>
<DayOrder>1</DayOrder>
<Month>11</Month>
<DayOfWeek>Sunday</DayOfWeek>
</StandardTime>
<DaylightTime>
<Bias>-60</Bias>
<Time>02:00:00</Time>
<DayOrder>2</DayOrder>
<Month>3</Month>
<DayOfWeek>Sunday</DayOfWeek>
</DaylightTime>
</t:TimeZone>
<MailboxDataArray>
<t:MailboxData>
@@ -3,6 +3,11 @@ serverVersion: WOString {
escapeHTML = NO;
}
bias: WOString {
value = bias;
escapeHTML = NO;
}
address: WOString {
value = address;
escapeHTML = NO;
+15 -10
View File
@@ -696,7 +696,7 @@ static Class iCalEventK = nil;
if ([title length])
[baseWhere
addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString]]];
if (component)
{
@@ -976,7 +976,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSCalendarDate *recurrenceId, *masterEndDate, *endDate;
NSMutableDictionary *newRecord;
NGCalendarDateRange *newRecordRange;
NSComparisonResult compare;
NSNumber *dateSecs;
id master;
@@ -1437,14 +1436,14 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
{
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString],
[title asSafeSQLString],
[title asSafeSQLString]]];
}
}
else
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString]]];
}
/* prepare mandatory fields */
@@ -2620,7 +2619,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
if (uid && folder)
{
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_uid = %@",
uid];
[uid asSafeSQLString]];
records = [folder fetchFields: nameFields matchingQualifier: qualifier];
count = [records count];
if (count)
@@ -3238,6 +3237,12 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
}
}
// If the UID isn't ending with the ".ics" extension, let's add it to avoid
// confusing broken CalDAV client (like Nokia N9 and Korganizer) that relies
// on this (see #2308)
if (![[uid lowercaseString] hasSuffix: @".ics"])
uid = [NSString stringWithFormat: @"%@.ics", uid];
object = [SOGoAppointmentObject objectWithName: uid
inContainer: self];
[object setIsNew: YES];
@@ -3317,7 +3322,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
else
{
// If the start date is a "floating time", let's use the user's timezone
// during the import for both the start and end dates.
// during the import for both the start and end dates. This is similar
// to what we do in SOGoAppointmentObject: -_adjustFloatingTimeInRequestCalendar:
NSString *s;
s = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
@@ -3401,8 +3407,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
timezone: timezone]))
{
imported++;
[uids setValue: uid
forKey: originalUid];
[uids setValue: uid forKey: originalUid];
}
}
@@ -36,6 +36,9 @@
#import "SOGoCalendarComponent.h"
@interface SOGoAppointmentObject : SOGoCalendarComponent
{
BOOL _resourceHasAutoAccepted;
}
- (NSException *) changeParticipationStatus: (NSString *) status
withDelegate: (iCalPerson *) delegate
@@ -57,6 +60,8 @@
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
fromRequest: (WORequest *) rq;
- (BOOL) resourceHasAutoAccepted;
@end
#endif /* __Appointments_SOGoAppointmentObject_H__ */
+222 -132
View File
@@ -369,6 +369,52 @@
return listHasChanged;
}
//
//
//
- (BOOL) _shouldScheduleEvent: (iCalPerson *) thePerson
{
//NSArray *userAgents;
NSString *v;
BOOL b;
//int i;
b = YES;
if (thePerson && (v = [thePerson value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
{
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
b = NO;
}
//
// If we have to deal with Thunderbird/Lightning, we always send invitation
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
// to NONE/CLIENT when responding to an external invitation received by
// SOGo - so no invitation responses are ever sent by Lightning. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
//
// This code has been disabled - see 0003274.
//
#if 0
userAgents = [[context request] headersForKey: @"User-Agent"];
for (i = 0; i < [userAgents count]; i++)
{
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
{
b = YES;
break;
}
}
#endif
return b;
}
//
//
//
@@ -394,11 +440,12 @@
owner: owner];
}
[self sendEMailUsingTemplateNamed: @"Update"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: updateAttendees
withType: @"calendar:invitation-update"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Update"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: updateAttendees
withType: @"calendar:invitation-update"];
}
// This method scans the list of attendees.
@@ -503,7 +550,9 @@
NSEnumerator *enumerator;
NSString *currentUID;
SOGoUser *user, *currentUser;
_resourceHasAutoAccepted = NO;
// Build a list of the attendees uids
attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]];
enumerator = [theAttendees objectEnumerator];
@@ -643,6 +692,7 @@
{
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
_resourceHasAutoAccepted = YES;
}
}
else
@@ -713,6 +763,7 @@
// set the resource as one!
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
_resourceHasAutoAccepted = YES;
}
} // if ([user isResource]) ...
@@ -823,11 +874,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
[self _handleRemovedUsers: deletedAttendees
withRecurrenceId: [newEvent recurrenceId]];
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [newEvent itipEntryWithMethod: @"cancel"]
previousObject: oldEvent
toAttendees: deletedAttendees
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [newEvent itipEntryWithMethod: @"cancel"]
previousObject: oldEvent
toAttendees: deletedAttendees
withType: @"calendar:cancellation"];
}
if ((ex = [self _handleAttendeesConflicts: [newEvent attendees] forEvent: newEvent force: forceSave]))
@@ -883,12 +935,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// Send an invitation to new attendees
if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent force: forceSave]))
return ex;
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: addedAttendees
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: addedAttendees
withType: @"calendar:invitation"];
}
if ([changes hasMajorChanges])
@@ -962,11 +1015,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([attendees count])
{
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
[self sendReceiptEmailForObject: newEvent
@@ -1166,19 +1220,17 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
statusChange: (NSString *) newStatus
inEvent: (iCalEvent *) event
{
iCalPerson *otherAttendee, *otherDelegate;
NSString *currentStatus, *organizerUID;
SOGoUser *ownerUser, *currentUser;
NSString *delegateEmail;
NSException *ex;
ex = nil;
BOOL addDelegate, removeDelegate;
currentStatus = [attendee partStat];
iCalPerson *otherAttendee, *otherDelegate;
NSString *delegateEmail;
BOOL addDelegate, removeDelegate;
otherAttendee = attendee;
ex = nil;
delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length])
@@ -1189,7 +1241,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
otherDelegate = nil;
/* We handle the addition/deletion of delegate users */
// We handle the addition/deletion of delegate users
addDelegate = NO;
removeDelegate = NO;
if (delegate)
@@ -1272,12 +1324,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
otherDelegate = nil;
}
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [event itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [event itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:cancellation"];
} // if (removeDelegate)
if (addDelegate)
@@ -1291,12 +1344,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self _addOrUpdateEvent: event
forUID: delegatedUID
owner: [theOwnerUser login]];
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:invitation"];
} // if (addDelegate)
// If the current user isn't the organizer of the event
@@ -1477,9 +1531,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]];
event = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
// If no occurence found, create one
if (event == nil)
// If no occurence found, create one
event = (iCalEvent*)[self newOccurenceWithID: recurrenceTime];
}
else
@@ -1558,50 +1612,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
return ex;
}
//
//
//
- (BOOL) _shouldScheduleEvent: (iCalPerson *) theOrganizer
{
NSArray *userAgents;
NSString *v;
BOOL b;
int i;
b = YES;
if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
{
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
b = NO;
}
//
// If we have to deal with Thunderbird/Lightning, we always send invitation
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
// to NONE/CLIENT when responding to an external invitation received by
// SOGo - so no invitation responses are ever sent by Lightning. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
//
userAgents = [[context request] headersForKey: @"User-Agent"];
for (i = 0; i < [userAgents count]; i++)
{
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
{
b = YES;
break;
}
}
return b;
}
//
//
//
@@ -1613,14 +1623,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
iCalEvent *event;
BOOL send_receipt;
ownerUser = [SOGoUser userWithLogin: owner];
event = [self component: NO secure: NO];
send_receipt = YES;
if (![self _shouldScheduleEvent: [event organizer]])
return;
if (occurence == nil)
{
// No occurence specified; use the master event.
@@ -1646,11 +1652,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// and send them an email.
[self _handleRemovedUsers: attendees
withRecurrenceId: recurrenceId];
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [occurence itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [occurence itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:cancellation"];
}
}
else if ([occurence userIsAttendee: ownerUser])
@@ -1664,12 +1672,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
send_receipt = NO;
}
if (send_receipt)
[self sendReceiptEmailForObject: event
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
operation: EventDeleted];
if (send_receipt)
[self sendReceiptEmailForObject: event
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
operation: EventDeleted];
}
- (NSException *) prepareDelete
@@ -1853,6 +1861,54 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
}
}
//
// This is similar to what we do in SOGoAppointmentFolder: -importCalendar:
//
- (void) _adjustFloatingTimeInRequestCalendar: (iCalCalendar *) rqCalendar
{
iCalDateTime *startDate, *endDate;
NSString *startDateAsString;
SOGoUserDefaults *ud;
NSArray *allEvents;
iCalTimeZone *tz;
iCalEvent *event;
int i, delta;
allEvents = [rqCalendar events];
for (i = 0; i < [allEvents count]; i++)
{
event = [allEvents objectAtIndex: i];
if ([event isAllDay])
continue;
startDate = (iCalDateTime *)[event uniqueChildWithTag: @"dtstart"];
startDateAsString = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
if (![startDate timeZone] &&
![startDateAsString hasSuffix: @"Z"] &&
![startDateAsString hasSuffix: @"z"])
{
ud = [[context activeUser] userDefaults];
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
if ([rqCalendar addTimeZone: tz])
{
delta = [[tz periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[startDate setTimeZone: tz];
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
if (endDate)
{
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[endDate setTimeZone: tz];
}
}
}
}
}
- (void) _decomposeGroupsInRequestCalendar: (iCalCalendar *) rqCalendar
{
NSArray *allEvents;
@@ -1942,9 +1998,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
fromRequest: (WORequest *) rq
{
SOGoUser *ownerUser;
NSException *ex;
NSArray *roles;
SOGoUser *ownerUser;
BOOL userIsOrganizer;
if (calendar == fullCalendar || calendar == safeCalendar
|| calendar == originalCalendar)
@@ -1974,6 +2032,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self _adjustEventsInRequestCalendar: calendar];
[self adjustClassificationInRequestCalendar: calendar];
[self _adjustPartStatInRequestCalendar: calendar];
[self _adjustFloatingTimeInRequestCalendar: calendar];
}
//
@@ -1984,13 +2043,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
iCalEvent *event;
NSArray *attendees;
NSString *eventUID;
BOOL scheduling;
attendees = nil;
event = [[calendar events] objectAtIndex: 0];
eventUID = [event uid];
scheduling = [self _shouldScheduleEvent: [event organizer]];
attendees = nil;
// make sure eventUID doesn't conflict with an existing event - see bug #1853
// TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1)
@@ -2003,34 +2059,51 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
// New event and we're the organizer -- send invitation to all attendees
//
if (scheduling && [event userIsOrganizer: ownerUser])
{
attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count])
{
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
return ex;
else
{
// We might have auto-accepted resources here. If that's the
// case, let's regenerate the versitstring and replace the
// one from the request.
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
}
userIsOrganizer = [event userIsOrganizer: ownerUser];
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
if (!userIsOrganizer &&
[[context activeUser] hasEmail: [[event organizer] rfc822Email]])
{
[[event organizer] setCn: [ownerUser cn]];
[[event organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[event organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
userIsOrganizer = YES;
}
if (userIsOrganizer)
{
attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count])
{
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
return ex;
else
{
// We might have auto-accepted resources here. If that's the
// case, let's regenerate the versitstring and replace the
// one from the request.
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
}
//
// We aren't the organizer but we're an attendee. That can happen when
// we receive an external invitation (IMIP/ITIP) and we accept it
// from a CUA - it gets added to a specific CalDAV calendar using a PUT
//
else if (scheduling && [event userIsAttendee: ownerUser])
else if ([event userIsAttendee: ownerUser] && [self _shouldScheduleEvent: [event userAsAttendee: ownerUser]])
{
[self sendIMIPReplyForEvent: event
from: ownerUser
@@ -2138,8 +2211,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
if ([[newEvent attendees] count] || [[oldEvent attendees] count])
{
BOOL userIsOrganizer;
// newEvent might be nil here, if we're deleting a RECURRENCE-ID with attendees
// If that's the case, we use the oldEvent to obtain the organizer
if (newEvent)
@@ -2147,6 +2218,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
userIsOrganizer = [oldEvent userIsOrganizer: ownerUser];
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
if (!userIsOrganizer &&
[[context activeUser] hasEmail: [[newEvent organizer] rfc822Email]])
{
[[newEvent organizer] setCn: [ownerUser cn]];
[[newEvent organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[newEvent organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
userIsOrganizer = YES;
}
// With Thunderbird 10, if you create a recurring event with an exception
// occurence, and invite someone, the PUT will have the organizer in the
// recurrence-id and not in the master event. We must fix this, otherwise
@@ -2344,4 +2429,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
return response;
}
- (BOOL) resourceHasAutoAccepted
{
return _resourceHasAutoAccepted;
}
@end /* SOGoAppointmentObject */
+16 -2
View File
@@ -232,7 +232,7 @@
type, classification]
inContext: context];
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", nil];
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", @"RECURRENCE-ID", nil];
uid = [[component uid] asCryptedPassUsingScheme: @"ssha256"
withSalt: [[settings userSalt] dataUsingEncoding: NSASCIIStringEncoding]
andEncoding: encHex];
@@ -619,7 +619,10 @@
if (content)
ASSIGN (originalCalendar, [iCalCalendar parseSingleFromSource: content]);
else
[self warnWithFormat: @"content not available, we will crash"];
{
[self warnWithFormat: @"content not available, we don't update the event"];
return;
}
}
oldMaster = (iCalRepeatableEntityObject *)
@@ -794,6 +797,14 @@
if ([object firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"])
return;
// We never send IMIP inivitaton/deletion/update when the "initiator" is Outlook 2013/2016 over
// the EAS protocol. That is because Outlook will always issue a SendMail command
// with the meeting details (ie., IMIP message with METHOD:REQUEST) so there's
// no need to send it twice. Moreover, Outlook users can also choose to NOT send
// the IMIP messsage at all, so SOGo won't send one without user's consent
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
return;
ownerUser = [SOGoUser userWithLogin: owner];
dd = [ownerUser domainDefaults];
if ([dd appointmentSendEMailNotifications] && [object isStillRelevant])
@@ -933,6 +944,9 @@
/* get WOApplication instance */
app = [WOApplication application];
/* remove all alarms to avoid bug #3925 */
[event removeAllAlarms];
/* construct message content */
p = [app pageWithName: @"SOGoAptMailICalReply" inContext: context];
[p setApt: (iCalEvent *) event];
@@ -32,10 +32,6 @@
+ (id) sharedEMailAlarmsManager;
/* PUT */
- (void) handleAlarmsInCalendar: (iCalCalendar *) calendar
fromComponent: (SOGoCalendarComponent *) component;
/* DELETE */
- (void) deleteAlarmsFromComponent: (SOGoCalendarComponent *) component;
@@ -25,6 +25,8 @@
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
@@ -34,6 +36,9 @@
#import <GDLContentStore/GCSFolder.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentFolders.h"
#import "SOGoCalendarComponent.h"
+1
View File
@@ -21,6 +21,7 @@
#import <Foundation/NSCalendarDate.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -124,14 +124,14 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
url = [NSURL URLWithString: location];
if (url)
{
curl_global_init (CURL_GLOBAL_SSL);
curl = curl_easy_init ();
curl_global_init(CURL_GLOBAL_SSL);
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt (curl, CURLOPT_URL, [location UTF8String]);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt (curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_URL, [location UTF8String]);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
authInfos = [self _loadAuthData];
@@ -145,17 +145,17 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
/* buffering ivar, no need to retain/release */
buffer = [NSMutableData data];
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
error[0] = 0;
curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, &error);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
// Perform SOAP request
rc = curl_easy_perform (curl);
if (rc == 0)
rc = curl_easy_perform(curl);
if (rc == CURLE_OK)
{
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
[result setObject: [NSNumber numberWithUnsignedInt: status]
forKey: @"status"];
[self logWithFormat: @"Load web calendar %@ (%i)", location, status];
@@ -188,7 +188,11 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
[result setObject: @"http-error" forKey: @"error"];
}
else
[result setObject: @"bad-url" forKey: @"error"];
{
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", location, rc,
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
[result setObject: @"bad-url" forKey: @"error"];
}
curl_easy_cleanup (curl);
}
}
@@ -35,9 +35,9 @@ vtodo_class2 = "(Gizli görev)";
= "%{Organizer} %{SentByText}şu etkinliği iptal etti: %{Summary}.\n\nBaşlangıç: %{StartDate} saat %{StartTime}\nBitiş: %{EndDate} saat %{EndTime}\nTanım: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "\"%{Summary}\" isimli, %{OldStartDate} tarihli çalışma değişti";
= "%{OldStartDate} tarihindeki \"%{Summary}\" çalışması değişti";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "\"%{Summary}\" isimli, %{OldStartDate} tarihli ve %{OldStartTime} saatli çalışma değişti";
= " %{OldStartDate} tarihli ve %{OldStartTime} başlangıç saatli \"%{Summary}\" çalışması değişti";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "\"%{Summary}\" buluşmasının şu özellikleri değişti:";
"Please accept or decline those changes."
@@ -45,16 +45,16 @@ vtodo_class2 = "(Gizli görev)";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Davet kabul edilidi: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Davet Geri Çevrildi: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Temsilci atanmış davet: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Vekalet edilen davet: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Davet hakkında henüz karar verilmedi: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}etkinlik davetinizi kabul etti.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText}davetinizi reddetti.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText} davete temsilci atadı: %{Delegate} ";
= "%{Attendee} %{SentByText} tarafından %{Delegate} davete vekil olarak atadı.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}henüz etkinlik davetinizle ilgili kararını vermedi.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kaynağa ulaşılamıyor: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" kanağı için maksimum eşzamanlı kayıt adedine (%{NumberOfSimultaneousBookings}) ulaşıldı. Çakışan \"%{EventTitle}\" etkinlik, %{StartDate} tarihinde başlıyor.";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" kaynağı için maksimum eşzamanlı kayıt adedine (%{NumberOfSimultaneousBookings}) ulaşıldı. Çakışan \"%{EventTitle}\" etkinlik, %{StartDate} tarihinde başlıyor.";
@@ -140,7 +140,7 @@ NSNumber *iCalDistantFutureNumber = nil;
[currentAttendee rfc822Email], @"email",
([[currentAttendee cnWithoutQuotes] length] ? [currentAttendee cnWithoutQuotes] : [currentAttendee rfc822Email]) , @"name",
nil];
if ((uid = [currentAttendee uid]))
if ((uid = [currentAttendee uidInContext: context]))
{
[attendeeData setObject: uid forKey: @"uid"];
}
@@ -156,12 +156,12 @@ NSNumber *iCalDistantFutureNumber = nil;
if ([source conformsToProtocol: @protocol (SOGoDNSource)] &&
[[(NSObject <SOGoDNSource>*) source MSExchangeHostname] length])
{
uid = [NSString stringWithFormat: @"%@:%@", [[context activeUser] login],
[contactData valueForKey: @"c_uid"]];
[attendeeData setObject: uid forKey: @"uid"];
[attendeeData setObject: [NSNumber numberWithInt: 1] forKey: @"isMSExchange"];
[attendeeData setObject: [contactData valueForKey: @"c_uid"] forKey: @"uid"];
}
}
}
[attendeeData setObject: [[currentAttendee partStat] lowercaseString] forKey: @"partstat"];
[attendeeData setObject: [[currentAttendee role] lowercaseString] forKey: @"role"];
if ([[currentAttendee delegatedTo] length])
@@ -861,7 +861,7 @@ NSNumber *iCalDistantFutureNumber = nil;
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
// Delete old email alarms
if (alarm_number >= 0)
if (!nextAlarmDate && alarm_number >= 0)
[af deleteRecordForEntryWithCName: nameInContainer
inCalendarAtPath: [theContainer ocsPath]];
}
@@ -0,0 +1,2 @@
"Personal Address Book" = "אנשי קשר";
"Collected Address Book" = "אנשי קשר אשר נוצרו משליחת הודעה";
+1
View File
@@ -40,6 +40,7 @@
- (NSString *) workCompany;
- (NSString *) fullName;
- (NSArray *) emails;
- (NSArray *) secondaryEmails;
- (NSString *) workPhone;
+12
View File
@@ -21,6 +21,7 @@
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGCards/NSArray+NGCards.h>
@@ -235,12 +236,19 @@ convention:
- (void) _setEmails: (NSDictionary *) ldifRecord
{
CardElement *homeMail;
NSString* mail;
// Emails from the configured mail fields of the source have already been extracted in
// [LDAPSource _fillEmailsOfEntry:intoLDIFRecord:]
[self addElementWithTag: @"email"
ofType: @"work"
withValue: [ldifRecord objectForKey: @"c_emails"]];
// When importing an LDIF file, add the default mail attribute
mail = [ldifRecord objectForKey: @"mail"];
if ([mail length] && ![[self emails] containsObject: mail])
[self addElementWithTag: @"email"
ofType: @"work"
withValue: [ldifRecord objectForKey: @"mail"]];
homeMail = [self elementWithTag: @"email" ofType: @"home"];
[homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""];
[[self uniqueChildWithTag: @"x-mozilla-html"]
@@ -358,6 +366,10 @@ convention:
else
[self setCategories: [o componentsSeparatedByString: @","]];
// Photo
if ([ldifRecord objectForKey: @"photo"])
[self setPhoto: [[ldifRecord objectForKey: @"photo"] stringByEncodingBase64]];
[self cleanupEmptyChildren];
}
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSDictionary+LDIF.h - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSDictionary+LDIF.m - this file is part of SOGo
*
* Copyright (C) 2011-2012 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSString+LDIF.h - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSString+LDIF.m - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoContactEntryPhoto.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+15 -8
View File
@@ -1,8 +1,6 @@
/* SOGoContactEntryPhoto.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,25 +40,34 @@
- (id) GETAction: (WOContext *) localContext
{
NGVCardPhoto *photo;
NSData *data;
NSString *uri;
NSData *data;
id response;
photo = [self photo];
data = nil;
uri = nil;
if ([photo isInline])
data = [photo decodedContent];
else
data = [[photo flattenedValuesForKey: @""]
dataUsingEncoding: NSISOLatin1StringEncoding];
else if ([[photo value: 0 ofAttribute: @"value"] isEqualToString: @"uri"])
uri = [photo flattenedValuesForKey: @""];
if (data)
{
response = [localContext response];
[response setHeader: [self davContentType] forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat:@" %d",
(int)[data length]]
forKey: @"content-length"];
[response setContent: data];
}
else if (uri)
{
response = [localContext response];
[response setStatus: 302];
[response setHeader: uri forKey: @"location"];
}
else
response = nil;
+1 -1
View File
@@ -1,6 +1,6 @@
/* SOGoContactGCSEntry.h - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+2 -2
View File
@@ -1,6 +1,6 @@
/* SOGoContactGCSEntry.h - this file is part of SOGo
/* SOGoContactGCSEntry.m - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+3 -5
View File
@@ -121,7 +121,7 @@ static NSArray *folderListingFields = nil;
NSString *component;
Class objectClass;
qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_name = %@", [name asSafeSQLString]];
records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
matchingQualifier: qualifier];
@@ -190,8 +190,7 @@ static NSArray *folderListingFields = nil;
if ([filter length] > 0)
{
filter = [[filter stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'\\'"];
filter = [filter asSafeSQLString];
if ([criteria isEqualToString: @"name_or_address"])
qs = [NSString stringWithFormat:
@"(c_sn isCaseInsensitiveLike: '%%%@%%') OR "
@@ -338,8 +337,7 @@ static NSArray *folderListingFields = nil;
if (aName && [aName length] > 0)
{
aName = [[aName stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'\\'"];
aName = [aName asSafeSQLString];
qs = [NSString stringWithFormat: @"(c_name='%@')", aName];
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoContactGCSList.h - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -2
View File
@@ -1,7 +1,6 @@
/* SOGoContactLDIFEntry.h - this file is part of SOGo
* Copyright (C) 2006-2011 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+24 -2
View File
@@ -1,6 +1,6 @@
/* SOGoContactLDIFEntry.m - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
#import <SOGo/SOGoPermissions.h>
#import "NGVCard+SOGo.h"
#import "SOGoContactEntryPhoto.h"
#import "SOGoContactGCSEntry.h"
#import "SOGoContactLDIFEntry.h"
#import "SOGoContactSourceFolder.h"
@@ -141,7 +142,7 @@
- (BOOL) hasPhoto
{
return NO;
return ([ldifEntry objectForKey: @"photo"] != nil);
}
- (NSString *) davEntityTag
@@ -227,4 +228,25 @@
return [newContact saveComponent: newCard];
}
- (id) lookupName: (NSString *) lookupName
inContext: (id) localContext
acquire: (BOOL) acquire
{
id obj;
if ([lookupName isEqualToString: @"photo"])
{
if ([self hasPhoto])
obj = [SOGoContactEntryPhoto objectWithName: lookupName
inContainer: self];
else
obj = nil;
}
else
obj = [super lookupName: lookupName inContext: localContext
acquire: acquire];
return obj;
}
@end
@@ -338,6 +338,10 @@
[newRecord setObject: data forKey: @"contactInfo"];
}
// photo => hasPhoto
data = [oldRecord objectForKey: @"photo"];
[newRecord setObject: [NSNumber numberWithInt: [data length] ? 1 : 0] forKey: @"hasPhoto"];
recordSource = [oldRecord objectForKey: @"source"];
if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] &&
[[(NSObject <SOGoDNSource>*) recordSource MSExchangeHostname] length])
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoUserFolder+Appointments.h - this file is part of SOGo
*
* Copyright (C) 2009 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2009-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+2
View File
@@ -69,6 +69,8 @@ Mailer_RESOURCE_FILES += \
SOGoMailCroatianReply.wo \
SOGoMailGermanForward.wo \
SOGoMailGermanReply.wo \
SOGoMailHebrewForward.wo \
SOGoMailHebrewReply.wo \
SOGoMailHungarianForward.wo \
SOGoMailHungarianReply.wo \
SOGoMailIcelandicForward.wo \
@@ -0,0 +1,2 @@
"OtherUsersFolderName" = "משתשמשים אחרים";
"SharedFoldersName" = "תיקיות משותפות";
+1
View File
@@ -33,6 +33,7 @@
fromIndex: (int) start;
- (int) indexOf: (unichar) _c;
- (NSString *) decodedHeader;
- (NSString *) asSafeFilename;
@end
+50 -15
View File
@@ -48,11 +48,11 @@
@interface _SOGoHTMLContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
{
NSMutableArray *images;
NSArray *ignoreContentTags;
NSArray *specialTreatmentTags;
NSArray *voidTags;
BOOL ignoreContent;
BOOL orderedList;
BOOL unorderedList;
@@ -289,7 +289,7 @@
//
if ([value length] > 5 && [[value substringToIndex: 5] caseInsensitiveCompare: @"data:"] == NSOrderedSame)
{
NSString *uniqueId, *mimeType, *encoding;
NSString *uniqueId, *mimeType, *encoding, *attrName;
NGMimeBodyPart *bodyPart;
NGMutableHashMap *map;
NSData *data;
@@ -300,7 +300,7 @@
i = [value indexOf: ';'];
j = [value indexOf: ';' fromIndex: i+1];
k = [value indexOf: ','];
// We try to get the MIME type
mimeType = nil;
@@ -314,7 +314,7 @@
// We might get a stupid value. We discard anything that doesn't have a / in it
if ([mimeType indexOf: '/'] < 0)
mimeType = @"image/jpeg";
// We check and skip the charset
if (j < i)
j = i;
@@ -335,17 +335,30 @@
[map setObject: [NSString stringWithFormat: @"inline; filename=\"%@\"", uniqueId] forKey: @"content-disposition"];
[map setObject: [NSString stringWithFormat: @"%@; name=\"%@\"", mimeType, uniqueId] forKey: @"content-type"];
[map setObject: [NSString stringWithFormat: @"<%@>", uniqueId] forKey: @"content-id"];
body = [[NGMimeFileData alloc] initWithBytes: [data bytes] length: [data length]];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: body];
[body release];
[images addObject: bodyPart];
[result appendFormat: @"<img src=\"cid:%@\" type=\"%@\"/>", uniqueId, mimeType];
[result appendFormat: @"<img src=\"cid:%@\" type=\"%@\"", uniqueId, mimeType];
// Restore img attributes
for (i = 0; i < [attributes count]; i++)
{
attrName = [[attributes rawNameAtIndex: i] lowercaseString];
if (![attrName isEqualToString: @"src"] && ![attrName isEqualToString: @"type"])
{
value = [attributes valueAtIndex: i];
[result appendFormat: @" %@=\"%@\"", attrName, value];
}
}
[result appendString: @"/>"];
}
}
else if (voidTags)
@@ -643,19 +656,19 @@ convertChars (const char *oldString, unsigned int oldLength,
fromIndex: (int) start
{
int i, len;
len = [self length];
if (start < 0 || start >= len)
start = 0;
for (i = start; i < len; i++)
{
if ([self characterAtIndex: i] == _c) return i;
}
return -1;
}
- (int) indexOf: (unichar) _c
@@ -671,8 +684,30 @@ convertChars (const char *oldString, unsigned int oldLength,
decodedHeader];
if (!decodedHeader)
decodedHeader = self;
return decodedHeader;
}
- (NSString *) asSafeFilename
{
NSRange r;
NSMutableString *safeName;
r = [self rangeOfString: @"\\"
options: NSBackwardsSearch];
if (r.length > 0)
safeName = [NSMutableString stringWithString: [self substringFromIndex: r.location + 1]];
else
safeName = [NSMutableString stringWithString: self];
[safeName replaceString: @"/" withString: @"_"];
if ([self isEqualToString: @"."])
return @"_";
if ([self isEqualToString: @".."])
return @"__";
return safeName;
}
@end
-1
View File
@@ -97,7 +97,6 @@
/* attachments */
- (NSArray *) fetchAttachmentAttrs;
- (BOOL) isValidAttachmentName: (NSString *) _name;
- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name;
- (NSString *) pathToAttachmentWithName: (NSString *) _name;
- (NSException *) saveAttachment: (NSData *) _attach
+132 -310
View File
@@ -55,6 +55,8 @@
#import <SOGo/SOGoMailer.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <NGCards/NGVCard.h>
@@ -74,7 +76,7 @@
static NSString *contentTypeValue = @"text/plain; charset=utf-8";
static NSString *htmlContentTypeValue = @"text/html; charset=utf-8";
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
@"from", @"replyTo", @"message-id",
nil};
@@ -86,111 +88,6 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
@end
//
// Useful extension that comes from Pantomime which is also
// released under the LGPL. We should eventually merge
// this with the same category found in SOPE's NGSmtpClient.m
// or simply drop sope-mime in favor of Pantomime
//
@interface NSMutableData (DataCleanupExtension)
- (unichar) characterAtIndex: (int) theIndex;
- (NSRange) rangeOfCString: (const char *) theCString;
- (NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange;
@end
@implementation NSMutableData (DataCleanupExtension)
- (unichar) characterAtIndex: (int) theIndex
{
const char *bytes;
int i, len;
len = [self length];
if (len == 0 || theIndex >= len)
{
[[NSException exceptionWithName: NSRangeException
reason: @"Index out of range."
userInfo: nil] raise];
return (unichar)0;
}
bytes = [self bytes];
for (i = 0; i < theIndex; i++)
{
bytes++;
}
return (unichar)*bytes;
}
- (NSRange) rangeOfCString: (const char *) theCString
{
return [self rangeOfCString: theCString
options: 0
range: NSMakeRange(0,[self length])];
}
-(NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange
{
const char *b, *bytes;
int i, len, slen;
if (!theCString)
{
return NSMakeRange(NSNotFound,0);
}
bytes = [self bytes];
len = [self length];
slen = strlen(theCString);
b = bytes;
if (len > theRange.location + theRange.length)
{
len = theRange.location + theRange.length;
}
if (theOptions == NSCaseInsensitiveSearch)
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!strncasecmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
else
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!memcmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
return NSMakeRange(NSNotFound,0);
}
@end
//
//
//
@@ -271,7 +168,7 @@ static NSString *userAgent = nil;
NSFileManager *fm;
fm = [NSFileManager defaultManager];
return ([fm createDirectoriesAtPath: [container userSpoolFolderPath]
attributes: nil]
&& [fm createDirectoriesAtPath: [self draftFolderPath]
@@ -308,7 +205,7 @@ static NSString *userAgent = nil;
messageID = [NSString generateMessageID];
[headers setObject: messageID forKey: @"message-id"];
}
priority = [newHeaders objectForKey: @"X-Priority"];
if (priority)
{
@@ -641,6 +538,13 @@ static NSString *userAgent = nil;
error = nil;
message = [self mimeMessageAsData];
if (!message)
{
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Message is too big"];
return error;
}
client = [[self imap4Connection] client];
if (![imap4 doesMailboxExistAtURL: [container imap4URL]])
@@ -649,7 +553,7 @@ static NSString *userAgent = nil;
atURL: [[self mailAccountFolder] imap4URL]];
[imap4 flushFolderHierarchyCache];
}
folder = [imap4 imap4FolderNameForURL: [container imap4URL]];
result = [client append: message toFolder: folder
withFlags: [NSArray arrayWithObjects: @"draft", nil]];
@@ -745,7 +649,7 @@ static NSString *userAgent = nil;
- if there is a 'reply-to' header, only include that (as TO)
- if we reply to all, all non-from addresses are added as CC
- the from is always the lone TO (except for reply-to)
Note: we cannot check reply-to, because Cyrus even sets a reply-to in the
envelope if none is contained in the message itself! (bug or
feature?)
@@ -776,11 +680,11 @@ static NSString *userAgent = nil;
int i;
identities = [[[self container] mailAccountFolder] identities];
for (i = 0; i < [identities count]; i++)
{
email = [[identities objectAtIndex: i] objectForKey: @"email"];
if (email)
[allRecipients addObject: email];
}
@@ -838,66 +742,29 @@ static NSString *userAgent = nil;
[self _purgeRecipients: allRecipients
fromAddresses: addrs];
[self _addEMailsOfAddresses: addrs toArray: to];
[_info setObject: to forKey: @"cc"];
[to release];
}
}
//
//
//
- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
fromResponseFetch: (NSDictionary *) fetch;
{
NSEnumerator *attachmentKeys;
NSMutableArray *bodies;
NSString *currentKey;
NSDictionary *body;
bodies = [NSMutableArray array];
attachmentKeys = [paths objectEnumerator];
while ((currentKey = [attachmentKeys nextObject]))
{
body = [fetch objectForKey: [currentKey lowercaseString]];
if (body)
[bodies addObject: [body objectForKey: @"data"]];
else
[bodies addObject: [NSData data]];
}
return bodies;
}
//
//
//
- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail
{
unsigned int count, max;
NSArray *parts, *paths, *bodies;
NSData *body;
unsigned int max, count;
NSArray *attachments;
NSDictionary *currentInfo;
NGHashMap *response;
parts = [sourceMail fetchFileAttachmentKeys];
max = [parts count];
if (max > 0)
attachments = [sourceMail fetchFileAttachments];
max = [attachments count];
for (count = 0; count < max; count++)
{
paths = [parts keysWithFormat: @"BODY[%{path}]"];
response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"];
bodies = [self _attachmentBodiesFromPaths: paths
fromResponseFetch: [response objectForKey: @"fetch"]];
for (count = 0; count < max; count++)
{
currentInfo = [parts objectAtIndex: count];
body = [[bodies objectAtIndex: count]
bodyDataFromEncoding: [currentInfo
objectForKey: @"encoding"]];
[self saveAttachment: body withMetadata: currentInfo];
}
currentInfo = [attachments objectAtIndex: count];
[self saveAttachment: [currentInfo objectForKey: @"body"]
withMetadata: currentInfo];
}
}
@@ -1002,14 +869,14 @@ static NSString *userAgent = nil;
SOGoUserDefaults *ud;
[sourceMail fetchCoreInfos];
if ([sourceMail subjectForForward])
{
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
forKey: @"subject"];
[self setHeaders: info];
}
[self setSourceURL: [sourceMail imap4URLString]];
[self setSourceFlag: @"$Forwarded"];
[self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]];
@@ -1052,7 +919,7 @@ static NSString *userAgent = nil;
- (NSString *) sender
{
id tmp;
if ((tmp = [headers objectForKey: @"from"]) == nil)
return nil;
if ([tmp isKindOfClass:[NSArray class]])
@@ -1098,52 +965,36 @@ static NSString *userAgent = nil;
return ma;
}
- (BOOL) isValidAttachmentName: (NSString *) filename
{
return (!([filename rangeOfString: @"/"].length
|| [filename isEqualToString: @"."]
|| [filename isEqualToString: @".."]));
}
- (NSString *) pathToAttachmentWithName: (NSString *) _name
{
if ([_name length] == 0)
return nil;
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
}
- (NSException *) invalidAttachmentNameError: (NSString *) _name
{
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason: @"Invalid attachment name!"];
}
/**
* Write attachment file to the spool directory of the draft and write a dot
* file with its mime type.
*/
- (NSException *) saveAttachment: (NSData *) _attach
withMetadata: (NSDictionary *) metadata
{
NSString *p, *pmime, *name, *mimeType;
NSRange r;
if (![_attach isNotNull]) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason: @"Missing attachment content!"];
}
if (![self _ensureDraftFolderPath]) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason: @"Could not create folder for draft!"];
}
name = [metadata objectForKey: @"filename"];
r = [name rangeOfString: @"\\"
options: NSBackwardsSearch];
if (r.length > 0)
name = [name substringFromIndex: r.location + 1];
if (![self isValidAttachmentName: name])
return [self invalidAttachmentNameError: name];
name = [[metadata objectForKey: @"filename"] asSafeFilename];
p = [self pathToAttachmentWithName: name];
if (![_attach writeToFile: p atomically: YES])
{
@@ -1162,7 +1013,7 @@ static NSString *userAgent = nil;
reason: @"Could not write attachment to draft!"];
}
}
return nil; /* everything OK */
}
@@ -1173,21 +1024,14 @@ static NSString *userAgent = nil;
NSException *error;
error = nil;
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName: [_name asSafeFilename]];
if ([fm fileExistsAtPath: p])
if (![fm removeFileAtPath: p handler: nil])
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Could not delete attachment from draft!"];
if ([self isValidAttachmentName:_name])
{
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName:_name];
if ([fm fileExistsAtPath: p])
if (![fm removeFileAtPath: p handler: nil])
error
= [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Could not delete attachment from draft!"];
}
else
error = [self invalidAttachmentNameError:_name];
return error;
return error;
}
//
@@ -1201,9 +1045,9 @@ static NSString *userAgent = nil;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
[map setObject: contentTypeValue forKey: @"content-type"];
/* prepare body content */
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
@@ -1224,7 +1068,7 @@ static NSString *userAgent = nil;
*/
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
@@ -1232,7 +1076,7 @@ static NSString *userAgent = nil;
if (text)
[map setObject: (isHTML ? htmlContentTypeValue : contentTypeValue)
forKey: @"content-type"];
/* prepare body content */
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: text];
@@ -1242,11 +1086,11 @@ static NSString *userAgent = nil;
- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map
{
NGMimeMessage *message;
NGMimeMessage *message;
id body;
message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
if (!isHTML)
{
[message setHeader: contentTypeValue forKey: @"content-type"];
@@ -1263,9 +1107,9 @@ static NSString *userAgent = nil;
// Add the HTML part
[body addBodyPart: [self bodyPartForText]];
}
[message setBody: body];
return message;
}
@@ -1287,9 +1131,8 @@ static NSString *userAgent = nil;
{
NSString *s, *p;
NSData *mimeData;
p = [self pathToAttachmentWithName:
[NSString stringWithFormat: @".%@.mime", _name]];
p = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.mime", _name]];
mimeData = [NSData dataWithContentsOfFile: p];
if (mimeData)
{
@@ -1308,20 +1151,18 @@ static NSString *userAgent = nil;
}
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
andContentType: (NSString *) _type
{
NSString *type;
NSString *cdtype;
NSString *cd;
SOGoDomainDefaults *dd;
type = [self contentTypeForAttachmentWithName:_name];
if ([type hasPrefix: @"text/"])
if ([_type hasPrefix: @"text/"])
{
dd = [[context activeUser] domainDefaults];
cdtype = [dd mailAttachTextDocumentsInline] ? @"inline" : @"attachment";
}
else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"])
else if ([_type hasPrefix: @"image/"] || [_type hasPrefix: @"message"])
cdtype = @"inline";
else
cdtype = @"attachment";
@@ -1348,7 +1189,7 @@ static NSString *userAgent = nil;
if (_name == nil) return nil;
/* check attachment */
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName: _name];
if (![fm isReadableFileAtPath: p]) {
@@ -1357,7 +1198,7 @@ static NSString *userAgent = nil;
}
attachAsString = NO;
attachAsRFC822 = NO;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
@@ -1369,22 +1210,22 @@ static NSString *userAgent = nil;
else if ([s hasPrefix: @"message/rfc822"])
attachAsRFC822 = YES;
}
if ((s = [self contentDispositionForAttachmentWithName: _name]))
if ((s = [self contentDispositionForAttachmentWithName: _name andContentType: s]))
{
NGMimeContentDispositionHeaderField *o;
o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s];
[map setObject: o forKey: @"content-disposition"];
[o release];
}
/* prepare body content */
if (attachAsString) { // TODO: is this really necessary?
NSString *s;
content = [[NSData alloc] initWithContentsOfMappedFile:p];
s = [[NSString alloc] initWithData: content
encoding: [NSString defaultCStringEncoding]];
if (s != nil) {
@@ -1399,7 +1240,7 @@ static NSString *userAgent = nil;
}
}
else {
/*
/*
Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
NGMimeFileData objects are not processed by the MIME generator!
*/
@@ -1415,34 +1256,43 @@ static NSString *userAgent = nil;
content = [content dataByEncodingBase64];
[map setObject: @"base64" forKey: @"content-transfer-encoding"];
}
[map setObject:[NSNumber numberWithInt:[content length]]
[map setObject:[NSNumber numberWithInt:[content length]]
forKey: @"content-length"];
/* Note: the -init method will create a temporary file! */
body = [[NGMimeFileData alloc] initWithBytes:[content bytes]
length:[content length]];
}
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody:body];
[body release]; body = nil;
return bodyPart;
}
//
//
// returns nil on error
//
- (NSArray *) bodyPartsForAllAttachments
{
/* returns nil on error */
NSArray *attrs;
unsigned i, count;
NGMimeBodyPart *bodyPart;
NSMutableArray *bodyParts;
NSArray *attrs;
unsigned i, count, size, limit;
attrs = [self fetchAttachmentAttrs];
count = [attrs count];
size = 0;
// We first check if we don't go over our message size limit
limit = [[SOGoSystemDefaults sharedSystemDefaults] maximumMessageSizeLimit] * 1024;
for (i = 0; i < count; i++)
size += [[[attrs objectAtIndex: i] objectForKey: @"size"] intValue];
if (limit && size > limit)
return nil;
bodyParts = [NSMutableArray arrayWithCapacity: count];
for (i = 0; i < count; i++)
@@ -1462,14 +1312,14 @@ static NSString *userAgent = nil;
NGMimeMultipartBody *textParts;
NGMutableHashMap *header;
NGMimeBodyPart *part;
header = [NGMutableHashMap hashMap];
[header addObject: MultiAlternativeType forKey: @"content-type"];
part = [NGMimeBodyPart bodyPartWithHeader: header];
textParts = [[NGMimeMultipartBody alloc] initWithPart: part];
// Get the text part from it and add it
[textParts addBodyPart: [self plainTextBodyPartForText]];
@@ -1488,25 +1338,21 @@ static NSString *userAgent = nil;
- (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map
andBodyParts: (NSArray *) _bodyParts
{
NGMimeMessage *message;
NGMimeMessage *message;
NGMimeMultipartBody *mBody;
NSEnumerator *e;
id part;
[map addObject: MultiMixedType forKey: @"content-type"];
message = [[NGMimeMessage alloc] initWithHeader: map];
[message autorelease];
mBody = [[NGMimeMultipartBody alloc] initWithPart: message];
if (!isHTML)
{
part = [self bodyPartForText];
}
part = [self bodyPartForText];
else
{
part = [self mimeMultipartAlternative];
}
part = [self mimeMultipartAlternative];
[mBody addBodyPart: part];
@@ -1535,11 +1381,11 @@ static NSString *userAgent = nil;
if ([_h count] == 0)
return;
names = [_h keyEnumerator];
while ((name = [names nextObject]) != nil) {
id value;
value = [_h objectForKey:name];
[_map addObject:value forKey:name];
}
@@ -1549,10 +1395,10 @@ static NSString *userAgent = nil;
{
if (![_value isNotNull])
return YES;
if ([_value isKindOfClass: [NSArray class]])
return [_value count] == 0 ? YES : NO;
if ([_value isKindOfClass: [NSString class]])
return [_value length] == 0 ? YES : NO;
@@ -1630,9 +1476,9 @@ static NSString *userAgent = nil;
NSString *s, *dateString;
NGMutableHashMap *map;
id emails, from, replyTo;
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
/* add recipients */
if ((emails = [headers objectForKey: @"to"]) != nil && [emails isKindOfClass: [NSArray class]])
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"to"];
@@ -1643,7 +1489,7 @@ static NSString *userAgent = nil;
/* add senders */
from = [headers objectForKey: @"from"];
if (![self isEmptyValue:from]) {
if ([from isKindOfClass:[NSArray class]])
[map setObjects: [self _quoteSpecialsInArray: from] forKey: @"from"];
@@ -1661,7 +1507,7 @@ static NSString *userAgent = nil;
if ([(s = [headers objectForKey: @"subject"]) length] > 0)
[map setObject: [s asQPSubjectString: @"utf-8"]
forKey: @"subject"];
if ([(s = [headers objectForKey: @"message-id"]) length] > 0)
[map setObject: s
forKey: @"message-id"];
@@ -1685,16 +1531,16 @@ static NSString *userAgent = nil;
forKey: @"Disposition-Notification-To"];
[self _addHeaders: _headers toHeaderMap: map];
// We remove what we have to...
if (_exclude)
{
int i;
for (i = 0; i < [_exclude count]; i++)
[map removeAllObjectsForKey: [_exclude objectAtIndex: i]];
}
return map;
}
@@ -1707,8 +1553,10 @@ static NSString *userAgent = nil;
{
NSMutableArray *bodyParts;
NGMimeMessage *message;
NSArray *allBodyParts;
NGMutableHashMap *map;
NSString *newText;
BOOL has_inline_images;
message = nil;
@@ -1730,11 +1578,15 @@ static NSString *userAgent = nil;
if (map)
{
//[self debugWithFormat: @"MIME Envelope: %@", map];
[bodyParts addObjectsFromArray: [self bodyPartsForAllAttachments]];
allBodyParts = [self bodyPartsForAllAttachments];
if (!allBodyParts)
return nil;
[bodyParts addObjectsFromArray: allBodyParts];
//[self debugWithFormat: @"attachments: %@", bodyParts];
if ([bodyParts count] == 0)
/* no attachments */
message = [self mimeMessageForContentWithHeaderMap: map];
@@ -1748,13 +1600,13 @@ static NSString *userAgent = nil;
[map addObject: MultiRelatedType forKey: @"content-type"];
}
message = [self mimeMultiPartMessageWithHeaderMap: map
message = [self mimeMultiPartMessageWithHeaderMap: map
andBodyParts: bodyParts];
//[self debugWithFormat: @"message: %@", message];
}
}
return message;
}
@@ -1772,10 +1624,19 @@ static NSString *userAgent = nil;
- (NSData *) mimeMessageAsData
{
NGMimeMessageGenerator *generator;
NGMimeMessage *mimeMessage;
NSData *message;
generator = [NGMimeMessageGenerator new];
message = [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO]];
mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO];
if (!mimeMessage)
{
[generator release];
return nil;
}
message = [generator generateMimeFromPart: mimeMessage];
[generator release];
return message;
@@ -1827,9 +1688,9 @@ static NSString *userAgent = nil;
- (NSException *) sendMail
{
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
if ([ud mailAddOutgoingAddresses])
{
NSString *recipient, *emailAddress, *addressBook, *uid;
@@ -1840,9 +1701,9 @@ static NSString *userAgent = nil;
NGMailAddressParser *parser;
SOGoFolder <SOGoContactFolder> *folder;
NGVCard *card;
int i;
// Get all the addressbooks
contactFolders = [[[context activeUser] homeFolderInContext: context]
lookupName: @"Contacts"
@@ -1857,7 +1718,7 @@ static NSString *userAgent = nil;
parser = [NGMailAddressParser mailAddressParserWithString: recipient];
parsedRecipient = [parser parse];
emailAddress = [parsedRecipient address];
matchingContacts = [contactFolders allContactsFromFilter: emailAddress
excludeGroups: YES
excludeLists: YES];
@@ -1869,13 +1730,13 @@ static NSString *userAgent = nil;
addressBook = [ud selectedAddressBook];
folder = [contactFolders lookupName: addressBook inContext: context acquire: NO];
uid = [folder globallyUniqueObjectId];
if (folder && uid)
{
card = [NGVCard cardWithUid: uid];
[card addEmail: emailAddress types: nil];
[card setFn: [parsedRecipient displayName]];
newContact = [SOGoContactGCSEntry objectWithName: uid
inContainer: folder];
[newContact setIsNew: YES];
@@ -1891,59 +1752,20 @@ static NSString *userAgent = nil;
//
- (NSException *) sendMailAndCopyToSent: (BOOL) copyToSent
{
NSMutableData *cleaned_message;
SOGoMailFolder *sentFolder;
SOGoDomainDefaults *dd;
NSURL *sourceIMAP4URL;
NSException *error;
NSData *message;
NSRange r1;
unsigned int limit;
// We strip the BCC fields prior sending any mails
NGMimeMessageGenerator *generator;
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
message = [generator generateMimeFromPart: [self mimeMessage]];
//
// We now look for the Bcc: header. If it is present, we remove it.
// Some servers, like qmail, do not remove it automatically.
//
#warning FIXME - we should fix the case issue when we switch to Pantomime
cleaned_message = [NSMutableData dataWithData: message];
// We search only in the headers so we start at 0 until
// we find \r\n\r\n, which is the headers delimiter
r1 = [cleaned_message rangeOfCString: "\r\n\r\n"];
limit = r1.location-1;
r1 = [cleaned_message rangeOfCString: "\r\nbcc: "
options: 0
range: NSMakeRange(0,limit)];
if (r1.location != NSNotFound)
{
// We search for the first \r\n AFTER the Bcc: header and
// replace the whole thing with \r\n.
unsigned int i;
for (i = r1.location+7; i < limit; i++)
{
if ([cleaned_message characterAtIndex: i] == '\r' &&
(i+1 < limit && [cleaned_message characterAtIndex: i+1] == '\n') &&
(i+2 < limit && !isspace([cleaned_message characterAtIndex: i+2])))
break;
}
[cleaned_message replaceBytesInRange: NSMakeRange(r1.location, i-r1.location)
withBytes: NULL
length: 0];
}
dd = [[context activeUser] domainDefaults];
error = [[SOGoMailer mailerWithDomainDefaults: dd]
sendMailData: cleaned_message
sendMailData: message
toRecipients: [self allBareRecipients]
sender: [self sender]
withAuthenticator: [self authenticatorInContext: context]
+1
View File
@@ -67,6 +67,7 @@ typedef enum {
NSString *otherUsersFolderName;
NSString *sharedFoldersName;
NSMutableDictionary *subscribedFolders;
BOOL sieveFolderUTF8Encoding;
}
- (SOGoIMAPAclStyle) imapAclStyle;
+14 -3
View File
@@ -37,6 +37,7 @@
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoAuthenticator.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/SOGoSieveManager.h>
@@ -62,6 +63,7 @@ static NSString *inboxFolderName = @"INBOX";
{
if ((self = [super init]))
{
NSString *sieveFolderEncoding = [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
inboxFolder = nil;
draftsFolder = nil;
sentFolder = nil;
@@ -72,6 +74,7 @@ static NSString *inboxFolderName = @"INBOX";
otherUsersFolderName = nil;
sharedFoldersName = nil;
subscribedFolders = nil;
sieveFolderUTF8Encoding = [sieveFolderEncoding isEqualToString: @"UTF-8"];
}
return self;
@@ -461,7 +464,7 @@ static NSString *inboxFolderName = @"INBOX";
NSArray *pathComponents;
NSMutableArray *folders, *flags;
NSMutableDictionary *currentFolder, *parentFolder, *folder;
NSString *currentFolderName, *currentPath, *fullName, *folderType;
NSString *currentFolderName, *currentPath, *sievePath, *fullName, *folderType;
SOGoUserManager *userManager;
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed;
@@ -550,6 +553,13 @@ static NSString *inboxFolderName = @"INBOX";
flags, @"flags",
[NSNumber numberWithBool: isSubscribed], @"subscribed",
nil];
if (sieveFolderUTF8Encoding)
sievePath = [folderPath stringByDecodingImap4FolderName];
else
sievePath = folderPath;
[folder setObject: [sievePath substringFromIndex: 1] forKey: @"sievePath"];
// Either add this new folder to its parent or the list of root folders
[folders addObject: folder];
}
@@ -797,8 +807,9 @@ static NSString *inboxFolderName = @"INBOX";
// * LIST (\NonExistent \HasChildren) "/" shared
// * LIST (\NonExistent \HasChildren) "/" shared/jdoe@example.com
// * LIST (\HasNoChildren) "/" shared/jdoe@example.com/INBOX
else if (([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound &&
[[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound))
else if (!hasAnnotatemore &&
([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound &&
[[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound))
guid = [NSString stringWithFormat: @"%@", currentFolder];
else
{
+16 -3
View File
@@ -132,10 +132,23 @@
- (NGImap4Connection *) _createIMAP4Connection
{
NGImap4ConnectionManager *manager;
NSString *password;
NGImap4Connection *newConnection;
NSString *password;
NSHost *host;
[self imap4URL];
// We first check if we're trying to establish an IMAP connection to localhost
// for an account number greater than 0 (default account). We prevent that
// for security reasons if admins use an IMAP trust.
host = [NSHost hostWithName: [[self imap4URL] host]];
if (![[[self mailAccountFolder] nameInContainer] isEqualToString: @"0"] &&
[[host address] isEqualToString: @"127.0.0.1"])
{
[self errorWithFormat: @"Trying to use localhost for additional IMAP account - aborting."];
return nil;
}
manager = [self mailManager];
password = [self imap4PasswordRenewed: NO];
if (password)
@@ -212,8 +225,8 @@
if (!imap4)
{
imap4 = [self _createIMAP4Connection];
[sogoCache registerIMAP4Connection: imap4
forKey: cacheKey];
[sogoCache registerIMAP4Connection: imap4
forKey: cacheKey];
}
[imap4 retain];
}
+3
View File
@@ -76,6 +76,9 @@
@interface SOGoMailGermanForward : SOGoMailForward
@end
@interface SOGoMailHebrewForward : SOGoMailForward
@end
@interface SOGoMailHungarianForward : SOGoMailForward
@end
+3
View File
@@ -287,6 +287,9 @@
@implementation SOGoMailGermanForward
@end
@implementation SOGoMailHebrewForward
@end
@implementation SOGoMailHungarianForward
@end
@@ -0,0 +1,16 @@
<#newLine/>
<#newLine/>
<#signaturePlacementOnTop><#newLine/>
<#signature/><#newLine/></#signaturePlacementOnTop>
-------- הודעה מקורית --------<#newLine/>
נושא: <#subject/><#newLine/>
תאריך: <#date/><#newLine/>
מאת: <#from/><#newLine/>
<#hasReplyTo>השב: <#replyTo/></#hasReplyTo><#hasOrganization>אירגון: <#organization/></#hasOrganization>אל: <#to/><#newLine/>
<#hasCc>מכותב: <#cc/></#hasCc><#hasNewsGroups>קבוצות חדשות: <#newsgroups/></#hasNewsGroups><#hasReferences>תזכור: <#references/></#hasReferences><#newLine/>
<#newLine/>
<#messageBody/><#newLine/>
<#signaturePlacementOnBottom><#newLine/>
<#newLine/>
<#signature/></#signaturePlacementOnBottom>
<#newLine/>
@@ -0,0 +1,88 @@
subject: WOString {
value = subject;
escapeHTML = NO;
}
date: WOString {
value = date;
escapeHTML = NO;
}
from: WOString {
value = from;
escapeHTML = NO;
}
newLine: WOString {
value = newLine;
escapeHTML = NO;
}
hasReplyTo: WOConditional {
condition = hasReplyTo;
}
replyTo: WOString {
value = replyTo;
escapeHTML = NO;
}
hasOrganization: WOConditional {
condition = hasOrganization;
}
organization: WOString {
value = organization;
escapeHTML = NO;
}
to: WOString {
value = to;
escapeHTML = NO;
}
hasCc: WOConditional {
condition = hasCc;
}
cc: WOString {
value = cc;
escapeHTML = NO;
}
hasNewsGroups: WOConditional {
condition = hasNewsGroups;
}
newsgroups: WOString {
value = newsgroups;
escapeHTML = NO;
}
hasReferences: WOConditional {
condition = hasReferences;
}
references: WOString {
value = references;
escapeHTML = NO;
}
messageBody: WOString {
value = messageBody;
escapeHTML = NO;
}
signature: WOString {
value = signature;
escapeHTML = NO;
}
signaturePlacementOnTop: WOConditional {
condition = signaturePlacementOnTop;
}
signaturePlacementOnBottom: WOConditional {
condition = signaturePlacementOnTop;
negate = YES;
}
@@ -0,0 +1,16 @@
<#replyPlacementOnTop><#newLine/>
<#newLine/>
</#replyPlacementOnTop><#signaturePlacementOnTop><#newLine/>
<#signature/><#newLine/>
</#signaturePlacementOnTop><#outlookMode>-------- הודעה מקורית --------<#newLine/>
נושא: <#subject/><#newLine/>
תאריך: <#date/><#newLine/>
מאת: <#from/><#newLine/>
<#hasReplyTo>השב: <#replyTo/></#hasReplyTo><#hasOrganization>אירגון: <#organization/></#hasOrganization>אל: <#to/><#newLine/>
<#hasCc>מכותב: <#cc/></#hasCc><#hasNewsGroups>קבוצות חדשות: <#newsgroups/></#hasNewsGroups><#hasReferences>תזכור: <#references/></#hasReferences></#outlookMode><#newLine/>
<#standardMode>פועל <#date/>, <#from/> כתב:</#standardMode><#newLine/>
<#newLine/>
<#messageBody/><#newLine/>
<#replyPlacementOnBottom><#newLine/>
<#newLine/>
</#replyPlacementOnBottom><#signaturePlacementOnBottom><#signature/></#signaturePlacementOnBottom><#newLine/>
@@ -0,0 +1,106 @@
outlookMode: WOConditional {
condition = outlookMode;
}
standardMode: WOConditional {
condition = outlookMode;
negate = YES;
}
subject: WOString {
value = subject;
escapeHTML = NO;
}
date: WOString {
value = date;
escapeHTML = NO;
}
from: WOString {
value = from;
escapeHTML = NO;
}
newLine: WOString {
value = newLine;
escapeHTML = NO;
}
hasReplyTo: WOConditional {
condition = hasReplyTo;
}
replyTo: WOString {
value = replyTo;
escapeHTML = NO;
}
hasOrganization: WOConditional {
condition = hasOrganization;
}
organization: WOString {
value = organization;
escapeHTML = NO;
}
to: WOString {
value = to;
escapeHTML = NO;
}
hasCc: WOConditional {
condition = hasCc;
}
cc: WOString {
value = cc;
escapeHTML = NO;
}
hasNewsGroups: WOConditional {
condition = hasNewsGroups;
}
newsgroups: WOString {
value = newsgroups;
escapeHTML = NO;
}
hasReferences: WOConditional {
condition = hasReferences;
}
references: WOString {
value = references;
escapeHTML = NO;
}
messageBody: WOString {
value = messageBody;
escapeHTML = NO;
}
signature: WOString {
value = signature;
escapeHTML = NO;
}
replyPlacementOnTop: WOConditional {
condition = replyPlacementOnTop;
}
replyPlacementOnBottom: WOConditional {
condition = replyPlacementOnTop;
negate = YES;
}
signaturePlacementOnTop: WOConditional {
condition = signaturePlacementOnTop;
}
signaturePlacementOnBottom: WOConditional {
condition = signaturePlacementOnTop;
negate = YES;
}
+5
View File
@@ -1,4 +1,5 @@
/*
Copyright (C) 2009-2016 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
@@ -48,6 +49,8 @@
@class NGImap4Envelope;
@class NGImap4EnvelopeAddress;
@class WOResponse;
NSArray *SOGoMailCoreInfoKeys;
@interface SOGoMailObject : SOGoMailBaseObject
@@ -107,6 +110,8 @@ NSArray *SOGoMailCoreInfoKeys;
- (BOOL) hasAttachment;
- (NSDictionary *) fetchFileAttachmentIds;
- (NSArray *) fetchFileAttachmentKeys;
- (NSArray *) fetchFileAttachments;
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName;
/* flags */
+248 -89
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2014 Inverse inc.
Copyright (C) 2007-2016 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOGo.
@@ -20,6 +20,7 @@
02111-1307, USA.
*/
#import <Foundation/NSTask.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>
@@ -27,6 +28,7 @@
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSFileManager+Extensions.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h>
@@ -38,7 +40,9 @@
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/NSCalendarDate+SOGo.h>
@@ -122,10 +126,10 @@ static BOOL debugSoParts = NO;
- (NSString *) keyExtensionForPart: (id) _partInfo
{
NSString *mt, *st;
if (_partInfo == nil)
return nil;
mt = [_partInfo valueForKey: @"type"];
st = [[_partInfo valueForKey: @"subtype"] lowercaseString];
if ([mt isEqualToString: @"text"]) {
@@ -140,7 +144,7 @@ static BOOL debugSoParts = NO;
if ([st isEqualToString: @"pgp-signature"])
return @".asc";
}
return nil;
}
@@ -149,18 +153,18 @@ static BOOL debugSoParts = NO;
NSMutableArray *ma;
NSArray *parts;
unsigned i, count;
parts = [[self bodyStructure] valueForKey: @"parts"];
if (![parts isNotNull])
if (![parts isNotNull])
return nil;
if ((count = [parts count]) == 0)
return nil;
for (i = 0, ma = nil; i < count; i++) {
NSString *key, *ext;
id part;
BOOL hasParts;
part = [parts objectAtIndex:i];
hasParts = [part valueForKey: @"parts"] != nil ? YES:NO;
if ((hasParts && !_withParts) || (_withParts && !hasParts))
@@ -168,7 +172,7 @@ static BOOL debugSoParts = NO;
if (ma == nil)
ma = [NSMutableArray arrayWithCapacity:count - i];
ext = [self keyExtensionForPart:part];
key = [[NSString alloc] initWithFormat: @"%d%@", i + 1, ((id)ext?(id)ext: (id)@"")];
[ma addObject:key];
@@ -204,15 +208,15 @@ static BOOL debugSoParts = NO;
{
static NSArray *existsKey = nil;
id msgs;
if (coreInfos != nil) /* if we have coreinfos, we can use them */
return [coreInfos isNotNull];
/* otherwise fetch something really simple */
if (existsKey == nil) /* we use size, other suggestions? */
existsKey = [[NSArray alloc] initWithObjects: @"RFC822.SIZE", nil];
msgs = [self fetchParts:existsKey]; // returns dict
msgs = [msgs valueForKey: @"fetch"];
return [msgs count] > 0 ? YES : NO;
@@ -229,7 +233,7 @@ static BOOL debugSoParts = NO;
if (heavyDebug)
[self logWithFormat: @"M: %@", msgs];
msgs = [msgs valueForKey: @"fetch"];
// We MUST honor untagged IMAP responses here otherwise we could
// return really borken and nasty results.
if ([msgs count] > 0)
@@ -237,7 +241,7 @@ static BOOL debugSoParts = NO;
for (i = 0; i < [msgs count]; i++)
{
coreInfos = [msgs objectAtIndex: i];
if ([[coreInfos objectForKey: @"uid"] intValue] == [[self nameInContainer] intValue])
break;
@@ -337,13 +341,13 @@ static BOOL debugSoParts = NO;
{
NGMimeMessageParser *parser;
NSData *data;
if (headerPart != nil)
return [headerPart isNotNull] ? headerPart : nil;
if ([(data = [self mailHeaderData]) length] == 0)
return nil;
// TODO: do we need to set some delegate method which stops parsing the body?
parser = [[NGMimeMessageParser alloc] init];
headerPart = [[parser parsePartFromData:data] retain];
@@ -372,21 +376,21 @@ static BOOL debugSoParts = NO;
if (![_path isNotNull])
return nil;
if ((info = [self bodyStructure]) == nil) {
[self errorWithFormat: @"got no body part structure!"];
return nil;
}
/* ensure array argument */
if ([_path isKindOfClass:[NSString class]]) {
if ([_path length] == 0 || [_path isEqualToString: @"text"])
return info;
_path = [_path componentsSeparatedByString: @"."];
}
// deal with mails of type text/calendar
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"text"] &&
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
@@ -399,12 +403,12 @@ static BOOL debugSoParts = NO;
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"])
return info;
/*
For each path component, eg 1,1,3
/*
For each path component, eg 1,1,3
Remember that we need special processing for message/rfc822 which maps the
namespace of multiparts directly into the main namespace.
TODO(hh): no I don't remember, please explain in more detail!
*/
pe = [_path objectEnumerator];
@@ -412,7 +416,7 @@ static BOOL debugSoParts = NO;
unsigned idx;
NSArray *parts;
NSString *mt;
[self debugWithFormat: @"check PATH: %@", p];
idx = [p intValue] - 1;
@@ -421,7 +425,7 @@ static BOOL debugSoParts = NO;
if ([mt isEqualToString: @"message"]) {
/* we have special behaviour for message types */
id body;
if ((body = [info valueForKey: @"body"]) != nil) {
mt = [body valueForKey: @"type"];
if ([mt isEqualToString: @"multipart"])
@@ -430,10 +434,10 @@ static BOOL debugSoParts = NO;
parts = [NSArray arrayWithObject:body];
}
}
if (idx >= [parts count]) {
[self errorWithFormat:
@"body part index out of bounds(idx=%d vs count=%d): %@",
@"body part index out of bounds(idx=%d vs count=%d): %@",
(idx + 1), [parts count], info];
return nil;
}
@@ -448,40 +452,40 @@ static BOOL debugSoParts = NO;
{
NSData *content;
id result, fullResult;
// We avoid using RFC822 here as the part name as it'll flag the message as Seen
fullResult = [self fetchParts: [NSArray arrayWithObject: @"BODY.PEEK[]"]];
if (fullResult == nil)
return nil;
if ([fullResult isKindOfClass: [NSException class]])
return fullResult;
/* extract fetch result */
result = [fullResult valueForKey: @"fetch"];
if (![result isKindOfClass:[NSArray class]]) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
fullResult];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason: @"unexpected IMAP4 result"];
}
if ([result count] == 0)
return nil;
result = [result objectAtIndex:0];
/* extract message */
if ((content = [[result valueForKey: @"body[]"] valueForKey: @"data"]) == nil) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
result];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason: @"unexpected IMAP4 result"];
}
return [[content copy] autorelease];
}
@@ -507,7 +511,7 @@ static BOOL debugSoParts = NO;
[s autorelease];
else
[self logWithFormat:
@"ERROR: could not convert data of length %d to string",
@"ERROR: could not convert data of length %d to string",
[content length]];
}
else
@@ -529,11 +533,11 @@ static BOOL debugSoParts = NO;
withPeek: (BOOL) withPeek
parentMultipart: (NSString *) parentMPart
{
/*
/*
This is used to collect the set of IMAP4 fetch-keys required to fetch
the basic parts of the body structure. That is, to fetch all parts which
are displayed 'inline' in a single IMAP4 fetch.
The method calls itself recursively to walk the body structure.
*/
NSArray *parts;
@@ -555,7 +559,7 @@ static BOOL debugSoParts = NO;
multipart = mimeType;
else
multipart = parentMPart;
if ([types containsObject: mimeType])
{
if ([p length] > 0)
@@ -581,9 +585,9 @@ static BOOL debugSoParts = NO;
sp = (([p length] > 0)
? (id)[p stringByAppendingFormat: @".%d", i + 1]
: (id)[NSString stringWithFormat: @"%d", i + 1]);
childInfo = [parts objectAtIndex: i];
[self addRequiredKeysOfStructure: childInfo
path: sp
toArray: keys
@@ -591,7 +595,7 @@ static BOOL debugSoParts = NO;
withPeek: withPeek
parentMultipart: multipart];
}
/* check body */
body = [info objectForKey: @"body"];
if (body)
@@ -651,7 +655,7 @@ static BOOL debugSoParts = NO;
[self addRequiredKeysOfStructure: [self bodyStructure]
path: @""
toArray: ma
toArray: ma
acceptedTypes: types
withPeek: YES];
@@ -665,7 +669,7 @@ static BOOL debugSoParts = NO;
unsigned i, count;
NSArray *results;
id result;
[self debugWithFormat: @"fetch keys: %@", _fetchKeys];
result = [self fetchParts: [_fetchKeys objectsForKey: @"key"
@@ -681,7 +685,7 @@ static BOOL debugSoParts = NO;
for (i = 0; i < count; i++) {
NSString *key;
NSData *data;
key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"];
// We'll ask for the body.peek[] but SOPE returns us body[] responses
@@ -689,19 +693,19 @@ static BOOL debugSoParts = NO;
if ([key hasPrefix: @"body.peek["])
key = [NSString stringWithFormat: @"body[%@", [key substringFromIndex: 10]];
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
objectForKey: @"data"];
if (![data isNotNull]) {
[self errorWithFormat: @"got no data for key: %@", key];
continue;
}
if ([key isEqualToString: @"body[text]"])
key = @""; // see key collector for explanation (TODO: where?)
else if ([key hasPrefix: @"body["]) {
NSRange r;
key = [key substringFromIndex:5];
r = [key rangeOfString: @"]"];
if (r.length > 0)
@@ -836,6 +840,7 @@ static BOOL debugSoParts = NO;
path, @"path",
[part objectForKey: @"encoding"], @"encoding",
[part objectForKey:@ "size"], @"size",
[part objectForKey: @"bodyId"], @"bodyId",
[NSString stringWithFormat: @"%@/%@", prefix, filenameURL], @"url",
[NSString stringWithFormat: @"%@/asAttachment/%@", prefix, filenameURL], @"urlAsAttachment",
nil];
@@ -913,13 +918,167 @@ static BOOL debugSoParts = NO;
return keys;
}
/**
* Returns an array of dictionaries with the following keys:
* - encoding
* - filename
* - mimetype
* - path
* - size
* - url
* - urlAsAttachment
* - body (NSData)
*/
- (NSArray *) fetchFileAttachments
{
unsigned int count, max;
NGHashMap *response;
NSArray *parts, *paths; //, *bodies;
NSData *body;
NSDictionary *fetch, *currentInfo, *currentBody;
NSMutableArray *attachments;
NSMutableDictionary *currentAttachment;
NSString *currentPath;
parts = [self fetchFileAttachmentKeys];
max = [parts count];
attachments = [NSMutableArray arrayWithCapacity: max];
if (max > 0)
{
paths = [parts keysWithFormat: @"BODY[%{path}]"];
response = [[self fetchParts: paths] objectForKey: @"RawResponse"];
fetch = [response objectForKey: @"fetch"];
for (count = 0; count < max; count++)
{
currentInfo = [parts objectAtIndex: count];
currentPath = [[paths objectAtIndex: count] lowercaseString];
currentBody = [fetch objectForKey: currentPath];
if (currentBody)
{
body = [currentBody objectForKey: @"data"];
body = [body bodyDataFromEncoding: [currentInfo objectForKey: @"encoding"]];
}
else
body = [NSData data];
currentAttachment = [NSMutableDictionary dictionaryWithDictionary: currentInfo];
[currentAttachment setObject: body forKey: @"body"];
[attachments addObject: currentAttachment];
}
}
return attachments;
}
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName
{
NSArray *attachments;
NSData *body, *zipContent;
NSDictionary *currentAttachment;
NSException *error;
NSFileManager *fm;
NSMutableArray *zipTaskArguments;
NSString *spoolPath, *name, *fileName, *baseName, *extension, *zipPath, *qpFileName;
NSTask *zipTask;
SOGoMailFolder *folder;
WOResponse *response;
unsigned int max, count;
if (!archiveName)
archiveName = @"attachments.zip";
folder = [self container];
spoolPath = [folder userSpoolFolderPath];
if (![folder ensureSpoolFolderPath])
{
[self errorWithFormat: @"spool directory '%@' doesn't exist", spoolPath];
error = [NSException exceptionWithHTTPStatus: 500
reason: @"spool directory does not exist"];
return (WOResponse *)error;
}
// Prepare execution of zip
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath: zipPath])
{
error = [NSException exceptionWithHTTPStatus: 500
reason: @"zip not available"];
return (WOResponse *)error;
}
zipTask = [[NSTask alloc] init];
[zipTask setCurrentDirectoryPath: spoolPath];
[zipTask setLaunchPath: zipPath];
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
[zipTaskArguments addObject: @"attachments.zip"];
// Fetch attachments and write them on disk
attachments = [self fetchFileAttachments];
max = [attachments count];
for (count = 0; count < max; count++)
{
currentAttachment = [attachments objectAtIndex: count];
body = [currentAttachment objectForKey: @"body"];
name = [[currentAttachment objectForKey: @"filename"] asSafeFilename];
fileName = [NSString stringWithFormat:@"%@/%@", spoolPath, name];
[body writeToFile: fileName atomically: YES];
[zipTaskArguments addObject: [NSString stringWithFormat: @"./%@", name]];
}
// Zip files
[zipTask setArguments: zipTaskArguments];
[zipTask launch];
[zipTask waitUntilExit];
[zipTask release];
zipContent = [[NSData alloc] initWithContentsOfFile:
[NSString stringWithFormat: @"%@/attachments.zip", spoolPath]];
// Delete attachments from disk
max = [zipTaskArguments count];
for (count = 0; count < max; count++)
{
fileName = [zipTaskArguments objectAtIndex: count];
[fm removeFileAtPath:
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
}
// Prepare response
response = [context response];
baseName = [archiveName stringByDeletingPathExtension];
extension = [archiveName pathExtension];
if ([extension length] > 0)
extension = [@"." stringByAppendingString: extension];
else
extension = @"";
qpFileName = [NSString stringWithFormat: @"%@%@",
[baseName asQPSubjectString: @"utf-8"], extension];
[response setHeader: [NSString stringWithFormat: @"application/zip;"
@" name=\"%@\"", qpFileName]
forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
qpFileName]
forKey: @"Content-Disposition"];
[response setContent: zipContent];
[zipContent release];
return response;
}
/* convert parts to strings */
- (NSString *) stringForData: (NSData *) _data
partInfo: (NSDictionary *) _info
{
NSString *charset, *s;
NSData *mailData;
if ([_data isNotNull])
{
mailData
@@ -934,13 +1093,13 @@ static BOOL debugSoParts = NO;
{
s = [NSString stringWithData: mailData usingEncodingNamed: charset];
}
// If it has failed, we try at least using UTF-8. Normally, this can NOT fail.
// Unfortunately, it seems to fail under GNUstep so we try latin1 if that's
// the case
if (!s)
s = [[[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding] autorelease];
if (!s)
s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
}
@@ -975,17 +1134,17 @@ static BOOL debugSoParts = NO;
/*
The fetched parts are NSData objects, this method converts them into
NSString objects based on the information inside the bodystructure.
The fetch-keys are body fetch-keys like: body[text] or body[1.2.3].
The keys in the result dictionary are "" for 'text' and 1.2.3 for parts.
*/
NSDictionary *datas;
if ((datas = [self fetchPlainTextParts:_fetchKeys]) == nil)
return nil;
if ([datas isKindOfClass:[NSException class]])
return datas;
return [self stringifyTextParts:datas];
}
@@ -1065,13 +1224,13 @@ static BOOL debugSoParts = NO;
acquire: (BOOL) _flag
{
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
/* lookup body part */
if ([self isBodyPartKey:_key]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
if (debugSoParts)
@@ -1087,7 +1246,7 @@ static BOOL debugSoParts = NO;
[obj setAsAttachment];
return obj;
}
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason: @"Did not find mail method or part-reference!"];
@@ -1131,7 +1290,7 @@ static BOOL debugSoParts = NO;
newName: (NSString *) _name
inContext: (id)_ctx
{
/*
/*
Note: this is special because we create SOGoMailObject's even if they do
not exist (for performance reasons).
@@ -1181,7 +1340,7 @@ static BOOL debugSoParts = NO;
NSException *error;
WOResponse *r;
NSData *content;
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
/* check whether the mail still exists */
if (![self doesMailExist]) {
@@ -1190,7 +1349,7 @@ static BOOL debugSoParts = NO;
}
return error; /* return 304 or 416 */
}
content = [self content];
if ([content isKindOfClass:[NSException class]])
return content;
@@ -1198,7 +1357,7 @@ static BOOL debugSoParts = NO;
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason: @"did not find IMAP4 message"];
}
r = [(WOContext *)_ctx response];
[r setHeader: @"message/rfc822" forKey: @"content-type"];
[r setContent:content];
@@ -1257,19 +1416,19 @@ static BOOL debugSoParts = NO;
inContext: _ctx])
{
/* b) mark deleted */
error = [[self imap4Connection] markURLDeleted: [self imap4URL]];
if (error != nil) return error;
[self flushMailCaches];
}
return nil;
}
- (NSException *) delete
{
/*
/*
Note: delete is different to DELETEAction: for mails! The 'delete' runs
either flags a message as deleted or moves it to the Trash while
the DELETEAction: really deletes a message (by flagging it as
@@ -1279,7 +1438,7 @@ static BOOL debugSoParts = NO;
NSException *error;
// TODO: check for safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
return error;
}
@@ -1287,15 +1446,15 @@ static BOOL debugSoParts = NO;
- (id) DELETEAction: (id) _ctx
{
NSException *error;
// TODO: ensure safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
if (error != nil) return error;
error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]];
if (error != nil) return error; // TODO: unflag as deleted?
return [NSNumber numberWithBool:YES]; /* delete was successful */
}
@@ -1304,20 +1463,20 @@ static BOOL debugSoParts = NO;
- (BOOL) isMailingListMail
{
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
return [[h objectForKey: @"list-id"] isNotEmpty];
}
- (BOOL) isVirusScanned
{
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
if (![[h objectForKey: @"x-virus-status"] isNotEmpty]) return NO;
if (![[h objectForKey: @"x-virus-scanned"] isNotEmpty]) return NO;
return YES;
@@ -1329,10 +1488,10 @@ static BOOL debugSoParts = NO;
/* Note: not very tolerant on embedded commands and <> */
// TODO: does not really belong here, should be a header-field-parser
NSRange r;
if (![_value isNotEmpty])
return nil;
if ([_value isKindOfClass:[NSArray class]]) {
NSEnumerator *e;
id value;
@@ -1344,10 +1503,10 @@ static BOOL debugSoParts = NO;
}
return nil;
}
if (![_value isKindOfClass:[NSString class]])
return nil;
/* check for commas in string values */
r = [_value rangeOfString: @","];
if (r.length > 0) {
@@ -1358,7 +1517,7 @@ static BOOL debugSoParts = NO;
/* value qualifies */
if (![(NSString *)_value hasPrefix:_prefix])
return nil;
/* unquote */
if ([_value characterAtIndex:0] == '<') {
r = [_value rangeOfString: @">"];
@@ -1456,7 +1615,7 @@ static BOOL debugSoParts = NO;
if (property)
{
parts = [NSArray arrayWithObject: property];
msgs = [self fetchParts: parts];
msgs = [msgs valueForKey: @"fetch"];
if ([msgs count]) {
@@ -1512,7 +1671,7 @@ static BOOL debugSoParts = NO;
// date already exists, but this one is the correct format
- (NSString *) davDate
{
return [[self date] rfc822DateString];
return [[self date] rfc822DateString];
}
- (BOOL) hasAttachment
@@ -1597,12 +1756,12 @@ static BOOL debugSoParts = NO;
if ([fetch count])
{
data = [fetch objectForKey: @"header"];
value = [[NSString alloc] initWithData: data
value = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
range = [value rangeOfString: @"received:"
options: NSCaseInsensitiveSearch
range: NSMakeRange (10, [value length] - 11)];
if (range.length
if (range.length
&& range.location < [value length]
&& range.length < [value length])
{
+3
View File
@@ -75,6 +75,9 @@
@interface SOGoMailGermanReply : SOGoMailReply
@end
@interface SOGoMailHebrewReply : SOGoMailReply
@end
@interface SOGoMailHungarianReply : SOGoMailReply
@end
+3
View File
@@ -123,6 +123,9 @@
@implementation SOGoMailGermanReply
@end
@implementation SOGoMailHebrewReply
@end
@implementation SOGoMailHungarianReply
@end
+8 -3
View File
@@ -1211,7 +1211,6 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
{
ldapConnection = [self _ldapConnection];
qualifier = [self _qualifierForFilter: match];
// attributes = [self _searchAttributes];
attributes = [NSArray arrayWithObject: @"*"];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
@@ -1237,10 +1236,9 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) qualifier
{
NGLdapConnection *ldapConnection;
NSArray *attributes;
NSEnumerator *entries;
NSArray *attributes;
// attributes = [self _searchAttributes];
ldapConnection = [self _ldapConnection];
attributes = [NSArray arrayWithObject: @"*"];
@@ -1307,12 +1305,19 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
{
NGLdapConnection *ldapConnection;
NGLdapEntry *entry;
EOQualifier *qualifier;
NSString *login;
login = nil;
qualifier = nil;
ldapConnection = [self _ldapConnection];
if (_filter)
qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
entry = [ldapConnection entryAtDN: theDN
qualifier: qualifier
attributes: [NSArray arrayWithObject: UIDField]];
if (entry)
login = [[entry attributeWithName: UIDField] stringValueAtIndex: 0];
+11 -2
View File
@@ -721,7 +721,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
cryptParts = [cryptString componentsSeparatedByString: @"$"];
// correct number of elements (first one is an empty string)
if ([cryptParts count] != 4)
if ([cryptParts count] < 4)
{
return [NSData data];
}
@@ -731,7 +731,16 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame)
{
// third is the salt; convert it to NSData
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
if ([cryptParts count] == 4)
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
else
{
NSString *saltWithRounds;
saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]];
return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding];
}
}
// nothing good
return [NSData data];
+5 -3
View File
@@ -302,7 +302,8 @@ static int cssEscapingCount;
{
c = buf[i];
if (c == 0x9 ||
if (c == 0x0 ||
c == 0x9 ||
c == 0xA ||
c == 0xD ||
(c >= 0x20 && c <= 0xD7FF) ||
@@ -683,8 +684,9 @@ static int cssEscapingCount;
- (NSString *) asSafeSQLString
{
return [[self stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'"];
return [[[self stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'"]
stringByReplacingString: @"\%" withString: @"\\%"];
}
- (NSUInteger) countOccurrencesOfString: (NSString *) substring
+2
View File
@@ -505,6 +505,8 @@ static memcached_st *handle = NULL;
{
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"];
}
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"LastRequestDate"];
[d setObject: count forKey: @"FailedCount"];
[self _cacheValues: [d jsonRepresentation]
+2 -1
View File
@@ -54,6 +54,7 @@
"Finnish",
"French",
"German",
"Hebrew",
"Hungarian",
"Icelandic",
"Italian",
@@ -104,7 +105,7 @@
SOGoTrashFolderName = "Trash";
SOGoJunkFolderName = "Junk";
SOGoMailComposeMessageType = "html";
SOGoMailComposeFontSize = 0;
SOGoMailComposeFontSize = "0";
SOGoMailDisplayRemoteInlineImages = "never";
SOGoMailAutoSave = "5";
+31 -11
View File
@@ -424,7 +424,23 @@ static NSArray *childRecordFields = nil;
- (GCSFolder *) ocsFolderForPath: (NSString *) _path
{
return [[self folderManager] folderAtPath: _path];
NSDictionary *record;
SOGoCache *cache;
cache = [SOGoCache sharedCache];
record = [[cache valueForKey: _path] objectFromJSONString];
if (!record)
{
record = [[self folderManager] recordAtPath: _path];
if (!record)
return nil;
[cache setValue: [record jsonRepresentation] forKey: _path];
}
return [[self folderManager] folderForRecord: record];
}
- (BOOL) folderIsMandatory
@@ -471,13 +487,19 @@ static NSArray *childRecordFields = nil;
{
NSMutableArray *newPath;
NSURL *davURL;
unsigned int max, count;
davURL = [self realDavURL];
newPath = [NSMutableArray arrayWithArray: [[davURL path] componentsSeparatedByString: @"/"]];
[newPath insertObject: @"public" atIndex: 3];
davURL = [[NSURL alloc] initWithScheme: [davURL scheme]
host: [davURL host]
path: [newPath componentsJoinedByString: @"/"]];
max = [newPath count];
for (count = 0; count < max; count++)
[newPath replaceObjectAtIndex: count
withObject: [[newPath objectAtIndex: count] stringByEscapingURL]];
davURL = [NSURL URLWithString: [newPath componentsJoinedByString: @"/"]
relativeToURL: davURL];
return davURL;
}
@@ -496,14 +518,11 @@ static NSArray *childRecordFields = nil;
publicParticle = @"";
path = [NSString stringWithFormat: @"/%@/dav%@/%@/%@/%@/",
appName, publicParticle,
[self ownerInContext: nil],
[container nameInContainer],
[self realNameInContainer]];
[[self ownerInContext: nil] stringByEscapingURL],
[[container nameInContainer] stringByEscapingURL],
[[self realNameInContainer] stringByEscapingURL]];
currentDavURL = [self davURL];
realDavURL = [[NSURL alloc] initWithScheme: [currentDavURL scheme]
host: [currentDavURL host]
path: path];
[realDavURL autorelease];
realDavURL = [NSURL URLWithString: path relativeToURL: currentDavURL];
}
else
realDavURL = [self davURL];
@@ -575,6 +594,7 @@ static NSArray *childRecordFields = nil;
[self removeFolderSettings: moduleSettings
withReference: [self folderReference]];
[us synchronize];
[[SOGoCache sharedCache] removeValueForKey: ocsPath];
if ([[context request] handledByDefaultHandler])
[self sendFolderAdvisoryTemplate: @"Removal"];
+1 -1
View File
@@ -293,7 +293,7 @@
if (user)
{
[logins addObject: login];
[logins addObject: [user loginInDomain]];
[_members addObject: user];
}
[pool release];
+145 -2
View File
@@ -35,6 +35,111 @@
#import "SOGoMailer.h"
//
// Useful extension that comes from Pantomime which is also
// released under the LGPL. We should eventually merge
// this with the same category found in SOPE's NGSmtpClient.m
// or simply drop sope-mime in favor of Pantomime
//
@interface NSMutableData (DataCleanupExtension)
- (unichar) characterAtIndex: (int) theIndex;
- (NSRange) rangeOfCString: (const char *) theCString;
- (NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange;
@end
@implementation NSMutableData (DataCleanupExtension)
- (unichar) characterAtIndex: (int) theIndex
{
const char *bytes;
int i, len;
len = [self length];
if (len == 0 || theIndex >= len)
{
[[NSException exceptionWithName: NSRangeException
reason: @"Index out of range."
userInfo: nil] raise];
return (unichar)0;
}
bytes = [self bytes];
for (i = 0; i < theIndex; i++)
{
bytes++;
}
return (unichar)*bytes;
}
- (NSRange) rangeOfCString: (const char *) theCString
{
return [self rangeOfCString: theCString
options: 0
range: NSMakeRange(0,[self length])];
}
-(NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange
{
const char *b, *bytes;
int i, len, slen;
if (!theCString)
{
return NSMakeRange(NSNotFound,0);
}
bytes = [self bytes];
len = [self length];
slen = strlen(theCString);
b = bytes;
if (len > theRange.location + theRange.length)
{
len = theRange.location + theRange.length;
}
if (theOptions == NSCaseInsensitiveSearch)
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!strncasecmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
else
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!memcmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
return NSMakeRange(NSNotFound,0);
}
@end
@implementation SOGoMailer
+ (SOGoMailer *) mailerWithDomainDefaults: (SOGoDomainDefaults *) dd
@@ -236,12 +341,50 @@
reason: @"cannot send message: no sender set"];
else
{
NSMutableData *cleaned_message;
NSRange r1;
unsigned int limit;
//
// We now look for the Bcc: header. If it is present, we remove it.
// Some servers, like qmail, do not remove it automatically.
//
#warning FIXME - we should fix the case issue when we switch to Pantomime
cleaned_message = [NSMutableData dataWithData: data];
// We search only in the headers so we start at 0 until
// we find \r\n\r\n, which is the headers delimiter
r1 = [cleaned_message rangeOfCString: "\r\n\r\n"];
limit = r1.location-1;
r1 = [cleaned_message rangeOfCString: "\r\nBcc: "
options: 0
range: NSMakeRange(0,limit)];
if (r1.location != NSNotFound)
{
// We search for the first \r\n AFTER the Bcc: header and
// replace the whole thing with \r\n.
unsigned int i;
for (i = r1.location+7; i < limit; i++)
{
if ([cleaned_message characterAtIndex: i] == '\r' &&
(i+1 < limit && [cleaned_message characterAtIndex: i+1] == '\n') &&
(i+2 < limit && !isspace([cleaned_message characterAtIndex: i+2])))
break;
}
[cleaned_message replaceBytesInRange: NSMakeRange(r1.location, i-r1.location)
withBytes: NULL
length: 0];
}
if ([mailingMechanism isEqualToString: @"sendmail"])
result = [self _sendmailSendData: data
result = [self _sendmailSendData: cleaned_message
toRecipients: recipients
sender: [sender pureEMailAddress]];
else
result = [self _smtpSendData: data
result = [self _smtpSendData: cleaned_message
toRecipients: recipients
sender: [sender pureEMailAddress]
withAuthenticator: authenticator
+1 -7
View File
@@ -291,14 +291,8 @@ static SoSecurityManager *sm = nil;
// This is important because user A could delete folder X, and user B has subscribed to it.
// If the "default roles" are enabled for calendars/address books, -validatePersmission:.. will
// work (grabbing the default role) and the deleted resource will be incorrectly returned.
//
// FIXME - 2015/01/29 - this fix (24c6c8c91d421594bd51f07904d4cd3911cd187c) was reverted. Normally, we should add
// [subscribedFolder ocsFolderForPath: [subscribedFolder ocsPath]] to check the existence of the folder but it causes
// massive SQL traffic.
//
// The proper fix would be to check upon login and only upon login if GCS folders that we're subscribed to are still existent.
//
if (subscribedFolder
&& [subscribedFolder ocsFolderForPath: [subscribedFolder ocsPath]]
&& ![sm validatePermission: SOGoPerm_AccessObject
onObject: subscribedFolder
inContext: context])
+61 -21
View File
@@ -365,7 +365,7 @@ static NSString *sieveScriptName = @"sogo";
}
else
scriptError = @"Rule lacks a 'value' parameter";
return (scriptError == nil);
}
@@ -783,7 +783,7 @@ static NSString *sieveScriptName = @"sogo";
SOGoDomainDefaults *dd;
NGSieveClient *client;
NSString *filterScript, *v;
BOOL b;
BOOL b, dateCapability;
unsigned int now;
dd = [user domainDefaults];
@@ -797,7 +797,7 @@ static NSString *sieveScriptName = @"sogo";
if (!client)
return NO;
// We adjust the "methodRequirements" based on the server's
// We adjust the "methodRequirements" based on the server's
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
// potentially others) expose "imap4flags" as specified in RFC5332
if ([client hasCapability: @"imap4flags"])
@@ -806,7 +806,9 @@ static NSString *sieveScriptName = @"sogo";
[methodRequirements setObject: @"imap4flags" forKey: @"removeflag"];
[methodRequirements setObject: @"imap4flags" forKey: @"flag"];
}
dateCapability = [client hasCapability: @"date"] && [client hasCapability: @"relational"];
//
// Now let's generate the script
//
@@ -835,16 +837,22 @@ static NSString *sieveScriptName = @"sogo";
now = [[NSCalendarDate calendarDate] timeIntervalSince1970];
if (values && [[values objectForKey: @"enabled"] boolValue] &&
(![values objectForKey: @"startDateEnabled"] || [[values objectForKey: @"startDate"] intValue] < now))
(![[values objectForKey: @"startDateEnabled"] boolValue] ||
dateCapability || [[values objectForKey: @"startDate"] intValue] < now) &&
(![[values objectForKey: @"endDateEnabled"] boolValue] ||
dateCapability || [[values objectForKey: @"endDate"] intValue] > now))
{
NSCalendarDate *startDate, *endDate;
NSMutableArray *allConditions;
NSMutableString *vacation_script;
NSArray *addresses;
NSString *text, *templateFilePath, *customSubject;
SOGoTextTemplateFile *templateFile;
BOOL ignore, alwaysSend, useCustomSubject;
int days, i;
allConditions = [NSMutableArray array];
days = [[values objectForKey: @"daysBetweenResponse"] intValue];
addresses = [values objectForKey: @"autoReplyEmailAddresses"];
alwaysSend = [[values objectForKey: @"alwaysSend"] boolValue];
@@ -883,12 +891,43 @@ static NSString *sieveScriptName = @"sogo";
days = 7;
vacation_script = [NSMutableString string];
[req addObjectUniquely: @"vacation"];
// Skip mailing lists
if (ignore)
[vacation_script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) { "];
{
[allConditions addObject: @"not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"]"];
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"]"];
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\""];
}
// Start date of auto-reply
if ([[values objectForKey: @"startDateEnabled"] boolValue] && dateCapability)
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
startDate = [NSCalendarDate dateWithTimeIntervalSince1970:
[[values objectForKey: @"startDate"] intValue]];
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"ge\" \"date\" \"%@\"",
[startDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
}
// End date of auto-reply
if ([[values objectForKey: @"endDateEnabled"] boolValue] && dateCapability)
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
endDate = [NSCalendarDate dateWithTimeIntervalSince1970:
[[values objectForKey: @"endDate"] intValue]];
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"le\" \"date\" \"%@\"",
[endDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
}
// Apply conditions
if ([allConditions count])
[vacation_script appendFormat: @"if allof ( %@ ) { ",
[allConditions componentsJoinedByString: @", "]];
// Custom subject
if (useCustomSubject)
@@ -910,20 +949,21 @@ static NSString *sieveScriptName = @"sogo";
for (i = 0; i < [addresses count]; i++)
{
[vacation_script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]];
if (i == [addresses count]-1)
[vacation_script appendString: @"] "];
else
[vacation_script appendString: @", "];
}
[vacation_script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text];
if (ignore)
// Closing bracket of conditions
if ([allConditions count])
[vacation_script appendString: @"}\r\n"];
//
// See http://sogo.nu/bugs/view.php?id=2332 for details
// See https://sogo.nu/bugs/view.php?id=2332 for details
//
if (alwaysSend)
[script insertString: vacation_script atIndex: 0];
@@ -941,7 +981,7 @@ static NSString *sieveScriptName = @"sogo";
int i;
b = YES;
addresses = [values objectForKey: @"forwardAddress"];
if ([addresses isKindOfClass: [NSString class]])
addresses = [NSArray arrayWithObject: addresses];
@@ -952,11 +992,11 @@ static NSString *sieveScriptName = @"sogo";
if (v && [v length] > 0)
[script appendFormat: @"redirect \"%@\";\r\n", v];
}
if ([[values objectForKey: @"keepCopy"] boolValue])
[script appendString: @"keep;\r\n"];
}
if ([req count])
{
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
@@ -970,7 +1010,7 @@ static NSString *sieveScriptName = @"sogo";
result = [client setActiveScript: @""];
// We delete the existing Sieve script
result = [client deleteScript: sieveScriptName];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"WARNING: Could not delete Sieve script - continuing...: %@", result];
}
@@ -980,13 +1020,13 @@ static NSString *sieveScriptName = @"sogo";
if (b && [script length])
{
result = [client putScript: sieveScriptName script: script];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"Could not upload Sieve script: %@", result];
[client closeConnection];
[client closeConnection];
return NO;
}
result = [client setActiveScript: sieveScriptName];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"Could not enable Sieve script: %@", result];
+2
View File
@@ -94,6 +94,8 @@
- (int) maximumFailedLoginInterval;
- (int) failedLoginBlockInterval;
- (int) maximumMessageSizeLimit;
- (int) maximumMessageSubmissionCount;
- (int) maximumRecipientCount;
- (int) maximumSubmissionInterval;
+8
View File
@@ -578,6 +578,14 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return v;
}
//
//
//
- (int) maximumMessageSizeLimit
{
return [self integerForKey: @"SOGoMaximumMessageSizeLimit"];
}
//
//
//
+12 -7
View File
@@ -531,9 +531,9 @@ static Class NSNullK;
grace: (int *) _grace
useCache: (BOOL) useCache
{
NSString *dictPassword, *username, *jsonUser;
NSMutableDictionary *currentUser;
NSDictionary *failedCount;
NSString *dictPassword, *username, *jsonUser;
SOGoSystemDefaults *sd;
BOOL checkOK;
@@ -573,23 +573,25 @@ static Class NSNullK;
failedCount = [[SOGoCache sharedCache] failedCountForLogin: username];
if (failedCount)
{
unsigned int current_time, start_time, delta, block_time;
unsigned int current_time, last_request_time, start_time, delta_start, delta_last_request, block_time;
current_time = [[NSCalendarDate date] timeIntervalSince1970];
start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue];
delta = current_time - start_time;
last_request_time = [[failedCount objectForKey: @"LastRequestDate"] unsignedIntValue];
delta_start = current_time - start_time;
delta_last_request = current_time - last_request_time;
block_time = [sd failedLoginBlockInterval];
if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] &&
delta >= [sd maximumFailedLoginInterval] &&
delta <= block_time )
delta_last_request >= [sd maximumFailedLoginInterval] &&
delta_start <= block_time )
{
*_perr = PolicyAccountLocked;
return NO;
}
if (delta > block_time)
if (delta_start > block_time)
{
[[SOGoCache sharedCache] setFailedCount: 0
forLogin: username];
@@ -832,8 +834,11 @@ static Class NSNullK;
{
currentSource = [_sources objectForKey: sourceID];
// Use the provided domain during the lookup. If none is defined, use the source's one
// so if there's a match based on the source's domain, the user ID will be associated
// to the right source.
userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID
inDomain: theDomain];
inDomain: (theDomain ? theDomain : [currentSource domain])];
if (userEntry)
{
[theCurrentUser setObject: sourceID forKey: @"SOGoSource"];
+1 -1
View File
@@ -14,7 +14,7 @@ DEBUG=False
# must be kept in sync with SoObjects/SOGo/SOGoDefaults.plist
# this should probably be fetched magically...
SOGoSupportedLanguages = [ "Arabic", "Basque", "Catalan", "ChineseTaiwan", "Croatian", "Czech", "Dutch", "Danish", "Welsh", "English", "Finnish",
"SpanishSpain", "SpanishArgentina", "French", "German",
"SpanishSpain", "SpanishArgentina", "French", "German", "Hebrew",
"Icelandic", "Italian", "Lithuanian", "Macedonian", "Hungarian", "Portuguese", "BrazilianPortuguese",
"NorwegianBokmal", "NorwegianNynorsk", "Polish", "Russian", "Serbian", "Slovak",
"Slovenian", "Swedish", "TurkishTurkey", "Ukrainian" ];
+2 -2
View File
@@ -107,10 +107,10 @@ class WebDAVClient:
self.conn.request(query.method, query.url,
body, self.prepare_headers(query, body))
try:
query.set_response(self.conn.getresponse());
query.set_response(self.conn.getresponse())
except httplib.BadStatusLine, e:
print e
query.set_response(self.conn.getresponse());
query.set_response(self.conn.getresponse())
query.duration = time.time() - query.start
class HTTPSimpleQuery:
+2
View File
@@ -20,6 +20,8 @@ $(SOGO_TOOL)_OBJC_FILES += \
SOGoToolRemove.m \
SOGoToolRemoveDoubles.m \
SOGoToolRenameUser.m \
SOGoToolCheckupUser.m \
SOGoToolCleanupUser.m \
SOGoToolRestore.m \
SOGoToolCreateFolder.m \
SOGoToolUserPreferences.m \
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoSockD.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2017 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoSockD.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2017 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Loaded 100 of 416 files, more files were not shown because too many files have changed in this diff. Show more