Compare commits

...
207 Commits
Author SHA1 Message Date
Francis Lachapelle 3d0636c756 Update ChangeLog 2017-01-23 10:02:35 -05:00
Francis Lachapelle 655e5ffe01 (css) Update default theme 2017-01-23 09:59:25 -05:00
Francis Lachapelle f65514f13e (js/css) Update generated files 2017-01-23 09:57:38 -05:00
Francis Lachapelle b60749e86c Preparation for release 3.2.6 2017-01-23 09:56:40 -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
304 changed files with 12343 additions and 6214 deletions

No files matched your search

+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];
+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];
}
}
//
+94 -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,10 @@ void handle_eas_terminate(int signum)
{
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
inContainer: collection];
[appointmentObject saveComponent: event];
if (folderType == ActiveSyncEventFolder)
[appointmentObject saveComponent: event force: YES];
else
[appointmentObject saveComponent: event];
}
if (uidCache && [calendarId length] > 64)
@@ -2651,6 +2659,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 +2706,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 +2730,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 +2828,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 +3169,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 +3204,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 +3220,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 +3355,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
{
@@ -3993,23 +4039,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 +4102,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"];
+45 -35
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;
}
+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);
}
+2278
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.6 - January 2017</releaseinfo>
<subtitle>for version 3.2.6</subtitle>
<date>2017-01-23</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.6
// 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;
+105 -1
View File
@@ -1,10 +1,114 @@
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
+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 -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 = (
+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;
+25 -14
View File
@@ -423,26 +423,32 @@ static Class iCalEventK = nil;
//
// If the user is the owner of the calendar, by default we include the freebusy information.
//
// If the user is NOT the owner of the calendar, by default we exclude the freebusy information.
// If the user is NOT the owner of the calendar but is subscribed, by default we exclude the freebusy information.
//
// We must include the freebusy information of other users if we are actually looking at their freebusy information
// but we aren't necessarily subscribed to their calendars.
//
- (BOOL) includeInFreeBusy
{
NSString *userLogin, *subscriptionReference;
NSNumber *excludeFromFreeBusy;
NSString *userLogin;
BOOL is_owner;
BOOL is_owner, is_subscription;
userLogin = [[context activeUser] login];
is_owner = [userLogin isEqualToString: self->owner];
// We check if we're subscribed. We must NOT use here [self isSubscribed] as it'll look
// at the owner's settings, and not the activeUser's settings.
subscriptionReference = [NSString stringWithFormat: @"%@:Calendar/%@", self->owner, [self nameInContainer]];
is_subscription = [[[[[self->context activeUser] userSettings] objectForKey: @"Calendar"] objectForKey: @"SubscribedFolders"] containsObject: subscriptionReference];
// Check if the owner (not the active user) has excluded the calendar from her/his free busy data.
excludeFromFreeBusy
= [self folderPropertyValueInCategory: @"FreeBusyExclusions"
forUser: [context activeUser]];
if ([self isSubscription])
if (is_subscription)
{
// If the user has not yet set an include/not include fb information let's EXCLUDE it.
if (!excludeFromFreeBusy)
@@ -696,7 +702,7 @@ static Class iCalEventK = nil;
if ([title length])
[baseWhere
addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString]]];
if (component)
{
@@ -976,7 +982,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSCalendarDate *recurrenceId, *masterEndDate, *endDate;
NSMutableDictionary *newRecord;
NGCalendarDateRange *newRecordRange;
NSComparisonResult compare;
NSNumber *dateSecs;
id master;
@@ -1437,14 +1442,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 +2625,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 +3243,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 +3328,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 +3413,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]];
}
+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
+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
+139 -173
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};
@@ -142,18 +144,18 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
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;
@@ -163,7 +165,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!strncasecmp(theCString,b,slen))
@@ -176,7 +178,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!memcmp(theCString,b,slen))
@@ -185,7 +187,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
}
}
}
return NSMakeRange(NSNotFound,0);
}
@@ -271,7 +273,7 @@ static NSString *userAgent = nil;
NSFileManager *fm;
fm = [NSFileManager defaultManager];
return ([fm createDirectoriesAtPath: [container userSpoolFolderPath]
attributes: nil]
&& [fm createDirectoriesAtPath: [self draftFolderPath]
@@ -308,7 +310,7 @@ static NSString *userAgent = nil;
messageID = [NSString generateMessageID];
[headers setObject: messageID forKey: @"message-id"];
}
priority = [newHeaders objectForKey: @"X-Priority"];
if (priority)
{
@@ -641,6 +643,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 +658,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 +754,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 +785,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 +847,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 +974,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 +1024,7 @@ static NSString *userAgent = nil;
- (NSString *) sender
{
id tmp;
if ((tmp = [headers objectForKey: @"from"]) == nil)
return nil;
if ([tmp isKindOfClass:[NSArray class]])
@@ -1098,52 +1070,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 +1118,7 @@ static NSString *userAgent = nil;
reason: @"Could not write attachment to draft!"];
}
}
return nil; /* everything OK */
}
@@ -1173,21 +1129,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 +1150,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 +1173,7 @@ static NSString *userAgent = nil;
*/
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
@@ -1232,7 +1181,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 +1191,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 +1212,9 @@ static NSString *userAgent = nil;
// Add the HTML part
[body addBodyPart: [self bodyPartForText]];
}
[message setBody: body];
return message;
}
@@ -1287,9 +1236,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 +1256,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 +1294,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 +1303,7 @@ static NSString *userAgent = nil;
}
attachAsString = NO;
attachAsRFC822 = NO;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
@@ -1369,22 +1315,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 +1345,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 +1361,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 +1417,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 +1443,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 +1486,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 +1500,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 +1581,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 +1594,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 +1612,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 +1636,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 +1658,10 @@ static NSString *userAgent = nil;
{
NSMutableArray *bodyParts;
NGMimeMessage *message;
NSArray *allBodyParts;
NGMutableHashMap *map;
NSString *newText;
BOOL has_inline_images;
message = nil;
@@ -1730,11 +1683,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 +1705,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 +1729,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 +1793,9 @@ static NSString *userAgent = nil;
- (NSException *) sendMail
{
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
if ([ud mailAddOutgoingAddresses])
{
NSString *recipient, *emailAddress, *addressBook, *uid;
@@ -1840,9 +1806,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 +1823,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 +1835,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];
@@ -1921,7 +1887,7 @@ static NSString *userAgent = nil;
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
+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];
}
+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: 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])
{
+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]
+1 -1
View File
@@ -104,7 +104,7 @@
SOGoTrashFolderName = "Trash";
SOGoJunkFolderName = "Junk";
SOGoMailComposeMessageType = "html";
SOGoMailComposeFontSize = 0;
SOGoMailComposeFontSize = "0";
SOGoMailDisplayRemoteInlineImages = "never";
SOGoMailAutoSave = "5";
+3 -7
View File
@@ -475,9 +475,8 @@ static NSArray *childRecordFields = nil;
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: @"/"]];
davURL = [NSURL URLWithString: [newPath componentsJoinedByString: @"/"]
relativeToURL: davURL];
return davURL;
}
@@ -500,10 +499,7 @@ static NSArray *childRecordFields = nil;
[container nameInContainer],
[self realNameInContainer]];
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];
+1 -1
View File
@@ -293,7 +293,7 @@
if (user)
{
[logins addObject: login];
[logins addObject: [user loginInDomain]];
[_members addObject: user];
}
[pool release];
+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"] ||
dateCapability || [[values objectForKey: @"startDate"] intValue] < now) &&
(![values objectForKey: @"endDateEnabled"] ||
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"];
+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:
+1
View File
@@ -20,6 +20,7 @@ $(SOGO_TOOL)_OBJC_FILES += \
SOGoToolRemove.m \
SOGoToolRemoveDoubles.m \
SOGoToolRenameUser.m \
SOGoToolCleanupUser.m \
SOGoToolRestore.m \
SOGoToolCreateFolder.m \
SOGoToolUserPreferences.m \
+52 -51
View File
@@ -94,9 +94,9 @@
- (void) usage
{
fprintf (stderr, "backup directory ALL|user1 [user2] ...\n\n"
" directory the target directory where backup files will be stored\n"
" user the user of whom to save the data or ALL for everybody\n\n"
"Example: sogo-tool backup /tmp/foo ALL\n");
" directory the target directory where backup files will be stored\n"
" user the user of whom to save the data or ALL for everybody\n\n"
"Example: sogo-tool backup /tmp/foo ALL\n");
}
- (BOOL) checkDirectory
@@ -137,7 +137,6 @@
int count, max;
lm = [SOGoUserManager sharedUserManager];
pool = [[NSAutoreleasePool alloc] init];
max = [users count];
user = [users objectAtIndex: 0];
@@ -156,61 +155,63 @@
folderLocation = [fm folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
allSqlUsers = [NSMutableArray new];
sql
= [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
{
allSqlUsers = [NSMutableArray new];
sql
= [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
users = allSqlUsers;
max = [users count];
}
users = allSqlUsers;
max = [users count];
[allSqlUsers autorelease];
}
}
pool = [[NSAutoreleasePool alloc] init];
allUsers = [NSMutableArray new];
for (count = 0; count < max; count++)
{
if (count > 0 && count%100 == 0)
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
user = [users objectAtIndex: count];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
[allUsers addObject: infos];
else
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
r = [user rangeOfString: @"@"];
r = [user rangeOfString: @"@"];
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
}
[allUsers autorelease];
@@ -261,9 +262,9 @@
if (fc)
{
sql
= [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
@" WHERE c_path = '%@'"),
[folderLocation gcsTableName], folder];
= [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
@" WHERE c_path = '%@'"),
[folderLocation gcsTableName], folder];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
row = [fc fetchAttributes: attrs withZone: NULL];
@@ -341,7 +342,7 @@
forKey: @"acl"];
[folderRecord setObject: tableRecord forKey: folder];
rc = YES;
}
}
else
{
NSLog(@"Unable to extract records for folder %@", folder);
@@ -398,11 +399,11 @@
currentSource = [lm sourceWithID: sourceID];
userEntry = [currentSource lookupContactEntry: uid inDomain: domain];
if (userEntry)
{
{
[userRecord setObject: [userEntry ldifRecordAsString]
forKey: @"ldif_record"];
done = YES;
}
}
}
return YES;
+294
View File
@@ -0,0 +1,294 @@
/* SOGoToolCleanup.m - this file is part of SOGo
*
* Copyright (C) 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
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
#import <GDLAccess/EOAdaptorChannel.h>
#import <GDLContentStore/GCSChannelManager.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <GDLContentStore/GCSFolder.h>
#import <GDLContentStore/NSURL+GCS.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoSystemDefaults.h>
#import "SOGoTool.h"
@interface SOGoToolCleanup : SOGoTool
{
NSArray *usersToCleanup;
unsigned int days;
}
@end
@implementation SOGoToolCleanup
+ (NSString *) command
{
return @"cleanup";
}
+ (NSString *) description
{
return @"cleanup deleted elements of user(s)";
}
- (id) init
{
if ((self = [super init]))
{
usersToCleanup = nil;
days = 0;
}
return self;
}
- (void) dealloc
{
[usersToCleanup release];
[super dealloc];
}
- (void) usage
{
fprintf (stderr, "cleanup [days] [user]...\n\n"
" days the age of deleted records to purge in days\n"
" user the user to purge the records or ALL for everybody\n\n"
"Example: sogo-tool cleanup jdoe\n");
}
- (BOOL) fetchUserIDs: (NSArray *) users
{
NSAutoreleasePool *pool;
SOGoUserManager *lm;
NSDictionary *infos;
NSString *user;
id allUsers;
int count, max;
lm = [SOGoUserManager sharedUserManager];
max = [users count];
user = [users objectAtIndex: 0];
if (max == 1 && [user isEqualToString: @"ALL"])
{
GCSFolderManager *fm;
GCSChannelManager *cm;
NSURL *folderLocation;
EOAdaptorChannel *fc;
NSArray *attrs;
NSMutableArray *allSqlUsers;
NSString *sql;
fm = [GCSFolderManager defaultFolderManager];
cm = [fm channelManager];
folderLocation = [fm folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
allSqlUsers = [NSMutableArray new];
sql = [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
users = allSqlUsers;
max = [users count];
[allSqlUsers autorelease];
}
}
pool = [[NSAutoreleasePool alloc] init];
allUsers = [NSMutableArray new];
for (count = 0; count < max; count++)
{
if (count > 0 && count%100 == 0)
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
user = [users objectAtIndex: count];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
r = [user rangeOfString: @"@"];
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
}
[allUsers autorelease];
ASSIGN (usersToCleanup, allUsers);
DESTROY(pool);
return ([usersToCleanup count] > 0);
}
- (BOOL) parseArguments
{
BOOL rc;
NSRange usersRange;
int max;
max = [arguments count];
if (max > 1)
{
days = [[arguments objectAtIndex: 0] intValue];
usersRange.location = 1;
usersRange.length = max - 1;
rc = [self fetchUserIDs: [arguments subarrayWithRange: usersRange]];
}
else
{
[self usage];
rc = NO;
}
return rc;
}
- (BOOL) cleanupFolder: (NSString *) folder
withFM: (GCSFolderManager *) fm
{
GCSFolder *gcsFolder;
NSException *error;
BOOL rc;
unsigned int count;
gcsFolder = [fm folderAtPath: folder];
count = [gcsFolder recordsCountDeletedBefore: days];
error = nil;
if (count > 0)
error = [gcsFolder purgeDeletedRecordsBefore: days];
if (error)
{
NSLog(@"Unable to purge records of folder %@", folder);
rc = NO;
}
else
{
NSLog(@"Purged %u records from folder %@", count, folder);
rc = YES;
}
return rc;
}
- (BOOL) cleanupUserFolders: (NSString *) uid
{
GCSFolderManager *fm;
NSArray *folders;
int count, max;
NSString *basePath, *folder;
fm = [GCSFolderManager defaultFolderManager];
basePath = [NSString stringWithFormat: @"/Users/%@", uid];
folders = [fm listSubFoldersAtPath: basePath recursive: YES];
max = [folders count];
for (count = 0; count < max; count++)
{
folder = [NSString stringWithFormat: @"%@/%@", basePath, [folders objectAtIndex: count]];
//NSLog (@"folder %d: %@", count, folder);
[self cleanupFolder: folder withFM: fm];
}
return YES;
}
- (BOOL) cleanupUser: (NSDictionary *) theUser
{
NSString *gcsUID, *domain;
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
domain = [theUser objectForKey: @"c_domain"];
gcsUID = [theUser objectForKey: @"c_uid"];
if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound)
gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, domain];
return [self cleanupUserFolders: gcsUID];
}
- (BOOL) proceed
{
NSAutoreleasePool *pool;
int count, max;
BOOL rc;
rc = YES;
pool = [NSAutoreleasePool new];
max = [usersToCleanup count];
for (count = 0; rc && count < max; count++)
{
rc = [self cleanupUser: [usersToCleanup objectAtIndex: count]];
if ((count % 10) == 0)
[pool emptyPool];
}
[pool release];
return rc;
}
- (BOOL) run
{
return ([self parseArguments] && [self proceed]);
}
@end
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Delegovaný je organizátor. Prosím určete jiného delegovaného.";
"delegate is a participant" = "Delegovaný je již účastníkem.";
"delegate is a group" = "Určená adresa odpovídá skupině. Delegovat můžete pouze na osobu.";
"Snooze for " = "Odložit o ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 hodinu";
"1 day" = "1 den";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Začátek";
"Due Date" = "Do dne";
"Location" = "Místo";
"Snooze" = "Odložit";
"Snooze for " = "Odložit o ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 hodinu";
"1 day" = "1 den";
/* mail labels */
"Important" = "Důležitý";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Špatné uživatelské jméno nebo heslo.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Zadejte alespoň %{minimumSearchLength} znaků";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Byl dosažen limit pro velikost nahraného souboru";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Přepnout zobrazení";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Přepnout rozsah položek";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Zobrazit nebo skrýt tuto nápovědu";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + mezerník";
/* Up arrow key */
"key_up" = "↑";
+13 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "The delegate is the organizer. Please specify a different delegate.";
"delegate is a participant" = "The delegate is already a participant.";
"delegate is a group" = "The specified address corresponds to a group. You can only delegate to a unique person.";
"Snooze for " = "Snooze for ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 hour";
"1 day" = "1 day";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Start";
"Due Date" = "Due Date";
"Location" = "Location";
"Snooze" = "Snooze";
"Snooze for " = "Snooze for ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 hour";
"1 day" = "1 day";
/* mail labels */
"Important" = "Important";
@@ -118,9 +119,12 @@
/* Authentication failed */
"Wrong username or password." = "Wrong username or password.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "File size upload limit reached";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Toggle visibility";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "L'adresse spécifiée correspond à l'organisateur. Veuillez entrer un autre délégué.";
"delegate is a participant" = "Le délégué est déjà un participant.";
"delegate is a group" = "L'adresse spécifiée correspond à un groupe. Vous ne pouvez déléguer qu'à une personne.";
"Snooze for " = "Rappel dans ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 heure";
"1 day" = "1 jour";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Début";
"Due Date" = "Échéance";
"Location" = "Lieu";
"Snooze" = "Rappel";
"Snooze for " = "Rappel dans ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 heure";
"1 day" = "1 jour";
/* mail labels */
"Important" = "Important";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Mauvais nom d'utilisateur ou mot de passe.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Entrez au moins %{minimumSearchLength} caractères";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Le fichier dépasse la taille limite permise";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Basculer l'affichage";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Basculer l'étendue des éléments";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Afficher ou cache cette aide";
/* Space key */
"key_space" = "espace";
/* Shift and space key */
"key_shift+space" = "maj. + espace";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Der Vertreter ist der Organisator. Bitte einen anderen Vertreter angeben.";
"delegate is a participant" = "Der Vertreter ist bereits ein Teilnehmer.";
"delegate is a group" = "Die angegebene Adresse gehört einer Gruppe. Es kann nur an eine einzelne Person delegiert werden.";
"Snooze for " = "Erneut erinnern nach ";
"5 minutes" = "5 Minuten";
"10 minutes" = "10 Minuten";
"15 minutes" = "15 Minuten";
"30 minutes" = "30 Minuten";
"45 minutes" = "45 Minuten";
"1 hour" = "1 Stunde";
"1 day" = "1 Tag";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Beginn";
"Due Date" = "Fällig";
"Location" = "Ort";
"Snooze" = "Erneut erinnern";
"Snooze for " = "Erneut erinnern nach ";
"5 minutes" = "5 Minuten";
"10 minutes" = "10 Minuten";
"15 minutes" = "15 Minuten";
"30 minutes" = "30 Minuten";
"45 minutes" = "45 Minuten";
"1 hour" = "1 Stunde";
"1 day" = "1 Tag";
/* mail labels */
"Important" = "Wichtig";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Falscher Benutzername oder falsches Passwort";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Mindestens %{minimumSearchLength} Zeichen eingeben";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Maximale Dateigröße für das Hochladen erreicht";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Sichtbarkeit umschalten";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Mehrere Punkte umschalten";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Diese Hilfe anzeigen oder ausblenden";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "Umschalt + Leerzeichen";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Wskazany delegat jest organizatorem. Wskaż innego delegata.";
"delegate is a participant" = "Delegat jest już uczestnikiem.";
"delegate is a group" = "Wskazany adres jest grupą. Możesz oddelegować tylko pojedynczną osobę.";
"Snooze for " = "Drzemka przez ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 godzinę";
"1 day" = "1 dzień";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Początek";
"Due Date" = "Termin";
"Location" = "Miejsce";
"Snooze" = "Drzemka";
"Snooze for " = "Drzemka przez ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 godzinę";
"1 day" = "1 dzień";
/* mail labels */
"Important" = "Ważne";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Zła nazwa użytkownika lub hasło.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Wprowadź co najmniej %{minimumSearchLength} znaków";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Rozmiar pliku jest większy niż dopuszczalny limit";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Odwróć widok";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Odwróć wskazany zakres";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Pokaż lub ukryj tę podpowiedź";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + spacja";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Это организатор. Выберите другого приглашаемого.";
"delegate is a participant" = "Приглашаемый уже участник.";
"delegate is a group" = "Указанный адрес - адрес группы. Вы можете делегировать приглашение лишь отдельным людям.";
"Snooze for " = "Отложить на";
"5 minutes" = "5 минут";
"10 minutes" = "10 минут";
"15 minutes" = "15 минут";
"30 minutes" = "30 минут";
"45 minutes" = "45 минут";
"1 hour" = "1 час";
"1 day" = "1 день";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Начало";
"Due Date" = "Дата начала";
"Location" = "Место";
"Snooze" = "Отложить";
"Snooze for " = "Отложить на";
"5 minutes" = "5 минут";
"10 minutes" = "10 минут";
"15 minutes" = "15 минут";
"30 minutes" = "30 минут";
"45 minutes" = "45 минут";
"1 hour" = "1 час";
"1 day" = "1 день";
/* mail labels */
"Important" = "Важно";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Неправильный логин или пароль.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Введите не менее %{minimumSearchLength} символов";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Достигнуто ограничение на размер загружаемого файла";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Переключить видимость";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Сменить состояние нескольких элементов";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Показать или скрыть подсказку";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "Shift + Пробел";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Delegát je organizátorom. Zadajte prosím iného delegáta.";
"delegate is a participant" = "Delegát je už účastníkom.";
"delegate is a group" = "Zadaná adresa zodpovedá skupine. Môžete ju delegovať len na jedinečnú osobu.";
"Snooze for " = "Odložiť";
"5 minutes" = "5 minút";
"10 minutes" = "10 minút";
"15 minutes" = "15 minút";
"30 minutes" = "30 minút";
"45 minutes" = "45 minút";
"1 hour" = "1 hodinu";
"1 day" = "1 deň";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Štart";
"Due Date" = "Splatnosť";
"Location" = "Umiestnenie";
"Snooze" = "Odložit";
"Snooze for " = "Odložiť do";
"5 minutes" = "5 minút";
"10 minutes" = "10 minút";
"15 minutes" = "15 minút";
"30 minutes" = "30 minút";
"45 minutes" = "45 minút";
"1 hour" = "1 hodinu";
"1 day" = "1 deň";
/* mail labels */
"Important" = "Dôležité";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Nesprávne uživateľské meno alebo heslo.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Vložte minimálne %{minimumSearchLength} znakov";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Bol dosiahnutý limit pre veľkosť nahraného súboru";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Prepnúť zobrazenie";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Prepnúť rozsah položiek";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Zobraziť alebo skryť túto nápovedu";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + medzerník";
/* Up arrow key */
"key_up" = "↑";
@@ -65,18 +65,10 @@
= "Bu modül veya sisteme erişim yetkiniz yok. Lütfen, sistem yöneticisiyle iletişime geçin.";
"You don't have the required privileges to perform the operation."
= "Bu işlemi gerçekleştirmek için gereken yetkilere sahip değilsiniz.";
"noEmailForDelegation" = "Davetinize temsilci atayacağınız adresleri belirtmelisiniz.";
"delegate is organizer" = "Temsilci etkinliği düzenleyen kişidir. Lütfen başka bir temsilci seçin. ";
"delegate is a participant" = "Temsilci zaten bu etkinliğe katılıyor.";
"delegate is a group" = "Belirtilen adres bir gruba denk geliyor. Sadece bir kişiyi temsilci atayabilirsiniz.";
"Snooze for " = "Beklenecek süre";
"5 minutes" = "5 dakika";
"10 minutes" = "10 dakika";
"15 minutes" = "15 dakika";
"30 minutes" = "30 dakika";
"45 minutes" = "45 dakika";
"1 hour" = "1 saat";
"1 day" = "1 gün";
"noEmailForDelegation" = "Davetinize vekil atayacağınız kişinin adresini belirtmelisiniz.";
"delegate is organizer" = "Vekil edilen kişi etkinliği düzenleyen kişidir. Lütfen başka bir vekil belirleyin edin. ";
"delegate is a participant" = "Vekil edilen kişi zaten bu etkinliğe katılıyor.";
"delegate is a group" = "Belirtilen adres bir gruba denk geliyor. Sadece bir kişiyi vekil tayin edebilirsiniz.";
/* common buttons */
"OK" = "Tamam";
@@ -89,6 +81,15 @@
"Start" = "Başlangıç";
"Due Date" = "Son Tarih";
"Location" = "Konum";
"Snooze" = "Ertele";
"Snooze for " = "Beklenecek süre";
"5 minutes" = "5 dakika";
"10 minutes" = "10 dakika";
"15 minutes" = "15 dakika";
"30 minutes" = "30 dakika";
"45 minutes" = "45 dakika";
"1 hour" = "1 saat";
"1 day" = "1 gün";
/* mail labels */
"Important" = "Önemli";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Kullanıcı adı veya şifre yanlış.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "En az %{minimumSearchLength} harf girin";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Yükleme üst sınırı aşıldı";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Görünürlük geçişi";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Ögeler arasında geçiş yap";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Bu yardımı gizle veya göster";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "üst karakter + boşluk";
/* Up arrow key */
"key_up" = "↑";
+49 -1
View File
@@ -20,12 +20,15 @@
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <SoObjects/SOGo/SOGoParentFolder.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserSettings.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
@@ -91,4 +94,49 @@
return response;
}
- (WOResponse *) saveFoldersActivationAction
{
NSDictionary *params;
NSEnumerator *foldersEnumerator;
NSMutableArray *folderSubscription;
NSMutableDictionary *moduleSettings;
NSString *baseFolder, *folderName;
SOGoParentFolder *clientObject;
SOGoUser *activeUser;
SOGoUserSettings *us;
WORequest *request;
BOOL makeActive;
request = [context request];
params = [[request contentAsString] objectFromJSONString];
activeUser = [context activeUser];
clientObject = [self clientObject];
baseFolder = [clientObject nameInContainer];
us = [activeUser userSettings];
moduleSettings = [us objectForKey: baseFolder];
if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary];
[us setObject: moduleSettings forKey: baseFolder];
folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"];
if (!folderSubscription)
{
folderSubscription = [NSMutableArray array];
[moduleSettings setObject: folderSubscription forKey: @"InactiveFolders"];
}
foldersEnumerator = [params keyEnumerator];
while ((folderName = [foldersEnumerator nextObject]))
{
makeActive = [[params objectForKey: folderName] boolValue];
if (makeActive)
[folderSubscription removeObject: folderName];
else
[folderSubscription addObjectUniquely: folderName];
}
[us synchronize];
return [self responseWith204];
}
@end
+5
View File
@@ -78,6 +78,11 @@
actionClass = "UIxParentFolderActions";
actionName = "createFolder";
};
saveFoldersActivation = {
protectedBy = "Change Images And Files";
actionClass = "UIxParentFolderActions";
actionName = "saveFoldersActivation";
};
};
};
SOGoFolder = {
@@ -246,7 +246,7 @@
"Add Screen Name" = "Додади прекар";
"Synchronization" = "Синхронизација";
"Synchronize" = "Синхронизирај";
"Sucessfully subscribed to address book" = "Успешно сте претплатени на адресарот";
"Sucessfully subscribed to address book" = "Успешно се претплативте на адресната книга";
/* Aria label for scope of search on contacts */
"Search scope" = "Предмет на пребарување";
@@ -153,8 +153,8 @@
= "Bu klasöre üye olunamıyor.";
/* acls */
"Access rights to" = "Adres defterinin erişim yetkileri";
"For user" = "Aşağıdaki kullanıcı için";
"Access rights to" = "Adres defteri";
"For user" = "Erişen";
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
"Public Access" = "Herkese Açık Erişim";
"This person can add cards to this addressbook."
+34 -16
View File
@@ -49,8 +49,17 @@
#import "UIxContactFolderActions.h"
static NSArray *photoTags = nil;
@implementation UIxContactFolderActions
+ (void) initialize
{
if (!photoTags)
{
photoTags = [[NSArray alloc] initWithObjects: @"jpegphoto", @"photo", @"thumbnailphoto", nil];
}
}
/* actions */
@@ -146,24 +155,26 @@
- (int) importLdifData: (NSString *) ldifData
{
NSMutableDictionary *entry, *encodedEntry;
SOGoContactLDIFEntry *ldifEntry;
NSArray *ldifContacts, *lines;
SOGoContactGCSFolder *folder;
NSString *key, *value;
NSArray *ldifContacts, *lines, *components;
NSMutableDictionary *entry;
NSEnumerator *keyEnumerator;
NSString *key, *uid, *line;
NGVCard *vCard;
NSString *uid;
int i,j,count,linesCount;
int rc = 0;
id value;
NSRange r;
int i, j, count, linesCount;
int rc;
folder = [self clientObject];
ldifContacts = [ldifData componentsSeparatedByString: @"\ndn"];
count = [ldifContacts count];
rc = 0;
for (i = 0; i < count; i++)
{
SOGoContactLDIFEntry *ldifEntry;
NSEnumerator *keyEnumerator;
NSMutableDictionary *encodedEntry;
encodedEntry = [NSMutableDictionary dictionary];
lines = [[ldifContacts objectAtIndex: i]
componentsSeparatedByString: @"\n"];
@@ -172,7 +183,6 @@
linesCount = [lines count];
for (j = 0; j < linesCount; j++)
{
NSString *line;
line = [lines objectAtIndex: j];
/* skip embedded comment lines */
@@ -188,17 +198,17 @@
if (key != NULL)
{
value = [[encodedEntry valueForKey: key]
stringByAppendingString: [line substringFromIndex: 1]];
stringByAppendingString: [line substringFromIndex: 1]];
[encodedEntry setValue: value forKey: key];
}
continue;
}
components = [line componentsSeparatedByString: @": "];
if ([components count] == 2)
r = [line rangeOfString: @": "];
if (r.location != NSNotFound)
{
key = [[components objectAtIndex: 0] lowercaseString];
value = [components objectAtIndex: 1];
key = [[line substringToIndex: r.location] lowercaseString];
value = [line substringFromIndex: NSMaxRange(r)];
if ([key length] == 0)
key = @"dn";
@@ -220,8 +230,16 @@
if ([key hasSuffix: @":"])
{
key = [key substringToIndex: [key length] - 1];
value = [value stringByDecodingBase64];
if ([photoTags containsObject: key])
value = [value dataByDecodingBase64];
else
value = [value stringByDecodingBase64];
}
// Standard key recognized in NGCards
if ([photoTags containsObject: key])
key = @"photo";
[entry setValue: value forKey: key];
}
+1 -522
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2005-2015 Inverse inc.
Copyright (C) 2005-2016 Inverse inc.
This file is part of SOGo.
@@ -62,73 +62,6 @@
[super dealloc];
}
/* accessors */
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// byEscapingHTMLString: (BOOL) escapeHTML
// asLinkScheme: (NSString *) scheme
// withLinkAttributes: (NSString *) attrs
// {
// NSMutableString *cardString;
// cardString = [NSMutableString stringWithCapacity: 80];
// value = [value stringByReplacingString: @"\r" withString: @""];
// if ([value length] > 0)
// {
// if (escapeHTML)
// value = [value stringByEscapingHTMLString];
// if ([scheme length] > 0)
// value = [NSString stringWithFormat: @"<a href=\"%@%@\" %@>%@</a>", scheme, value, attrs, value];
// if (label)
// [cardString appendFormat: @"<dt>%@</dt><dd>%@</dd>\n",
// [self labelForKey: label], value];
// else
// [cardString appendFormat: @"<dt></dt><dd>%@</dd>\n", value];
// }
// return cardString;
// }
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// {
// return [self _cardStringWithLabel: label
// value: value
// byEscapingHTMLString: YES
// asLinkScheme: nil
// withLinkAttributes: nil];
// }
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// asLinkScheme: (NSString *) scheme
// {
// return [self _cardStringWithLabel: label
// value: value
// byEscapingHTMLString: YES
// asLinkScheme: scheme
// withLinkAttributes: nil];
// }
// - (NSString *) displayName
// {
// return [self _cardStringWithLabel: @"Display Name:"
// value: [card fn]];
// }
// - (NSString *) nickName
// {
// return [self _cardStringWithLabel: @"Nickname:"
// value: [card nickname]];
// }
// - (NSString *) fullName
// {
// return [card fullName];
// }
- (NSArray *) _languageContactsCategories
{
NSArray *categoryLabels;
@@ -168,346 +101,6 @@
return cats;
}
// - (NSString *) primaryEmail
// {
// NSString *email, *fn, *attrs;
// email = [card preferredEMail];
// if ([email length] > 0)
// {
// fn = [card fn];
// if ([fn length] > 0)
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
// else
// attrs = email;
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
// }
// else
// {
// attrs = nil;
// }
// return [self _cardStringWithLabel: @"Email:"
// value: email
// byEscapingHTMLString: YES
// asLinkScheme: @"mailto:"
// withLinkAttributes: attrs];
// }
// - (NSArray *) secondaryEmails
// {
// NSMutableArray *secondaryEmails;
// NSString *email, *fn, *attrs;
// NSArray *emails;
// emails = [card secondaryEmails];
// secondaryEmails = [NSMutableArray array];
// attrs = nil;
// // We might not have a preferred item but rather something like this:
// // EMAIL;TYPE=work:dd@ee.com
// // EMAIL;TYPE=home:ff@gg.com
// //
// // or:
// //
// // EMAIL;TYPE=INTERNET:a@a.com
// // EMAIL;TYPE=INTERNET,HOME:b@b.com
// //
// // In this case, we always return the entry NOT matching the primaryEmail
// if ([emails count] > 0)
// {
// int i;
// for (i = 0; i < [emails count]; i++)
// {
// email = [[emails objectAtIndex: i] flattenedValuesForKey: @""];
// if ([email length])
// {
// fn = [card fn];
// if ([fn length])
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
// else
// attrs = email;
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
// value: email
// byEscapingHTMLString: YES
// asLinkScheme: @"mailto:"
// withLinkAttributes: attrs]];
// }
// }
// }
// else
// {
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
// value: nil]];
// }
// return secondaryEmails;
// }
// - (NSString *) screenName
// {
// NSString *screenName;
// screenName = [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""];
// return [self _cardStringWithLabel: @"Screen Name:"
// value: screenName
// asLinkScheme: @"aim:goim?screenname="];
// }
// - (NSString *) preferredTel
// {
// return [self _cardStringWithLabel: @"Phone Number:"
// value: [card preferredTel] asLinkScheme: @"tel:"];
// }
// - (NSString *) preferredAddress
// {
// return @"";
// }
// - (BOOL) hasTelephones
// {
// if (!phones)
// phones = [card childrenWithTag: @"tel"];
// return ([phones count] > 0);
// }
// - (NSString *) workPhone
// {
// // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX
// // one if we do see the FAX field BEFORE the WORK number.
// return [self _cardStringWithLabel: @"Work:" value: [card workPhone] asLinkScheme: @"tel:"];
// }
// - (NSString *) homePhone
// {
// return [self _cardStringWithLabel: @"Home:" value: [card homePhone] asLinkScheme: @"tel:"];
// }
// - (NSString *) fax
// {
// return [self _cardStringWithLabel: @"Fax:" value: [card fax] asLinkScheme: @"tel:"];
// }
// - (NSString *) mobile
// {
// return [self _cardStringWithLabel: @"Mobile:" value: [card mobile] asLinkScheme: @"tel:"];
// }
// - (NSString *) pager
// {
// return [self _cardStringWithLabel: @"Pager:" value: [card pager] asLinkScheme: @"tel:"];
// }
// - (BOOL) hasHomeInfos
// {
// BOOL result;
// NSArray *elements;
// elements = [card childrenWithTag: @"adr"
// andAttribute: @"type"
// havingValue: @"home"];
// if ([elements count] > 0)
// {
// result = YES;
// homeAdr = [elements objectAtIndex: 0];
// }
// else
// result = ([[card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"home"] count] > 0);
// return result;
// }
// - (NSString *) homePobox
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 0
// forKey: @""]];
// }
// - (NSString *) homeExtendedAddress
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 1
// forKey: @""]];
// }
// - (NSString *) homeStreetAddress
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 2
// forKey: @""]];
// }
// - (NSString *) homeCityAndProv
// {
// NSString *city, *prov;
// NSMutableString *data;
// city = [homeAdr flattenedValueAtIndex: 3 forKey: @""];
// prov = [homeAdr flattenedValueAtIndex: 4 forKey: @""];
// data = [NSMutableString string];
// [data appendString: city];
// if ([city length] > 0 && [prov length] > 0)
// [data appendString: @", "];
// [data appendString: prov];
// return [self _cardStringWithLabel: nil value: data];
// }
// - (NSString *) homePostalCodeAndCountry
// {
// NSString *postalCode, *country;
// NSMutableString *data;
// postalCode = [homeAdr flattenedValueAtIndex: 5 forKey: @""];
// country = [homeAdr flattenedValueAtIndex: 6 forKey: @""];
// data = [NSMutableString string];
// [data appendString: postalCode];
// if ([postalCode length] > 0 && [country length] > 0)
// [data appendFormat: @", "];
// [data appendString: country];
// return [self _cardStringWithLabel: nil value: data];
// }
// - (NSString *) _formattedURL: (NSString *) url
// {
// NSRange schemaR;
// NSString *schema, *data;
// if ([url length] > 0)
// {
// schemaR = [url rangeOfString: @"://"];
// if (schemaR.length > 0)
// {
// schema = [url substringToIndex: schemaR.location + schemaR.length];
// data = [url substringFromIndex: schemaR.location + schemaR.length];
// }
// else
// {
// schema = @"http://";
// data = url;
// }
// }
// else
// {
// schema = nil;
// data = nil;
// }
// return [self _cardStringWithLabel: nil
// value: data
// byEscapingHTMLString: YES
// asLinkScheme: schema
// withLinkAttributes: @"target=\"_blank\""];
// }
// - (NSString *) _urlOfType: (NSString *) aType
// {
// NSArray *elements;
// NSString *url;
// elements = [card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: aType];
// if ([elements count] > 0)
// url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// else
// url = nil;
// return [self _formattedURL: url];
// }
// - (NSString *) homeUrl
// {
// NSString *s;
// s = [self _urlOfType: @"home"];
// if (!s || [s length] == 0)
// {
// NSArray *elements;
// NSString *workURL;
// int i;
// elements = [card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"work"];
// workURL = nil;
// if ([elements count] > 0)
// workURL = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// elements = [card childrenWithTag: @"url"];
// if (workURL && [elements count] > 1)
// {
// for (i = 0; i < [elements count]; i++)
// {
// if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""]
// caseInsensitiveCompare: workURL] != NSOrderedSame)
// {
// s = [[elements objectAtIndex: i] flattenedValuesForKey: @""];
// break;
// }
// }
// }
// else if (!workURL && [elements count] > 0)
// {
// s = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// }
// if (s && [s length] > 0)
// s = [self _formattedURL: s];
// }
// return s;
// }
// - (BOOL) hasWorkInfos
// {
// BOOL result;
// NSArray *elements;
// elements = [card childrenWithTag: @"adr"
// andAttribute: @"type"
// havingValue: @"work"];
// if ([elements count] > 0)
// {
// result = YES;
// workAdr = [elements objectAtIndex: 0];
// }
// else
// result = (([[card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"work"] count] > 0)
// || [[card childrenWithTag: @"org"] count] > 0);
// return result;
// }
// - (NSString *) workTitle
// {
// return [self _cardStringWithLabel: nil value: [card title]];
// }
- (NSArray *) orgUnits
{
NSMutableArray *orgUnits;
@@ -640,120 +233,6 @@
return addresses;
}
// - (NSString *) workService
// {
// NSMutableArray *orgServices;
// NSArray *values;
// CardElement *org;
// NSString *service, *services;
// NSUInteger count, max;
// org = [card org];
// values = [org valuesForKey: @""];
// max = [values count];
// if (max > 1)
// {
// orgServices = [NSMutableArray arrayWithCapacity: max];
// for (count = 1; count < max; count++)
// {
// service = [org flattenedValueAtIndex: count forKey: @""];
// if ([service length] > 0)
// [orgServices addObject: service];
// }
// services = [orgServices componentsJoinedByString: @", "];
// }
// else
// services = nil;
// return [self _cardStringWithLabel: nil value: services];
// }
// - (NSString *) workUrl
// {
// return [self _urlOfType: @"work"];
// }
// - (BOOL) hasOtherInfos
// {
// return ([[card note] length] > 0
// || [[card bday] length] > 0
// || [[card tz] length] > 0);
// }
// - (NSString *) bday
// {
// SOGoDateFormatter *dateFormatter;
// NSCalendarDate *date;
// NSString *bday;
// date = [card birthday];
// bday = nil;
// if (date)
// {
// dateFormatter = [[[self context] activeUser] dateFormatterInContext: context];
// bday = [dateFormatter formattedDate: date];
// }
// return bday;
// //return [self _cardStringWithLabel: @"Birthday:" value: bday];
// }
// - (NSString *) tz
// {
// return [self _cardStringWithLabel: @"Timezone:" value: [card tz]];
// }
// - (NSArray *) notes
// {
// NSMutableArray *notes;
// NSString *note;
// NSUInteger count, max;
// notes = [NSMutableArray arrayWithArray: [card notes]];
// max = [notes count];
// for (count = 0; count < max; count++)
// {
// note = [notes objectAtIndex: count];
// note = [note stringByEscapingHTMLString];
// note = [note stringByReplacingString: @"\r\n"
// withString: @"<br />"];
// note = [note stringByReplacingString: @"\n"
// withString: @"<br />"];
// [notes replaceObjectAtIndex: count withObject: note];
// }
// return notes;
// }
/* hrefs */
// - (NSString *) completeHrefForMethod: (NSString *) _method
// withParameter: (NSString *) _param
// forKey: (NSString *) _key
// {
// NSString *href;
// [self setQueryParameter:_param forKey:_key];
// href = [self completeHrefForMethod:[self ownMethodName]];
// [self setQueryParameter:nil forKey:_key];
// return href;
// }
// - (NSString *)attributesTabLink {
// return [self completeHrefForMethod:[self ownMethodName]
// withParameter:@"attributes"
// forKey:@"tab"];
// }
// - (NSString *)debugTabLink {
// return [self completeHrefForMethod:[self ownMethodName]
// withParameter:@"debug"
// forKey:@"tab"];
// }
/* action */
- (id <WOActionResults>) defaultAction
+12 -3
View File
@@ -79,10 +79,18 @@
{
NSString *s;
WORequest *rq;
static NSArray *sortKeys = nil;
if (!sortKeys)
{
sortKeys = [NSArray arrayWithObjects: @"c_cn", @"c_sn", @"c_givenname", @"c_mail",
@"c_screenname", @"c_o", @"c_telephonenumber", nil];
[sortKeys retain];
}
rq = [context request];
s = [rq formValueForKey: @"sort"];
if (![s length])
if (![s length] || ![sortKeys containsObject: s])
s = [self defaultSortKey];
return s;
@@ -98,7 +106,8 @@
ascending = [[context request] formValueForKey: @"asc"];
if ([sort length])
{
{
sort = [self sortKey];
us = [[context activeUser] userSettings];
contactSettings = [us objectForKey: @"Contact"];
// Must create if it doesn't exist
@@ -1,7 +1,7 @@
ACCEPTED = "kabul edildi";
COMPLETED = "tamamlandı";
DECLINED = "reddedildi";
DELEGATED = "temsilci atanmış";
DELEGATED = "yetkilendirildi";
"IN-PROCESS" = "işleniyor";
"NEEDS-ACTION" = "eyleme ihtiyaç var";
TENTATIVE = "geçici";
@@ -16,7 +16,7 @@ Appointment = "Çalışma";
was = "önceden";
Organizer = "Düzenleyen";
Time = "Süre";
Time = "Zaman";
Attendees = "Katılımcılar";
request_info = "sizi toplantıya katılmaya davet ediyor. ";
"Add to calendar" = "Takvime ekle";
@@ -25,10 +25,10 @@ request_info = "sizi toplantıya katılmaya davet ediyor. ";
Accept = "Kabul et";
Decline = "Reddet";
Tentative = "Geçici";
"Delegate ..." = "Temsilci Ata ...";
"Delegated to" = "Temsilci atanan";
"Delegate ..." = "Yetkili Ata ...";
"Delegated to" = "Yetkilendirilen";
"Update status in calendar" = "Takvimdeki durumu güncelle";
"delegated from" = "tarafından temsilci atandı";
"delegated from" = "Yetkilendiren";
reply_info_no_attendee = "Bir etkinlik programına yanıt aldınız fakat yanıtı gönderen kişi katılımcı değil.";
reply_info = "Yapmış olduğunuz etkinlik davetinin yanıtıdır.";
"to" = "alıcı";
@@ -168,6 +168,7 @@
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
[viewer setBodyInfo: info];
[viewer setPartPath: [self childPartPath]];
[viewer setAttachmentIds: attachmentIds];
[renderedParts addObject: [viewer renderedPart]];
}
+13 -4
View File
@@ -28,6 +28,7 @@
#include <libxml/encoding.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/Mailer/SOGoMailBodyPart.h>
@@ -908,6 +909,8 @@ static NSData* _sanitizeContent(NSData *theData)
NSObject <SaxXMLReader> *parser;
NSData *preparsedContent;
SOGoMailObject *mail;
NSString *s;
xmlCharEncoding enc;
mail = [self clientObject];
@@ -917,7 +920,7 @@ static NSData* _sanitizeContent(NSData *theData)
createXMLReaderForMimeType: @"text/html"];
handler = [_UIxHTMLMailContentHandler new];
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
[handler setAttachmentIds: attachmentIds];
// We check if we got an unsupported charset. If so
// we convert everything to UTF-16{LE,BE} so it passes
@@ -926,8 +929,6 @@ static NSData* _sanitizeContent(NSData *theData)
enc = [self _xmlCharEncoding];
if (enc == XML_CHAR_ENCODING_ERROR)
{
NSString *s;
s = [NSString stringWithData: preparsedContent
usingEncodingNamed: [[bodyInfo objectForKey:@"parameterList"]
objectForKey: @"charset"]];
@@ -954,6 +955,14 @@ static NSData* _sanitizeContent(NSData *theData)
#endif
}
// Let's sanitize the string to make sure libxml doesn't go havoc
if (enc == XML_CHAR_ENCODING_UTF8)
{
s = [[NSString alloc] initWithData: preparsedContent encoding: NSUTF8StringEncoding];
preparsedContent = [[s safeString] dataUsingEncoding: NSUTF8StringEncoding];
RELEASE(s);
}
[handler setContentEncoding: enc];
[parser setContentHandler: handler];
@@ -1038,7 +1047,7 @@ static NSData* _sanitizeContent(NSData *theData)
encoding = @"us-ascii";
handler = [_UIxHTMLMailContentHandler new];
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
[handler setAttachmentIds: attachmentIds];
// We check if we got an unsupported charset. If so
// we convert everything to UTF-16{LE,BE} so it passes
+44 -8
View File
@@ -41,6 +41,7 @@
#import <Appointments/SOGoAppointmentFolders.h>
#import <Mailer/SOGoMailObject.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSString+Utilities.h>
#import <Mailer/SOGoMailBodyPart.h>
@@ -234,20 +235,20 @@
- (WOResponse *) _changePartStatusAction: (NSString *) newStatus
withDelegate: (iCalPerson *) delegate
{
WOResponse *response;
SOGoAppointmentObject *eventObject;
WOResponse *response;
iCalEvent *chosenEvent;
iCalAlarm *alarm;
chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
if (chosenEvent)
{
// For invitations, we take the organizers's alarm to start with
alarm = [[chosenEvent alarms] lastObject];
response = (WOResponse*)[eventObject changeParticipationStatus: newStatus
withDelegate: delegate
alarm: nil
alarm: alarm
forRecurrenceId: [chosenEvent recurrenceId]];
// if (ex)
// response = ex; //[self responseWithStatus: 500];
// else
if (!response)
response = [self responseWith204];
}
@@ -380,15 +381,50 @@
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
SOGoMailObject *mailObject;
NSString *emailFrom;
iCalPerson *p;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
p = [event findAttendeeWithEmail: emailFrom];
return [event findAttendeeWithEmail: emailFrom];
// We haven't found it yet, let's look in the identities
// associated to this user
if (!p)
{
SOGoUserManager *sm;
NSString *uid;
sm = [SOGoUserManager sharedUserManager];
uid = [sm getUIDForEmail: emailFrom];
if (uid)
{
NSArray *allEmails;
NSString *email;
SOGoUser *u;
int i;
u = [SOGoUser userWithLogin: uid];
allEmails = [u allEmails];
for (i = 0; i < [allEmails count]; i++)
{
email = [allEmails objectAtIndex: i];
if ([email caseInsensitiveCompare: emailFrom] == NSOrderedSame)
continue;
p = [event findAttendeeWithEmail: email];
if (p)
break;
}
}
}
return p;
}
- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent
+54 -25
View File
@@ -43,6 +43,7 @@
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserManager.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentObject.h>
@@ -470,31 +471,67 @@
return currentUser;
}
- (iCalPerson *) storedReplyAttendee
- (iCalPerson *) _attendeeFromMailSenderInEvent: (iCalEvent *) e
{
/*
TODO: since an attendee can have multiple email addresses, maybe we
should translate the email to an internal uid and then retrieve
all emails addresses for matching the participant.
Note: -findAttendeeWithEmail: does not parse the email!
*/
iCalEvent *e;
NSString *baseEmail;
iCalPerson *p;
p = nil;
e = [self storedEvent];
if (e)
baseEmail = [self replySenderBaseEMail];
/*
Note: -findAttendeeWithEmail: does not parse the email!
*/
p = [e findAttendeeWithEmail: baseEmail];
if (!p)
p = [e findAttendeeWithEmail:[self replySenderEMail]];
// We haven't found it yet, let's look in the identities
// associated to this user
if (!p)
{
p = [e findAttendeeWithEmail: [self replySenderBaseEMail]];
if (!p)
p = [e findAttendeeWithEmail:[self replySenderEMail]];
SOGoUserManager *sm;
NSString *uid;
sm = [SOGoUserManager sharedUserManager];
uid = [sm getUIDForEmail: baseEmail];
if (uid)
{
NSArray *allEmails;
NSString *email;
SOGoUser *u;
int i;
u = [SOGoUser userWithLogin: uid];
allEmails = [u allEmails];
for (i = 0; i < [allEmails count]; i++)
{
email = [allEmails objectAtIndex: i];
if ([email caseInsensitiveCompare: baseEmail] == NSOrderedSame)
continue;
p = [e findAttendeeWithEmail: email];
if (p)
break;
}
}
}
return p;
}
- (iCalPerson *) storedReplyAttendee
{
iCalEvent *e;
e = [self storedEvent];
if (e)
return [self _attendeeFromMailSenderInEvent: e];
return nil;
}
- (BOOL) isReplySenderAnAttendee
{
return ([self storedReplyAttendee] != nil);
@@ -502,15 +539,7 @@
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
return [event findAttendeeWithEmail: emailFrom];
return [self _attendeeFromMailSenderInEvent: event];
}
- (BOOL) hasSenderStatusChanged
Loaded 100 of 304 files, more files were not shown because too many files have changed in this diff. Show more