Compare commits

...
352 Commits
Author SHA1 Message Date
Francis Lachapelle 30b08941f6 Add Lithuanian translation 2016-03-16 11:34:11 -04:00
Francis Lachapelle e5b42c5e98 Update ChangeLog 2016-03-16 10:59:31 -04:00
Francis Lachapelle ed5d922ea4 Preparation for release 2.3.9 2016-03-16 10:59:15 -04:00
Francis Lachapelle 82b919e7ee Update translations
Added Lithuanian
2016-03-16 10:50:01 -04:00
Ludovic Marcotte 821b7c19ce Updated NEWS file. 2016-03-16 08:59:05 -04:00
Ludovic Marcotte bed5175a74 (fix) properly split long headers (fixes #3152) 2016-03-16 08:55:21 -04:00
Francis Lachapelle 188328d5d7 (js) Prefer RefreshViewCheck to MailMessageCheck
Fixes #3465
2016-03-15 16:00:13 -04:00
Ludovic Marcotte 17afe7c077 (fix) avoid escaping values that are quoted (fixes #3296) 2016-03-15 10:38:38 -04:00
Ludovic Marcotte bfb7fa2e14 (fix) don't skip last identity 2016-03-14 13:18:24 -04:00
Ludovic Marcotte e8ff3dc539 (fix) avoid compilation warning from previous commit 2016-03-13 13:47:03 -04:00
Ludovic Marcotte 20b1d3c0e9 Use GMT tz when setting birthday (fixes #3579) 2016-03-13 13:46:53 -04:00
RaphWeyman 3a66e54bd0 Update SOGoActiveSyncDispatcher.m
Add local memory pool so as to restrict memory consumption while waiting in the ping cycle.
Consumption can otherwise grow quite large while if the SOGoMaximumPingInterval and SOGoMaximumSyncInterval have been increased to allow for push ActiveSync.
2016-03-13 09:01:52 -04:00
Ludovic Marcotte d1c3327d47 (fix) avoid truncating the MIME content when fetching the whole message 2016-03-09 08:49:17 -05:00
Ludovic Marcotte 100841efbd (fix) remove unnecessary error message 2016-03-08 08:24:51 -05:00
Francis Lachapelle 8617ddc05e Review order of ProxyPass directives
Fixes #3461
2016-02-23 11:56:28 -05:00
Ludovic Marcotte 8e94c33a0f (fix) avoid truncating XML entities 2016-02-19 09:08:24 -05:00
Ludovic Marcotte 32bb1456e2 (feat) attachment size upload using WOMaxUploadSize (fixes #3510 and #3135) 2016-02-16 16:28:11 -05:00
Francis Lachapelle d01f5d36fa (css) Fix overlap of elements in preferences 2016-02-16 14:31:34 -05:00
Ludovic Marcotte 6de15edb9c (fix) don't mark content as truncated if it's not 2016-02-16 08:32:40 -05:00
Ludovic Marcotte a2078da5f0 Updated NEWS file regarding cherry-picked commits 2016-02-15 17:39:42 -05:00
Ludovic Marcotte 2193dbb678 Make sure we don't go overbounds 2016-02-15 17:39:15 -05:00
Ludovic Marcotte cb9f26cc1f (fix) support EAS MIMETruncation 2016-02-15 17:39:01 -05:00
Ludovic Marcotte e4473a656b (fix) make sure our heartbeat interval is always greater than the internal one 2016-02-12 13:08:17 -05:00
Ludovic Marcotte 7cbf019742 Updated NEWS file regarding previous commit 2016-02-11 15:07:01 -05:00
Ludovic Marcotte 456ff7d5a3 (fix) avoid EAS bday offset (fixes #3518) + small fix in previous commit 2016-02-11 15:05:26 -05:00
Ludovic Marcotte 5f0061205f Updated NEWS file regarding previous commit 2016-02-11 14:35:50 -05:00
Ludovic Marcotte 11a2bcfffd (fix) EAS issue with attachments in 2nd-level mailboxes (fixes #3505) 2016-02-11 14:35:01 -05:00
Ludovic Marcotte 0b1fbf0dee Updated NEWS file regarding previous commit. 2016-02-10 15:05:47 -05:00
Ludovic Marcotte 499dbf68ae (fix) allow resources to prevent invitations (fixes #3410) 2016-02-10 15:05:00 -05:00
Francis Lachapelle 4c6c0a3644 Update ChangeLog 2016-02-05 12:16:34 -05:00
Francis Lachapelle 1c65424242 Preparation for release 2.3.8 2016-02-05 12:16:13 -05:00
Francis Lachapelle 08bf6d528b Update translations 2016-02-05 12:14:48 -05:00
Francis Lachapelle a9083004ef Update CKEditor to version 4.5.7 2016-02-05 12:11:36 -05:00
Ludovic Marcotte ec02c41d9b Updated NEWS file 2016-02-05 10:09:47 -05:00
Ludovic Marcotte 07e3c48ed0 (fix) added local memory pool 2016-02-05 10:04:25 -05:00
Ludovic Marcotte 920bfba8b8 (fix) add shared/public namespaces in the list or returned folders 2016-02-05 10:02:05 -05:00
Ludovic Marcotte bc2e46cda7 Updated NEWS file regarding PR#196 2016-02-04 10:41:21 -05:00
Ludovic Marcotte e9d24fe0e6 (fix) correctly encode filename of attachments over EAS (#3491) 2016-02-04 10:37:44 -05:00
Francis Lachapelle 24d32ae220 Merge pull request #196 from gilcn/SOGo-issue-0003321
(fix) Folders with square brackets error

Fixes #3321
2016-02-04 10:32:42 -05:00
gilcn 8baf8731b7 (fix) Bug #3321: Folders with square brackets error
http://sogo.nu/bugs/view.php?id=3321
2016-02-04 11:34:07 +01:00
Francis Lachapelle b9cdc437eb Update translations 2016-01-27 13:13:14 -05:00
Francis Lachapelle a451a1120b Preparation for release 2.3.7a 2016-01-26 11:41:59 -05:00
Francis Lachapelle 6cfed4eb67 Update translations 2016-01-26 11:14:53 -05:00
Francis Lachapelle a55c98b98d Update French translation 2016-01-26 09:19:13 -05:00
Ludovic Marcotte 7c0649efcf (fix) small i18n fixes 2016-01-26 09:07:26 -05:00
Ludovic Marcotte 0ca6512617 (fix) added missing icon + localization 2016-01-26 08:16:19 -05:00
Francis Lachapelle 3ee29c0b71 Update ChangeLog 2016-01-25 11:19:20 -05:00
Francis Lachapelle 7b4c6346f8 Preparation for release 2.3.7 2016-01-25 11:19:06 -05:00
Ludovic Marcotte d5fd719212 (doc) missing SOGoMailKeepDraftsAfterSend documentation 2016-01-25 11:08:33 -05:00
Francis Lachapelle be7971ceef Add Croatian translation 2016-01-25 10:51:09 -05:00
Ludovic Marcotte 3f0c2b647c (fix) do not try to decode non-wbxml responses for debug output (#3444) 2016-01-25 10:25:54 -05:00
Ludovic Marcotte 155acfa9a6 (feat) newly created folders using EAS are always sync'ed by default (#3454) 2016-01-25 10:21:23 -05:00
extrafu 57901b8469 Merge pull request #187 from ethoms/patch-1
fix (revised) for issue with CardDAV sync with the DAVDroid sync client.
2016-01-25 09:19:16 -05:00
ethoms 5ba476789c fix (revised) for issue with CardDAV sync with the DAVDroid sync client.
This is a revised fix for the issue raiased in sogo bug tracker 3370 and 3373. It supercedes the fix in commit  2c723070c6 .

The fix was noted in NEWS with the comment "we now return all cards when we receive an empty addressbook-query REPORT". However it did not work for me and at least two others, as can be seen in the commit comments. In summary, only contacts with email addresses were synced. The suggested change from kwirk fixes the regular address book sync, but it completely breaks syncing of the read-only Group Directory (Corporate Directory). My suggested changes work in full (as far as I'm able to test).

I have done some fairly extensive testing of CardDAV sync (with DAVDroid only) and it seems to work 100% now. In addition to the obvious tests, I have tested with contacts that only have one field of data entered. The feilds I've tested (with all other fields empty) are as follows:

First name
Last name
Display name
email address
Work (telephone)
Home (telephone)
Fax (telephone)
Mobile (telephone)

Additionally, I tested syncing of a contact with only the Work Address fully populated. In the webmail, since the name fields are all missing, the "Organization" field of the Work Address takes the place of the name field in the 'Name' column. This does get synced to my phone and it also appears my Android contact list with 'Name' set to the 'Organization' field data. The address, organization and website fields being in tact also.

In addition, I tested a Group Directory (Corporate Directory) [SOGoUserSources->isAddressBook] sync. It seems contacts without email addresses do not sync. This seems to be the behaviour across the board with a "." search filter. This happens despite the filter in SOGoUserSources including ldap entries without a mail attribute. Nothing I can do to patch this in SOGoFolder+CardDAV.m, that would have to be fixed in the code that deals with the special "." search filter (I guess).

I think the contact search system needs some looking into, particularly the "." search filter behaviour. There is another bug related to contact search in the webmail address book view. I will make a bug report on that soon. It's a shame there isn't an "all" search filter, it would seem it would make various parts of SOGo easier to get the right behaviour.
2016-01-24 03:43:24 +08:00
Ludovic Marcotte 49d5a72d65 (fix) rewrote the string sanitization to be 32-bit Unicode safe 2016-01-22 20:30:40 -05:00
Ludovic Marcotte f34aa2996d (fix) more fix regarding recurrence exceptions and body tags 2016-01-22 14:02:05 -05:00
Ludovic Marcotte 5686e086cd (fix) now always generate invitation updates when using EAS 2016-01-22 13:35:02 -05:00
Ludovic Marcotte ed9ec04669 (fix) make sure we escape control chars 2016-01-22 12:37:00 -05:00
Ludovic Marcotte 737d2ffccc (feat) UI for previously commited new feature (junk/not junk) 2016-01-22 10:32:13 -05:00
Ludovic Marcotte 7ca66b4078 (feat) new junk/not junk capability with generic SMTP integration 2016-01-22 10:30:27 -05:00
Francis Lachapelle 04c68e0674 Localization 2016-01-21 10:10:13 -05:00
Francis Lachapelle bf7f5e8be7 Localization 2016-01-20 15:09:22 -05:00
Francis Lachapelle f1d0f770df Revert "Request all contacts when no filter"
This reverts commit 1c8b693656.
2016-01-20 13:59:13 -05:00
Francis Lachapelle 596c22d828 Update ChangeLog 2016-01-18 14:17:36 -05:00
Ludovic Marcotte e223d064d9 (fix) disabled test for now since it fails on squeeze 2016-01-18 14:13:25 -05:00
Ludovic Marcotte dbf6426449 (fix) don't use false, but use NO 2016-01-18 14:05:30 -05:00
Francis Lachapelle 7fc6eec157 Update ChangeLog 2016-01-18 11:24:23 -05:00
Ludovic Marcotte f48b3405d7 (fix) old case with gcc 2016-01-18 11:14:25 -05:00
Ludovic Marcotte 52bc589ab8 (fix) re-enabled test 2016-01-18 10:50:48 -05:00
Ludovic Marcotte 03f879433b (fix) let the test compile on older gcc 2016-01-18 10:23:19 -05:00
Francis Lachapelle ad141295ed Update ChangeLog 2016-01-18 09:55:36 -05:00
Francis Lachapelle 5fef815191 Preparation for release 2.3.6 2016-01-18 09:55:21 -05:00
Francis Lachapelle ec28fdc8d0 Update ChangeLog 2016-01-18 09:51:58 -05:00
Francis Lachapelle b1590f6b2e Update translations 2016-01-18 09:51:22 -05:00
Ludovic Marcotte 800d143a35 Update NEWS file regarding PR#184 2016-01-18 09:20:41 -05:00
extrafu a503b34d7a Merge pull request #184 from zentyal/master
Fixes from Zentyal since 2015/12/30
2016-01-18 09:10:35 -05:00
Jesús García Sáez 65670027cb Merge pull request #231 from zentyal/ejhernandez/calendar-perm
Calendar folder permission roles
2016-01-18 13:49:39 +01:00
Ludovic Marcotte d5bf661cf1 (fix) avoid generating empty ConversationId which can break Android EAS 2016-01-17 06:30:02 -05:00
Ludovic Marcotte dc2878b5e2 (fix) don't generate dual Body tag for event exceptions 2016-01-16 08:33:55 -05:00
Ludovic Marcotte f9aab0de75 (fix) correctly remove the event/task 2016-01-14 14:50:58 -05:00
Francis Lachapelle 5c58299458 Update Russian translation 2016-01-14 09:10:56 -05:00
Enrique J. Hernández Blasco 57fad470fb oc-calendar: Detail limitations on setting permissions
As MAPI permission flags are much more detailed that SOGo ones.
2016-01-13 17:52:49 +01:00
Enrique J. Hernández Blasco e0b7efbbbf oc-calendar: Set simple and Detailed Free/Busy permission level
By restricting when FolderVisible right is set to the following rights:

* ReadAny
* EditAny
* DeleteAny

This goes beyond what specs says about when FolderVisible right is
mandatory but there is no more information stored in SOGo
(no possibility to set FolderVisible specific right)

See [MS-OXCPERM] Section 2.2.6 for more details
2016-01-13 17:52:48 +01:00
Enrique J. Hernández Blasco 4278e417a7 oc-calendar: Enable editor role
When Outlook sets the editor role, the FreeBusyDetailed and FreeBusySimple
right flags are set as Full Details are available.

When we store this information in SOGo, the SOGoCalendarRole_PublicModifier,
SOGoCalendarRole_PrivateModifier and SOGoCalendarRole_ConfidentialModifier
are stored as well as the free busy related roles, but as
[SOGoAppointmentFolder aclsForUser:forObjectAtPath] only returns the highest
access rights, we have to set as well the FreeBusy flags.

More details on [MS-OXCPERM] Section 2.2.6
2016-01-13 17:52:48 +01:00
Ludovic Marcotte 230688bf4e (fix) adjusted JS files regarding translation updates 2016-01-13 11:29:41 -05:00
Francis Lachapelle 1a1f74b5d0 (fix) Remove non-localized languages names 2016-01-13 10:52:36 -05:00
Francis Lachapelle 93d1c3d4e7 verify_translations.py: fix syntax error 2016-01-13 10:49:29 -05:00
Francis Lachapelle ea37a9cd29 verify_translations.py: handle JavaScript files 2016-01-13 10:48:17 -05:00
Francis Lachapelle 8e8e19666a Localization 2016-01-13 09:45:03 -05:00
Francis Lachapelle 76b4563b2f verify_translations.py: handle toolbars 2016-01-13 09:41:03 -05:00
Francis Lachapelle 4ce3947e60 Fix localizable strings (templates, JavaScript) 2016-01-13 09:26:21 -05:00
Francis Lachapelle 0a8f52ce7d Update Russian translation 2016-01-13 09:17:20 -05:00
Enrique J. Hernández 9bb2473c8e Merge pull request #233 from zentyal/jgarcia/delete-attach-after-fail
Do not include failure attachments (really long filenames)
2016-01-13 12:08:58 +01:00
Jesús García Sáez 2d5995a8f4 Merge pull request #213 from zentyal/jamor/unittest-merge
Add RTFHandler unittest
2016-01-12 18:35:43 +01:00
Jesús García Sáez cee83df085 Merge pull request #232 from zentyal/ejhernandez/junit-output
Give support to JUnit output format for sogo-tests
2016-01-12 18:29:49 +01:00
Enrique J. Hernández Blasco 0c5f4edb36 Give support to JUnit output format for sogo-tests
To integrate with CI system.

sogo-tests now accepts a flag (-f) to determine the output format:

  * text : Current behaviour and default value
  * junit: XML output suitable for CI system such as Jenkins
2016-01-12 16:14:30 +01:00
Jesús García Sáez 90378b76eb draft: remove attachment on failure
Attachment creation can succeed and attachment mime file could fail

This can happen, e.g., when the filename's length is close to the maximum
allowed but your filesystem and then mime file will exceed that limit
(because it has a prefix).
2016-01-12 16:10:39 +01:00
Enrique J. Hernández 6aeb13ba65 Merge pull request #227 from zentyal/jgarcia/qp-encode-subjects
Fix encoding of subjects of emails with non-ascii characters
2016-01-12 16:04:42 +01:00
Francis Lachapelle 216371fd80 Russian localization
Fixes #3383
2016-01-11 09:47:27 -05:00
Jesús García Sáez 5927d3303c Merge pull request #230 from zentyal/ejhernandez/debug-rc
oc: Print retval on high debug for library interface
2016-01-11 09:33:29 +01:00
Ludovic Marcotte 6a3e64f849 (fix) when deleting an event using EAS, properly invoke the auto-scheduling code 2016-01-08 13:12:00 -05:00
Ludovic Marcotte 490043b2e1 (fix) we now follow 301 redirects when fetching ICS calendars 2016-01-08 12:02:33 -05:00
Ludovic Marcotte 210d6908b8 (fix) Fixed DomainLessLogin logins in multi-domain 2016-01-08 11:39:19 -05:00
Ludovic Marcotte ca7f093155 (fix) properly escape the UID too 2016-01-07 11:12:33 -05:00
Enrique J. Hernández 2ad00f8cef Merge pull request #229 from zentyal/jvalles/missing-appointment-notifications
Add the domain in the `uidInDomain` method
2016-01-07 16:42:03 +01:00
Ludovic Marcotte b5780357cb (fix) updated NEWS file 2016-01-07 10:00:45 -05:00
Ludovic Marcotte 67f38e1127 (fix) don't escape twice (#3423) 2016-01-07 09:58:44 -05:00
Ludovic Marcotte 36985a19e9 (feat) UI for previous commit 2016-01-07 09:17:46 -05:00
Ludovic Marcotte 06cbe527e8 (feat) Now able to sync only default mail folders when using EAS 2016-01-07 09:17:35 -05:00
Francis Lachapelle 51dbfaba15 Improve verification of SOGoLoginDomains array 2016-01-06 09:52:15 -05:00
Francis Lachapelle 3d55396661 Update ChangeLog 2016-01-05 14:33:12 -05:00
Francis Lachapelle 2c97b680a8 Preparation for release 2.3.5 2016-01-05 14:33:01 -05:00
Francis Lachapelle f9cb896f92 Update Russian translation
Fixes #3383
2016-01-05 14:08:05 -05:00
Francis Lachapelle 46edcce1d5 Don't try to translate "SOGo" 2016-01-05 14:01:27 -05:00
Francis Lachapelle 1b14fee96e Localization 2016-01-05 13:56:40 -05:00
Francis Lachapelle 0777e9fe67 Fix Danish localization
Fixes #3422
2016-01-05 13:44:20 -05:00
Enrique J. Hernández Blasco 8b078817e2 oc: Print retval on high debug for library interface
When exiting a library function
2016-01-04 09:38:10 +01:00
Ludovic Marcotte dd56d5e85c Updated NEWS file regarding the merge of PR #180 2015-12-30 09:08:38 -05:00
Ludovic Marcotte e009af6864 Merge branch 'v2' of https://github.com/inverse-inc/sogo into v2 2015-12-30 09:06:13 -05:00
extrafu 673db179b1 Merge pull request #180 from zentyal/master
Fixes from Zentyal since 2015/10/29
2015-12-30 09:06:15 -05:00
Ludovic Marcotte fd0f1499b1 Merge branch 'v2' of https://github.com/inverse-inc/sogo into v2 2015-12-30 08:57:08 -05:00
Jesús García Sáez 20575226b8 Merge pull request #221 from zentyal/jvalles/time-shifts-in-events
oc-calendar: Use the time zone provided by the client
2015-12-30 11:24:39 +01:00
Enrique J. Hernández 8b04a468c2 Merge pull request #223 from inverse-inc/v2
SOGo v2.3.4
2015-12-29 22:41:02 +01:00
Juan Vallés ccd9e385a0 Add the domain in the uidInDomain method
This method is used to get the login and we weren't returning
the domain, which led to problems when creating appointments
on multidomain environments like, for instance, not sending
the invitation mails.
2015-12-29 12:19:07 +01:00
Juan Vallés 376e717f45 oc-calendar: Use the calendar's time zone if it is present
If the event was created by the MAPI client, the client's time zone (if
present) is assigned to the event's calendar in iCalEvent+MAPIStore.
This way, we can use it to deliver the event's properties correctly.
2015-12-23 17:27:10 +01:00
Jesús García Sáez 5b25f85c02 Merge pull request #228 from zentyal/ejhernandez/fix-editor-perm
oc: Set Editor as role is now possible in Outlook
2015-12-23 15:54:56 +01:00
Jesús García Sáez 63b8c3ed5e Merge pull request #226 from zentyal/ejhernandez/return-rc-message-submit
oc-mail: Return error when delivery was not successful
2015-12-23 15:43:54 +01:00
Jesús García Sáez c76c48fb0c Merge pull request #225 from zentyal/ejhernandez/sogo-oc-samba-4.1
oc: Do compile with Samba 4.1
2015-12-23 15:37:03 +01:00
Enrique J. Hernández Blasco 7fe1b5f046 oc: Do compile with Samba 4.1
As requested by @extrafu to maintain sogo packages for a little while
2015-12-23 15:34:17 +01:00
Jesús García Sáez 2a25675b3a Merge pull request #220 from zentyal/ejhernandez/improve-dbmsgreader
oc: Dump property canonical name in dbmsgreader
2015-12-23 15:05:37 +01:00
Enrique J. Hernández Blasco dbfd86db04 oc: Set Editor as role is now possible in Outlook
According to [MS-OXCPERM] Section 2.2.7 in PidTagMemberRights possible
values, once we set the DeleteAny flag, the DeleteOwned flag must be set.
Likewise EditOwned must be set when EditAny is set. In this way,
the rights sent by the MAPI client are equal to the returned by the
server when Editor is set.

In real world practice, makes more strict Outlook 2013 work with editor permissions
the sharing of user's defined calendars, tasks or contacts folders as
the recipients can be editors of that folder.
2015-12-23 00:27:09 +01:00
Francis Lachapelle 135535e00e Update NEWS file 2015-12-22 14:25:00 -05:00
Francis Lachapelle e386404b0a Properly compute the last week number for the year
Fixes #1010
2015-12-22 14:21:37 -05:00
Jesús García Sáez 966d13c5b6 Merge pull request #219 from zentyal/ejhernandez/mult-cn-restriction
Manage multiple Change Number restrictions
2015-12-22 17:14:53 +01:00
Juan Vallés 4ae5feb131 oc-calendar: Extract time zone from TimeZoneDefinition
All-day and recurrent events have a binary property that describes the
time zone they take place in. We were using the user's time zone in
the webmail, but it may not be equal to the one in the client. This
difference eventually leads to time shifts in events.
2015-12-22 16:05:23 +01:00
Jesús García Sáez db213c2b73 unittest: just reformat and indentation
no real changes
2015-12-22 11:48:35 +01:00
Jesús García Sáez c2922e2a26 unittest: QP encode function from NGMimeHeaderFieldGenerator 2015-12-22 11:48:34 +01:00
Jesús García Sáez 1529e7ec4e unittest: compare properly decimal numbers 2015-12-22 11:48:34 +01:00
Juan Vallés 332508e2db oc-calendar: Use signed integer for time zone biases
This change adapts the bias fields in the TimeZoneStruct and TZRule
structures to the changes in openchange that allow this offsets to be
negative (zentyal/openchange bba372faea29d942b9471e6bed90bf425dc4b231)
2015-12-22 11:40:59 +01:00
Juan Vallés d2ea6fef2e oc-calendar: Initialise NSCalendarDate with a SYSTEMTIME struct
The method computes the date of a SYSTEMTIME structure, in which the day
within the month is given by the Nth occurrence of a weekday (see
[MS-OXOCAL] 2.2.1.39).
2015-12-22 11:40:59 +01:00
Jesús García Sáez 75ff4af7cc Use new function from sope to qp-encode the subject 2015-12-21 17:09:11 +01:00
Enrique J. Hernández Blasco 916c04387b oc-mail: Return error when delivery was not successful
For example, if the SMTP is down, then the message is not sent and
an error is returned. We returned back this error code to be managed
by upper layer.
2015-12-18 12:23:49 +01:00
Ludovic Marcotte 7c460b694e Enable new code. 2015-12-16 08:32:20 -05:00
Ludovic Marcotte 29f2d960a7 Enable old code. 2015-12-15 16:15:48 -05:00
Ludovic Marcotte 225fe847a7 Disabled today's code for now. It requires more testing. 2015-12-15 16:12:17 -05:00
Ludovic Marcotte 720b1d5ab5 (fix) carry over delegatedFrom too 2015-12-15 15:21:33 -05:00
Ludovic Marcotte 28bd4ca318 (fix) carry over the delegatedTo, not only the part stat 2015-12-15 15:05:40 -05:00
Francis Lachapelle c6dbcb2f7c Update ChangeLog 2015-12-15 14:24:25 -05:00
Francis Lachapelle 5dfbfe827d Preparation for release 2.3.4 2015-12-15 14:24:12 -05:00
Francis Lachapelle 6ddb40fd2b Update CKEditor to version 4.5.6 2015-12-15 14:20:57 -05:00
Francis Lachapelle 37f29c06ea Localization 2015-12-15 13:18:06 -05:00
Ludovic Marcotte 248892c394 (fix) small fix over previous commit 2015-12-15 11:14:58 -05:00
Ludovic Marcotte b4e4b9c29f (fix) avoid duplicating attendees when accepting event using a different identity over CalDAV 2015-12-15 11:08:59 -05:00
Francis Lachapelle e94b4416f8 Localization cleanup 2015-12-15 10:05:21 -05:00
Francis Lachapelle 8d19ec4aff Remove duplicate key in MainUI localization 2015-12-15 09:55:51 -05:00
Enrique J. Hernández aeeeeea802 Merge pull request #216 from zentyal/jag/fix-i18n-attachment-names
Priorize filename attribute first to get attachment name
2015-12-15 11:44:26 +01:00
Javier Amor García 41e388c5f6 sogo-mail: Priorize filename attribute to get attachment name
Some clients (such apple mail) use only the filename
attribute in the content/disposition header and the
name attribute from the content/type header is filled with
uninternationalized characters.

Example:

Content-Type: application/msword;
x-apple-part-url=C4977556-0D01-4C6C-8A51-451E0AADE431;
name=_______.doc
Content-Disposition: attachment;
filename*=utf-8''%D0%A0%D0%95%D0%9A%D0%92%D0%98%D0%97%D0%98.doc

This changeset gives priority to the filename attribute.
2015-12-15 08:01:21 +01:00
Enrique J. Hernández Blasco db17872dd6 oc: Dump property canonical name in dbmsgreader 2015-12-14 23:39:32 +01:00
Enrique J. Hernández 73e86bb8e5 Merge pull request #217 from zentyal/jgarcia/fix-contacts-listing
Request all contacts when there is no filter
2015-12-14 14:53:33 +01:00
Enrique J. Hernández c3bce410f1 Merge pull request #215 from zentyal/jgarcia/fix-contacts-outlook
Fix contacts on outlook
2015-12-14 11:06:37 +01:00
Ludovic Marcotte 980db7a980 (fix #127) fallback to "work" when export as ldif 2015-12-11 14:51:08 -05:00
Ludovic Marcotte c5c6ff33d8 (fix #119) disable servername and url by default 2015-12-11 14:11:26 -05:00
Ludovic Marcotte 7e1bbe34dc (fix #118) handle /.well-known/carddav 2015-12-11 14:03:50 -05:00
Enrique J. Hernández Blasco a618386d5e oc-db: Store version number instead of CN
To be able to search in this for every kind of operator.

As we cannot do a migration, we have to add a new key to the property
dictionary (@"version_number") which stores the version:

  version = exchange_globcnt(CN >> 16)

Instead of the CN structure which is stored in @"version" key.

This way we can do searches for CN to download only missing data from the
given state of the client for this kind of messages.
2015-12-11 11:38:21 +01:00
Enrique J. Hernández Blasco 5993fe97c3 oc-db: Implement description message to ease debugging
Which dumps the properties NSMutableDictionary
2015-12-11 11:37:02 +01:00
Enrique J. Hernández Blasco 7cdf48335b oc: Support every operator for CN restriction
No limitation is found in SQL queries. So use them all.
2015-12-11 11:35:55 +01:00
Enrique J. Hernández Blasco 3dff73636c oc: Use NSString index for version lookup
It was using NSNumber in versions Dictionary for GCSMessages but it is
stored as the NSString representation (0x390300000000001), so the lookup
has always failed.
2015-12-11 11:33:41 +01:00
Enrique J. Hernández Blasco 2fc21e48d8 oc: Use proper variable name for last modified value 2015-12-11 11:31:15 +01:00
Enrique J. Hernández Blasco 98ed9c3b17 oc-mail: Manage multiple CN restriction
By ignoring <, =< and = operators and simplifying the following
request:

    MODSEQ >= x || MODSEQ >= y || MODSEQ >= z  --> MODSEQ >= min(x, y, z)

This hack will reduce the number of current retrieved UID keys
from the IMAP server. Current status is to retrieve everything when
the multiple CN restriction is sent as the required restriction
is too complex and it is not defined by the IMAP spec.

The proper implementation for:

    CN > x_1 & CN < x_2 | CN > y_1 & CN < y_2 | CN > z_1

It will be something like this:

    set(MODSEQ >= x_1 + 1) - set(MODSEQ >= x_2)
    U
    set(MODSEQ >= y_1 + 1) - set(MODSEQ >= y_2)
    U
    set(MODSEQ >= z_1)

Assuming x_1 <= x_2 <= y_1 <= y_2 <= z_1.
2015-12-11 11:22:26 +01:00
Ludovic Marcotte f9884e3f55 (fix) added new strings from v3 2015-12-10 14:12:33 -05:00
Enrique J. Hernández Blasco 10cabca57e oc: Use ReplicaID from connection info
Instead of hardcoding it to 0x0001.
2015-12-10 16:36:22 +01:00
Jesús García Sáez 2e1ed274c8 unittest: fix TestNGMailAddressParser
It had wrong expectation strings
2015-12-09 20:02:57 +01:00
Jesús García Sáez e4cd451be8 unittest: removed TestNGMimeAddressHeaderFieldGenerator
Needs redo
2015-12-09 20:02:56 +01:00
Javier Amor García 603da75818 Added test for spanish characters 2015-12-09 20:02:56 +01:00
Javier Amor García 9ed3812795 Added test for character escape 2015-12-09 20:02:55 +01:00
Javier Amor García a0b15d0c8c Test for [RTFHandler parseFontTable]
This changeset includes changes in relevant classes to make
them easier to test.
2015-12-09 20:02:55 +01:00
Javier Amor García 7e4571874e Added test for RTF with russian characters
Changed and refactored TestRTFHandler helper methods
2015-12-09 20:02:54 +01:00
Javier Amor García b076801e1b Added test fixture for RTF crash 6977 2015-12-09 20:02:54 +01:00
Jesús García Sáez 0316e441fc unittest: RTF without color table definition 2015-12-09 20:02:53 +01:00
Jesús García Sáez 75208428dd unittest: New unittest with a rtf file
This currently sigfault due to bad parsing a \*\shppict tag
with a \binXXXX tag inside it
2015-12-09 20:02:53 +01:00
Jesús García Sáez 942f48c9a0 unittest: RTF with \pict 2015-12-09 20:02:52 +01:00
Jesús García Sáez 24a7515bb5 unittest: file as NSData and try different encodings 2015-12-09 20:02:51 +01:00
Jesús García Sáez a8e5a3c025 unittest: NSString objectFromJSONString for single values 2015-12-09 20:02:51 +01:00
Jesús García Sáez 0b69727a49 unittest: add rtf test 2015-12-09 20:02:50 +01:00
Jesús García Sáez 2a2d141629 unittest: rtf, test crash core dump 2015-12-09 20:02:48 +01:00
Jesús García Sáez b98d112738 unittest: commented failing test 2015-12-09 20:02:28 +01:00
Jesús García Sáez 1c8b693656 Request all contacts when there is no filter 2015-12-09 13:07:30 +01:00
Francis Lachapelle 15ab0f851d Localization 2015-12-08 10:05:19 -05:00
Ludovic Marcotte 1f98d7cced (fix) fixed broken translations 2015-12-03 15:40:38 -05:00
Ludovic Marcotte 2755a83e65 (fix) remove trailing semicolon everywhere 2015-12-03 15:02:01 -05:00
Francis Lachapelle 9936477f07 Update translations 2015-12-03 13:09:24 -05:00
Enrique J. Hernández 2e1bfb25d5 Merge pull request #210 from zentyal/cperez/ldapsource-refactor
Unify user sources API
2015-12-03 17:15:20 +01:00
Jesús García Sáez 25dbce42cd Reorganize methods according to MS-OXOCNTC
Nothing has been change but this way is much more readable
2015-12-03 16:58:58 +01:00
Jesús García Sáez a9fe80c367 oc-contacts: don't return empty values
If the property has an empty string, return not found
2015-12-03 16:58:58 +01:00
Jesús García Sáez bc5aa3cacb Whitespaces and indentation
No real changes
2015-12-03 16:58:58 +01:00
Jesús García Sáez 0ddbd79fad Remove commented code
These methods don't make any sense here
2015-12-03 16:58:57 +01:00
Jesús García Sáez 2a3367a13b oc-contacts: returning properly personal contacts
We needed to return AddressBookProviderEmailList,
AddressBookProviderArrayType and Email1OriginalEntryId to
make it compatible with the standard.

Besides a refactorization to return better EmailNFoobar properties
2015-12-03 16:58:57 +01:00
Carlos Pérez-Aradros Herce 4b7cbba0c8 Merge pull request #214 from zentyal/ejhernandez/no-crash-after-reconnect
oc-mail: Do not crash after reconnect sending mails
2015-12-03 14:27:07 +01:00
Enrique J. Hernández Blasco 2deda01e5c oc-mail: Do not crash after reconnect sending mails
After the providing the workaround on rebuilding the LDAP connection,
the sam_ctx variable can be freed and it was used as memory context
to store the AddressBookEntryId or OneOffEntryId when resolving
recipients. After this changeset, a local memory context which I
think is more sane.
2015-12-02 16:06:31 +01:00
Ludovic Marcotte 8c0ef9cf50 (fix) proper fix for #3389 2015-12-01 11:42:02 -05:00
Ludovic Marcotte 8456d49686 Revert "(fix) fixed addressbrook-only source entires having a c_uid set"
This reverts commit ef7de7c10d.
2015-12-01 11:39:07 -05:00
Ludovic Marcotte 5a3cafbbb0 (fix) prevent characters in calendar component UID causing issues during import process 2015-12-01 09:26:52 -05:00
Ludovic Marcotte dc085789e7 (feat) Initial support for EAS calendar exceptions 2015-11-30 09:19:01 -05:00
Enrique J. Hernández f6973d8aa4 Merge pull request #209 from zentyal/jag/rtf-parser-fix-unicode
RTFHandler fixes with focus in unicode and characters set
2015-11-30 12:20:04 +01:00
Javier Amor García 94d4da6e1e Reimplemented [RTFHandler parse] to have an unambiguous decoding of control words
[RTFHandler parseFontTable] reimplemented on the same line.
2015-11-27 17:49:01 +01:00
Javier Amor García 2f63542e07 Fixed problem with ascii chars that had different unicode value
Also cleaned a bit the parse loop
2015-11-27 17:49:01 +01:00
Javier Amor García 3155bd0172 Fixed bug which created unnecesary font tags 2015-11-27 17:34:08 +01:00
Javier Amor García 2518b37e32 Fixed RTF support for unicode characters greater than 32767 2015-11-27 17:34:08 +01:00
Javier Amor García 8e3e4a5445 Added RTF escapes parsing 2015-11-27 17:34:07 +01:00
Javier Amor García 01dcf3d6c8 Fixed activation of characters sets in [RTFHandler parse] 2015-11-27 17:34:07 +01:00
Javier Amor García fa4d3a0e20 Reimplementation of [RTFHandler parseFontTable] 2015-11-27 17:34:06 +01:00
Francis Lachapelle bbdb27e75e (css) Enlarge max width of toolbar buttons 2015-11-27 08:36:34 -05:00
Ludovic Marcotte 1279629e86 (fix) correctly handle all-day event exceptions when the master event changes 2015-11-25 15:53:08 -05:00
Ludovic Marcotte f7d748ea98 (fix) EAS fix on qp-encoded subjects (#3390) 2015-11-25 15:08:24 -05:00
Ludovic Marcotte 2d1a4b320c (fix) adjust doc and packaging regarding oc cleanup script 2015-11-25 10:56:39 -05:00
Jesús García Sáez e8e610c7b3 Merge pull request #212 from zentyal/safe-ldb-search
Adapt to new safe_ldb_search function
2015-11-25 12:12:28 +01:00
Ludovic Marcotte 8df311547f (fix) EAS fix for wrong charset being used (#3392) 2015-11-23 18:07:50 -05:00
Francis Lachapelle f42d6f5f4a Enlarge size of event/task editor window
Fixes #3381
2015-11-23 13:29:02 -05:00
Ludovic Marcotte 01642eca3e (fix) fixed one unit test 2015-11-23 13:20:55 -05:00
Carlos Pérez-Aradros Herce 9554e92c8b Adapt to new safe_ldb_search function 2015-11-23 17:01:35 +01:00
Francis Lachapelle 2ae8c6f8b2 Add missing tooltips on buttons of toolbars 2015-11-23 10:33:03 -05:00
Enrique J. Hernández f64b324133 Merge pull request #203 from inverse-inc/v2
Merge from upstream
2015-11-23 12:51:52 +01:00
Ludovic Marcotte 31deae11b4 (fix) domain in doc 2015-11-20 11:21:38 -05:00
extrafu 19676593ea (fix) allow getting password from context for OC 2015-11-19 17:57:04 -05:00
Francis Lachapelle 4a27beabdd (css) Limit toolbar buttons min/max width 2015-11-19 09:46:07 -05:00
Francis Lachapelle 8dae5faaa1 Localization 2015-11-19 09:41:36 -05:00
Francis Lachapelle 85ce41dc68 (fix) JS exception when printing calendars
Fixes #3203
2015-11-19 09:23:46 -05:00
Enrique J. Hernández 0622980f38 Merge pull request #205 from zentyal/jag/dont-sync-contact-group-from-sogo
Do not save IPM.DistList messages
2015-11-19 10:39:59 +01:00
Javier Amor García a73df96b78 [MAPIStoreGCSMessage getPidTagChangeKey:inMemCtx:] return on error
This method aborted when no PidTagChangeKey was found and the new flag was not
set. Now it returns MAPISTORE_ERR_NOT_FOUND.
2015-11-18 18:25:24 +01:00
Francis Lachapelle 7015713741 Update ChangeLog 2015-11-18 09:43:02 -05:00
Francis Lachapelle 7644d6fb4d Preparation for release 2.3.3a 2015-11-18 09:42:05 -05:00
Enrique J. Hernández 9dcf459827 Merge pull request #207 from zentyal/jag/idl-compile-warnings
sogo-openchange: Avoid compile warnings after changes in exchange.idl
2015-11-18 14:47:36 +01:00
Enrique J. Hernández 4c7bd6dd76 Merge pull request #208 from zentyal/jgarcia/wocontext-fun
User context activation when cached
2015-11-18 06:56:16 +01:00
Carlos Pérez-Aradros Herce ec598321c4 Add user defined user source 2015-11-16 17:43:52 +01:00
Francis Lachapelle 4b8a9bcc3b Always show user's name in top banner 2015-11-16 09:02:37 -05:00
Francis Lachapelle a9984d9487 Fix regression when saving mail folders state 2015-11-16 09:01:05 -05:00
Javier Amor García 88a74bcb5c Do not save IPM.DistList messages 2015-11-16 13:56:31 +01:00
Francis Lachapelle 2a90102a81 Update translations 2015-11-11 15:51:33 -05:00
Francis Lachapelle fbc73dc3bc Update ChangeLog 2015-11-11 14:06:11 -05:00
Francis Lachapelle 1d78b2df96 Preparation for release 2.3.3 2015-11-11 14:04:19 -05:00
Francis Lachapelle 6aef713d28 Update prototype.js to v1.7.3 2015-11-11 14:02:25 -05:00
Francis Lachapelle 71db3945cb Update CKEditor to version 4.5.4 2015-11-11 14:01:30 -05:00
Francis Lachapelle da230410be Handle ExpandedFolders settings like in v3
This implies a transition where ExpandedFolders can be either a string
or an array.
2015-11-11 13:31:58 -05:00
Francis Lachapelle 82114f75aa Fix Macedonian et Portuguese translations 2015-11-11 12:58:26 -05:00
Francis Lachapelle f8d31a6413 Update NEWS file 2015-11-11 12:04:19 -05:00
Francis Lachapelle 707aabde15 Update Slovenian translation 2015-11-11 12:04:04 -05:00
Francis Lachapelle ceec497aec Fix Portuguese translation 2015-11-11 11:50:32 -05:00
Francis Lachapelle 46f93d8629 Fix Macedonian translation 2015-11-11 11:50:06 -05:00
Francis Lachapelle f2347e13d7 Fix localizable strings in contact editor 2015-11-11 11:49:36 -05:00
Francis Lachapelle 106f8ec314 Improve Portuguese translation 2015-11-11 09:46:21 -05:00
Francis Lachapelle f58554a700 Add Macedonian translation 2015-11-11 09:46:01 -05:00
Francis Lachapelle 76b8de1805 Add Portuguese translation 2015-11-10 16:33:11 -05:00
Enrique J. Hernández 0751c51b89 Merge pull request #204 from zentyal/jvalles/event-invitation-subjects
oc-calendar: Make invitation/update mails have the event name as subject
2015-11-09 23:35:22 +01:00
Ludovic Marcotte 64456c10e5 (fix) additional fix for #3356 2015-11-09 14:30:58 -05:00
Jesús García Sáez 5f5b5d9273 oc: activate user context on initialization (for real)
Even with cached ones (completes cfab18e1b8)
2015-11-09 19:56:05 +01:00
Jesús García Sáez 194c415631 Log an error for SOGoObject without wocontext 2015-11-09 19:55:55 +01:00
Javier Amor García 08f05ac2ef sogo-openchange: Avoid compile warnings after changes in exchange.idl
The changes in the exchange.idl file from OpenChange has changed
some string pointers from 'const char *' to 'uint8_t *'.
This changeset cast them to avoid compilation warnings.
2015-11-09 19:08:51 +01:00
Jesús García Sáez af413f2b32 Merge pull request #202 from zentyal/ejhernandez/workaround-sync-configure-crash
Workaround sync configure crashes
2015-11-09 11:34:29 +01:00
Enrique J. Hernández Blasco 77bba8e5ea Use application context when initialising quick tables ACLs
As the stored context from initialisation may have changed
by `setContext` by other operations when acting as a OpenChange
library. This would make having a login set to nil and forcing
a NSException when it attempts to set a nil key at
[SOGoAppointmentFolder:roleForComponentsWithAccessClass:forUser]
inside [SOGoAppointmentFolder:initializeQuickTablesAclsInContext].
2015-11-09 11:30:03 +01:00
Juan Vallés 946665ff49 oc-calendar: Make invitation/update mails have the event name as subject
SOGo event notification mails add information to the event name in their
subject (e.g. _Event invitation: "foo"_). The client uses the mail
subject to create a copy of the event on the attendee's calendar, so we
need to strip out that extra information if we want the event to have
the proper name.
2015-11-09 10:28:46 +01:00
Ludovic Marcotte ef7de7c10d (fix) fixed addressbrook-only source entires having a c_uid set 2015-11-06 13:33:36 -05:00
Francis Lachapelle 106c6a24ae (js) Fix dialogs when partially visible
Fixes #2646, #3378
2015-11-06 09:42:09 -05:00
Ludovic Marcotte 863f182e84 (fix) fixed wrong comparison of meta vs. META tag in HTML mails 2015-11-06 09:25:49 -05:00
Francis Lachapelle ba37353e42 (fix) Position of popup menu
Fixes #3381
2015-11-06 09:15:23 -05:00
Francis Lachapelle 1915748e33 Bump version to 2.3.3 2015-11-06 09:15:23 -05:00
Ludovic Marcotte b170ae866f (fix) small fixes 2015-11-06 09:01:33 -05:00
Francis Lachapelle 7c69e37449 (fix) JavaScript syntax error in attendees editor 2015-11-06 08:45:42 -05:00
Ludovic Marcotte 7f26d47cd1 We drop Lucid. 2015-11-05 15:39:08 -05:00
Ludovic Marcotte b55ec52bfe (fix) handle emails having an attachment as their content 2015-11-05 15:21:13 -05:00
Ludovic Marcotte f919df050a (fix) one more fix for #3118 2015-11-05 14:58:58 -05:00
Ludovic Marcotte c2e4f7441d (fix) double declaration of same method 2015-11-05 13:57:51 -05:00
Ludovic Marcotte ca27464984 (fix) added type cast 2015-11-05 13:43:48 -05:00
extrafu 6cbc5573a1 Merge pull request #121 from ethoms/clang-warnings-stage1v2
Stage1b (v2 branch) of clang compiler warning patches.
2015-11-05 13:41:50 -05:00
Euan Thoms e4fae417b9 Stage 8 of clang compiler warning patches.
Update WORequest+SOGo.m
2015-11-05 13:30:40 -05:00
Carlos Pérez-Aradros Herce a6bbe13f9b Add missing header files 2015-11-05 19:11:19 +01:00
Jesús García Sáez 8cfb0f0d44 Avoid inserting second email for Contacts
When we have a contact with an email, avoid generating another one
with SOGoMailDomain value (normally we ended up with a contact with
two identical emails on 'emails' key and for multidomain source we
would had ended up with an email @localhost)
2015-11-05 19:11:19 +01:00
Jesús García Sáez 08f57f6ad5 SOGoUserManager: get domain after authentication
For multidomain source which has no domain defined, we get
the domain from the username which must have @domain as suffix
2015-11-05 19:11:19 +01:00
Jesús García Sáez 30a94161af SOGoUserManager.m: format and small refactor
Removed all tabs used for indentation
2015-11-05 19:11:19 +01:00
Jesús García Sáez ec533809aa return enableDomainBasedUID based only on the key 2015-11-05 19:11:19 +01:00
Jesús García Sáez 0d05e3579c defaultsForDomain return sharedSystemDefaults for unknown domains 2015-11-05 19:11:19 +01:00
Jesús García Sáez bf4a581d0f Added isDomainDefined
To check whether a domain is supported by our defined sources
2015-11-05 19:11:19 +01:00
Jesús García Sáez 9d3f3c619d LDAPSource: release displayName on dealloc 2015-11-05 19:11:19 +01:00
Jesús García Sáez 7d49674d9d Add domain to lookupContactEntry 2015-11-05 19:11:19 +01:00
Jesús García Sáez 48832a4878 Add domain to lookupGroupEntryByUID/ByEmail 2015-11-05 19:11:19 +01:00
Euan Thoms eda84c7b5d Stage1b (v2 branch) of clang compiler warning patches. 2015-11-06 01:29:26 +08:00
Ludovic Marcotte 9d31023724 (fix) additional fix for #3118 2015-11-05 11:11:18 -05:00
Ludovic Marcotte 41e80bb105 Updated NEWS file regarding PR#120 2015-11-05 09:48:17 -05:00
extrafu e428e74e6c Merge pull request #120 from zentyal/master
Fixes for OpenChange integration from Zentyal
2015-11-05 09:45:51 -05:00
Ludovic Marcotte 7a5e550510 (fix) manually added patch from PR#113 to avoid many "broken" commits 2015-11-05 09:36:11 -05:00
Euan Thoms 0605fdf02a Delete .orig files left over from applying patches to Stage 2. 2015-11-04 13:50:16 -05:00
Euan Thoms d3290dbb8d Stage 2 of clang compiler warning patches. 2015-11-04 13:50:05 -05:00
Ludovic Marcotte 34f626b84c (fix) small fixes around web calendars + sync 2015-11-04 09:21:42 -05:00
Euan Thoms e173c6d621 Stage 9 of clang compiler warning patches. 2015-11-04 08:42:03 -05:00
Euan Thoms 1425bb1c75 Stage 5 of clang compiler warning patches. 2015-11-04 08:41:49 -05:00
Euan Thoms 5b65eaa572 Stage 4 of clang compiler warning patches. 2015-11-04 08:41:36 -05:00
Euan Thoms 892bd693d7 Stage 3 of clang compiler warning patches.
Conflicts:

	UI/PreferencesUI/UIxJSONPreferences.m
2015-11-04 08:41:19 -05:00
Francis Lachapelle 6812cb08dc Display missing events when printing working hours 2015-11-03 09:00:19 -05:00
Francis Lachapelle b62167dd00 Fix Brazilian (Portuguese) translation
Fixes #3355
2015-11-03 08:27:50 -05:00
Enrique J. Hernández Blasco e0d49d2407 oc-mail: Check the number of uids fetched on synchroniseCache
Equals to the number of uids requested.

There are corner-cases when this is not happening, we don't know yet
why this is happening but IMAP server log should do the trick.

This avoids to launch a NSException trying to access an element
outside the array bounds.
2015-10-29 23:04:42 +01:00
Jesús García Sáez c8d05d41a5 Merge pull request #201 from zentyal/ejhernandez/sync-expunged-msgs-first-sync
oc-mail: Sync expunged messages on first cache sync
2015-10-26 19:37:01 +01:00
Ludovic Marcotte 1013c473cd (feat) support for ghosted elements on contacts over EAS 2015-10-26 10:25:11 -04:00
Ludovic Marcotte b8d77259cd (feat) we now save the supported elements in EAS collections 2015-10-26 10:15:35 -04:00
Ludovic Marcotte 40cb81fd0b (fix) prevent postal address showing on single line over EAS (#2614) 2015-10-26 10:08:00 -04:00
Jesús García Sáez 2937ba7f30 Merge pull request #200 from zentyal/ejhernandez/always-increase-cn-set-message-read-flag
oc-mail: Always increase the CN when setting the message read flag
2015-10-26 14:38:29 +01:00
Jesús García Sáez 3c6fb10d9f Merge pull request #199 from zentyal/jvalles/missing-calendar-metadata
oc-calendar: force cache synchronisation for a message before aborting
2015-10-26 14:30:38 +01:00
Ludovic Marcotte b378d258cb (fix) add unit test from PR #105 2015-10-23 15:54:33 -04:00
Ludovic Marcotte 3ba1e33eb6 (fix) perms fixes on scripts 2015-10-23 15:39:49 -04:00
Ludovic Marcotte 40a1d5fdbf (fix) inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271) 2015-10-23 14:34:19 -04:00
Ludovic Marcotte 1b44218a35 (fix) avoid crash when replying to a mail with no recipients (#3359) 2015-10-23 12:44:43 -04:00
Ludovic Marcotte df211af604 (fix) fixed br_pt translation + updated transifex (#3355) 2015-10-23 11:32:04 -04:00
Ludovic Marcotte 2c723070c6 (fix) we now return all cards when we receive an empty addressbook-query REPORT 2015-10-21 10:54:15 -04:00
Enrique J. Hernández Blasco 6f5bb3882c oc-mail: Sync expunged messages on first cache sync
There is a use case where this has caused crashes:

A message was hard-deleted using an IMAP client, this is the first
message you deleted in that folder and you have cleared offline
items in the client so a full sync is asked by upper layer.

In that situation, the SyncLastDeleteChangeNumber version property
is not set and return 0 in [getDeletedKeysFromChangeNumber:andCN:inTableType]
making OpenChange to crash while it is asking for deleted fmids
since a given change number.

This is a regression from 18d7070c4a.
2015-10-21 11:24:14 +02:00
Ludovic Marcotte fc9b175f25 (feat) now possible to choose which folders to sync over EAS 2015-10-20 15:33:18 -04:00
Ludovic Marcotte 1716e46210 (fix) last fixes for cache coherency 2015-10-20 08:48:39 -04:00
Juan Vallés 8800cdf641 oc-calendar: force cache synchronisation for a message before aborting
Sometimes we're trying to get the `objectVersion` of a calendar message,
but this message's entry is not in the cache. The method
`synchroniseCache` won't work in this case, so we try to force the
synchronisation of that particular message in order to get the change
number before aborting.
2015-10-20 11:33:10 +02:00
Enrique J. Hernández Blasco d998786ea6 oc-mail: Always increase the CN when setting the message read flag
And perform the real IMAP operation on save method as described by
[MS-OXCFXICS] and [MS-OXCMSG] Section 2.2.3.3, these operations must be
committed when SaveChangesMessage is called.

As it is expected by Outlook to increase the change number when
performing the `SetMessageReadFlag` ROP, if it is not done, the client
tries indefinitely to store that.
2015-10-20 09:56:11 +02:00
Jesús García Sáez a50758cc06 Merge pull request #197 from zentyal/jvalles/revert-content-as-blob-master
Revert "Store c_content in sogo_cache_folder as a BLOB"
2015-10-16 12:43:14 +02:00
Juan Vallés 498cec767b Revert "Store c_content in sogo_cache_folder as a BLOB"
This reverts commit b329513845. It was
causing issues and we'll need to do more testing on this optimisation.
2015-10-16 12:08:50 +02:00
Ludovic Marcotte 8cb580633d (fix) oh well, old crappy distros 2015-10-15 15:54:25 -04:00
Ludovic Marcotte 2b1e060881 (feat) initial S/MIME support for EAS (#3327) 2015-10-15 15:31:46 -04:00
Ludovic Marcotte 3702e79a00 (fix) don't escape quoted strings during versit generation 2015-10-15 12:51:30 -04:00
Ludovic Marcotte beb484da10 (fix) make sure English is always used when generating Date headers using EAS (#3356) 2015-10-14 09:57:56 -04:00
Ludovic Marcotte a54cc22da8 (fix) we no longer always rewrite emails for Outlook when using EAS 2015-10-14 09:29:52 -04:00
Ludovic Marcotte 7f92aca0bd (fix) handle references correctly over EAS (#3365) 2015-10-14 09:27:34 -04:00
Ludovic Marcotte 8d85ca13df (fix) numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849) 2015-10-14 09:21:32 -04:00
Carlos Pérez-Aradros Herce fb7e044bd3 Merge pull request #194 from zentyal/ejhernandez/skip-recurrence-id-without-dtstart
Ignore recurrence-id vevents without dtstart and outside date range
2015-10-07 17:49:56 +02:00
Enrique J. Hernández c08abdb32f Merge pull request #195 from zentyal/jbahillo/view-all-spanish
Changed Spanish string for view all (events/tasks)
2015-10-02 16:25:30 +02:00
Xavy Bahillo c55eab6404 Changed Spanish string for view all (events/tasks)
As current string was referring only to events
2015-10-01 18:16:59 +02:00
Enrique J. Hernández 7680c4091e Merge pull request #191 from zentyal/jvalles/cache-blob
Store c_content in sogo_cache_folder as a BLOB
2015-09-30 16:44:18 +02:00
Enrique J. Hernández 5911563b0f Merge pull request #178 from inverse-inc/v2
Merge from upstream 2.3.2
2015-09-30 14:26:38 +02:00
Enrique J. Hernández Blasco eebf878e89 Ignore recurrence-id vevents without dtstart and outside date range
Happened in an imported vevent from Mozilla Thunderbird.

The crash was:

  Sep 14 15:49:38 sogod [21063]: <0x6442DBF8[SOGoAppointmentFolder]:personal> missing 'c_startdate' in record?
  Sep 14 15:49:38 sogod [21063]: <0x6442DBF8[SOGoAppointmentFolder]:personal> missing 'c_enddate' in record?
  2015-09-14 15:49:38.927 sogod[21063] NGCalendarDateRange.m:37  Assertion failed in NGCalendarDateRange(instance), method initWithStartDate:endDate:.  startDate MUST NOT be nil!
  EXCEPTION: <NSException: 0x7fb96b3c0ed8> NAME:NSInternalInconsistencyException REASON:NGCalendarDateRange.m:37  Assertion failed in NGCalendarDateRange(instance), method initWithStartDate:endDate:.  startDate MUST NOT be nil! INFO:(null)

The relevant ICS file lines are the following ones:

  BEGIN:VEVENT
  UID:040000008200E00074C5B7101A82E00800000000901646A7234BCE01000000000000000010000000E9152C8FF1C27D488C91967FAAFCC2B0
  RECURRENCE-ID:20140513T100000Z
  DURATION:PT1H
  CLASS:PUBLIC
  ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=krsny >>
      Ann Thierry K:mailto:krsny@example.com
  END:VEVENT
2015-09-30 11:35:12 +02:00
Enrique J. Hernández a8e02b94d4 Merge pull request #193 from zentyal/jag/attach-has-content-id
oc-mail: Fixed false positive in [MAPIStoreAttachment hasContentId]
2015-09-29 19:24:41 +02:00
Jesús García Sáez 6ada034c33 Merge pull request #190 from zentyal/jgarcia/freebusy-multidomain
Fix freebusy for multidomain environments
2015-09-29 13:57:22 +02:00
Javier Amor García c01b21e402 oc-mail: Fixed false positive in [MAPIStoreAttachment hasContentId]
This method does not longer returns true if the content id
was a empty string.
In some case the old false positive triggered the removal of
attachments when sending messages.
2015-09-28 13:06:30 +02:00
Ludovic Marcotte c8c7aaa459 (feat) GUI code to set or now sync flag on address book.
Also thrashed entirely the sync tag concept. Funambol is dead, thank god for that.
2015-09-25 15:21:06 -04:00
Ludovic Marcotte 5409cb2927 (feat) moved FolderSynchronize code to GCS 2015-09-25 15:18:22 -04:00
Juan Vallés b329513845 Store c_content in sogo_cache_folder as a BLOB
This reduces considerably the query time for that column, which can be
rather large.
2015-09-25 12:14:32 +02:00
Jesús García Sáez 0ca6e7c6d3 Fix freebusy for multidomain environments
When returning contacts we have to supply also the domain field.
Because in a multidomain environment UIDField is unique only in
the domain so an user must be identified as uid@domain.

So when creating http requests from client side, we have to use
uid@domain instead of only uid so the SOGoUser created on server
side when parsing the requests is created properly.
2015-09-24 19:00:48 +02:00
Jesús García Sáez bea2415fad Merge pull request #184 from zentyal/jvalles/double-digit-folders
Make folderKey encoding consistent
2015-09-23 18:30:18 +02:00
Jesús García Sáez 180269e39e Merge pull request #185 from zentyal/ejhernandez/return-fai-props-on-available
Avoid crashing Outlook after setting a custom view in a calendar folder
2015-09-22 16:04:42 +02:00
Francis Lachapelle 8a3adeadd2 Rename Slovenian template
Closes #104
2015-09-21 20:37:50 -04:00
Enrique J. Hernández fc8eedf494 Merge pull request #186 from zentyal/ejhernandez/cherry-pick-42f10e1
(fix) safe-guard against not-yet-created calendar of local users
2015-09-18 21:57:25 +02:00
Ludovic Marcotte c8acba8f5a (fix) safe-guard against not-yet-created calendar of local users 2015-09-18 21:56:43 +02:00
Enrique J. Hernández Blasco deca6e383a oc: Include 0x683d0040 as another unknown FAI message property
And returning right type.

We have sorted the FAI properties by number and set the available
name as a comment to ease the tracking afterwards.
2015-09-18 21:45:00 +02:00
Enrique J. Hernández Blasco f3ac7b8334 oc: Return FAI available properties on instance method as well
The operation RopFastTransferSourceCopyTo calls the available
properties for a message using the instance method. It seems the
preferred method by Outlook to synchronise a FAI message. OpenChange
calls the message to get the available properties, so the instance
method is called. As it is specialised to return the custom hack
FAI properties, we have to call that class method instead of using
generic one available at NSObject (MAPIStoreProperties) class.

This avoids crashing the Outlook client after we synchronise the
calendar folder after changing the timeframe view (eg from day view
to month view).
2015-09-18 21:40:01 +02:00
Francis Lachapelle ad8941ba56 Fix URL escaping unti test 2015-09-18 13:48:36 -04:00
Ludovic Marcotte 2bccfcb725 (fix) additional fix for issue #3152 2015-09-17 16:28:42 -04:00
Ludovic Marcotte 4d174b4c6c (fix) typo in previous commit 2015-09-17 16:02:21 -04:00
Ludovic Marcotte 3b15a79c0a (fix) memory leak when sync'ing GCS collections 2015-09-17 15:58:38 -04:00
Ludovic Marcotte b4c2857bb5 (fix) typo in version number for doc 2015-09-16 15:18:10 -04:00
Juan Vallés ba68bd8935 Make folderKey encoding consistent
The folder names are encoded through the `asCSSIdentifier` and
`stringByEncodingImap4FolderName` functions when we store them as folder
keys. In addition, the prefix "folder" is added to the key.

The order in which these operations were done when storing the folder
keys (and reverted when retrieving them) wasn't consistent trough the
code. This led to problems such as creating twice a folder with a digit
at the beginning of its name.

The folder name goes now through the following operations when being
stored as a key (the retrieval reverts these in the reverse order):

 * `stringByEncodingImap4FolderName`
 * `asCSSIdentifier`
 * Add "folder" prefix
2015-09-15 09:57:30 +02:00
Juan Vallés 734aba5ddb oc: Fix asCSSIdentifier in openchange_user_cleanup
The method lacked the check for the initial character, which adds an
underscore at the beginning of the strings that start with a digit.
2015-09-14 15:53:07 +02:00
675 changed files with 26763 additions and 13487 deletions

No files matched your search

+44
View File
@@ -15,13 +15,17 @@ trans.es_ES = UI/MailerUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailerUI/Basque.lproj/Localizable.strings
trans.fi = UI/MailerUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MailerUI/French.lproj/Localizable.strings
trans.hr_HR = UI/MailerUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MailerUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MailerUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MailerUI/Italian.lproj/Localizable.strings
trans.lt = UI/MailerUI/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/MailerUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailerUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailerUI/Polish.lproj/Localizable.strings
trans.pt = UI/MailerUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailerUI/Russian.lproj/Localizable.strings
trans.sk = UI/MailerUI/Slovak.lproj/Localizable.strings
@@ -44,13 +48,17 @@ trans.es_ES = UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/PreferencesUI/Basque.lproj/Localizable.strings
trans.fi = UI/PreferencesUI/Finnish.lproj/Localizable.strings
trans.fr = UI/PreferencesUI/French.lproj/Localizable.strings
trans.hr_HR = UI/PreferencesUI/Croatian.lproj/Localizable.strings
trans.hu = UI/PreferencesUI/Hungarian.lproj/Localizable.strings
trans.is = UI/PreferencesUI/Icelandic.lproj/Localizable.strings
trans.it = UI/PreferencesUI/Italian.lproj/Localizable.strings
trans.lt = UI/PreferencesUI/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/PreferencesUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/PreferencesUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/PreferencesUI/Polish.lproj/Localizable.strings
trans.pt = UI/PreferencesUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/PreferencesUI/Russian.lproj/Localizable.strings
trans.sk = UI/PreferencesUI/Slovak.lproj/Localizable.strings
@@ -73,13 +81,17 @@ trans.es_ES = UI/Scheduler/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Scheduler/Basque.lproj/Localizable.strings
trans.fi = UI/Scheduler/Finnish.lproj/Localizable.strings
trans.fr = UI/Scheduler/French.lproj/Localizable.strings
trans.hr_HR = UI/Scheduler/Croatian.lproj/Localizable.strings
trans.hu = UI/Scheduler/Hungarian.lproj/Localizable.strings
trans.is = UI/Scheduler/Icelandic.lproj/Localizable.strings
trans.it = UI/Scheduler/Italian.lproj/Localizable.strings
trans.lt = UI/Scheduler/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/Scheduler/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Scheduler/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Scheduler/Polish.lproj/Localizable.strings
trans.pt = UI/Scheduler/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Scheduler/Russian.lproj/Localizable.strings
trans.sk = UI/Scheduler/Slovak.lproj/Localizable.strings
@@ -102,13 +114,17 @@ trans.es_ES = UI/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Contacts/Basque.lproj/Localizable.strings
trans.fi = UI/Contacts/Finnish.lproj/Localizable.strings
trans.fr = UI/Contacts/French.lproj/Localizable.strings
trans.hr_HR = UI/Contacts/Croatian.lproj/Localizable.strings
trans.hu = UI/Contacts/Hungarian.lproj/Localizable.strings
trans.is = UI/Contacts/Icelandic.lproj/Localizable.strings
trans.it = UI/Contacts/Italian.lproj/Localizable.strings
trans.lt = UI/Contacts/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Contacts/Polish.lproj/Localizable.strings
trans.pt = UI/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Contacts/Russian.lproj/Localizable.strings
trans.sk = UI/Contacts/Slovak.lproj/Localizable.strings
@@ -131,13 +147,17 @@ trans.es_ES = UI/MainUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MainUI/Basque.lproj/Localizable.strings
trans.fi = UI/MainUI/Finnish.lproj/Localizable.strings
trans.fr = UI/MainUI/French.lproj/Localizable.strings
trans.hr_HR = UI/MainUI/Croatian.lproj/Localizable.strings
trans.hu = UI/MainUI/Hungarian.lproj/Localizable.strings
trans.is = UI/MainUI/Icelandic.lproj/Localizable.strings
trans.it = UI/MainUI/Italian.lproj/Localizable.strings
trans.lt = UI/MainUI/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/MainUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MainUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MainUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MainUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MainUI/Polish.lproj/Localizable.strings
trans.pt = UI/MainUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MainUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MainUI/Russian.lproj/Localizable.strings
trans.sk = UI/MainUI/Slovak.lproj/Localizable.strings
@@ -160,13 +180,17 @@ trans.es_ES = UI/Common/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/Common/Basque.lproj/Localizable.strings
trans.fi = UI/Common/Finnish.lproj/Localizable.strings
trans.fr = UI/Common/French.lproj/Localizable.strings
trans.hr_HR = UI/Common/Croatian.lproj/Localizable.strings
trans.hu = UI/Common/Hungarian.lproj/Localizable.strings
trans.is = UI/Common/Icelandic.lproj/Localizable.strings
trans.it = UI/Common/Italian.lproj/Localizable.strings
trans.lt = UI/Common/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/Common/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/Common/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/Common/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/Common/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/Common/Polish.lproj/Localizable.strings
trans.pt = UI/Common/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/Common/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/Common/Russian.lproj/Localizable.strings
trans.sk = UI/Common/Slovak.lproj/Localizable.strings
@@ -189,13 +213,17 @@ trans.es_ES = UI/AdministrationUI/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/AdministrationUI/Basque.lproj/Localizable.strings
trans.fi = UI/AdministrationUI/Finnish.lproj/Localizable.strings
trans.fr = UI/AdministrationUI/French.lproj/Localizable.strings
trans.hr_HR = UI/AdministrationUI/Croatian.lproj/Localizable.strings
trans.hu = UI/AdministrationUI/Hungarian.lproj/Localizable.strings
trans.is = UI/AdministrationUI/Icelandic.lproj/Localizable.strings
trans.it = UI/AdministrationUI/Italian.lproj/Localizable.strings
trans.lt = UI/AdministrationUI/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/AdministrationUI/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/AdministrationUI/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/AdministrationUI/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/AdministrationUI/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/AdministrationUI/Polish.lproj/Localizable.strings
trans.pt = UI/AdministrationUI/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/AdministrationUI/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/AdministrationUI/Russian.lproj/Localizable.strings
trans.sk = UI/AdministrationUI/Slovak.lproj/Localizable.strings
@@ -218,13 +246,17 @@ trans.es_ES = SoObjects/Appointments/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Appointments/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Appointments/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Appointments/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Appointments/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Appointments/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Appointments/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Appointments/Italian.lproj/Localizable.strings
trans.lt = SoObjects/Appointments/Lithuanian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Appointments/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Appointments/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Appointments/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Appointments/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Appointments/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Appointments/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Appointments/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Appointments/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Appointments/Slovak.lproj/Localizable.strings
@@ -247,13 +279,17 @@ trans.es_ES = SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Contacts/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Contacts/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Contacts/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Contacts/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Contacts/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Contacts/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Contacts/Italian.lproj/Localizable.strings
trans.lt = SoObjects/Contacts/Lithuanian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Contacts/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Contacts/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Contacts/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Contacts/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Contacts/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Contacts/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Contacts/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Contacts/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Contacts/Slovak.lproj/Localizable.strings
@@ -276,13 +312,17 @@ trans.es_ES = SoObjects/Mailer/SpanishSpain.lproj/Localizable.strings
trans.eu = SoObjects/Mailer/Basque.lproj/Localizable.strings
trans.fi = SoObjects/Mailer/Finnish.lproj/Localizable.strings
trans.fr = SoObjects/Mailer/French.lproj/Localizable.strings
trans.hr_HR = SoObjects/Mailer/Croatian.lproj/Localizable.strings
trans.hu = SoObjects/Mailer/Hungarian.lproj/Localizable.strings
trans.is = SoObjects/Mailer/Icelandic.lproj/Localizable.strings
trans.it = SoObjects/Mailer/Italian.lproj/Localizable.strings
trans.lt = SoObjects/Mailer/Lithuanian.lproj/Localizable.strings
trans.mk_MK = SoObjects/Mailer/Macedonian.lproj/Localizable.strings
trans.nb_NO = SoObjects/Mailer/NorwegianBokmal.lproj/Localizable.strings
trans.nl = SoObjects/Mailer/Dutch.lproj/Localizable.strings
trans.nn_NO = SoObjects/Mailer/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = SoObjects/Mailer/Polish.lproj/Localizable.strings
trans.pt = SoObjects/Mailer/Portuguese.lproj/Localizable.strings
trans.pt_BR = SoObjects/Mailer/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = SoObjects/Mailer/Russian.lproj/Localizable.strings
trans.sk = SoObjects/Mailer/Slovak.lproj/Localizable.strings
@@ -305,13 +345,17 @@ trans.es_ES = UI/MailPartViewers/SpanishSpain.lproj/Localizable.strings
trans.eu = UI/MailPartViewers/Basque.lproj/Localizable.strings
trans.fi = UI/MailPartViewers/Finnish.lproj/Localizable.strings
trans.fr = UI/MailPartViewers/French.lproj/Localizable.strings
trans.hr_HR = UI/MailPartViewers/Croatian.lproj/Localizable.strings
trans.hu = UI/MailPartViewers/Hungarian.lproj/Localizable.strings
trans.is = UI/MailPartViewers/Icelandic.lproj/Localizable.strings
trans.it = UI/MailPartViewers/Italian.lproj/Localizable.strings
trans.lt = UI/MailPartViewers/Lithuanian.lproj/Localizable.strings
trans.mk_MK = UI/MailPartViewers/Macedonian.lproj/Localizable.strings
trans.nb_NO = UI/MailPartViewers/NorwegianBokmal.lproj/Localizable.strings
trans.nl = UI/MailPartViewers/Dutch.lproj/Localizable.strings
trans.nn_NO = UI/MailPartViewers/NorwegianNynorsk.lproj/Localizable.strings
trans.pl = UI/MailPartViewers/Polish.lproj/Localizable.strings
trans.pt = UI/MailPartViewers/Portuguese.lproj/Localizable.strings
trans.pt_BR = UI/MailPartViewers/BrazilianPortuguese.lproj/Localizable.strings
trans.ru = UI/MailPartViewers/Russian.lproj/Localizable.strings
trans.sk = UI/MailPartViewers/Slovak.lproj/Localizable.strings
+4 -1
View File
@@ -1 +1,4 @@
# compilation settings
ifeq ($(HAS_LIBRARY_ssl),yes)
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif
+1 -1
View File
@@ -101,7 +101,7 @@ static NSArray *asElementArray = nil;
int i, count;
if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil];
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil];
data = [NSMutableDictionary dictionary];
+196 -50
View File
@@ -46,13 +46,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
#import <NGObjWeb/WOContext+SoObjects.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
@implementation NGVCard (ActiveSync)
//
// This function is called for each elements which can be ghosted according to specs.
// https://msdn.microsoft.com/en-us/library/gg650908%28v=exchg.80%29.aspx
//
- (BOOL) _isGhosted: (NSString *) element
inContext: (WOContext *) context
{
NSArray *supportedElements;
supportedElements = [context objectForKey: @"SupportedElements"];
// If the client does not include a Supported element in the initial Sync command request for
// a folder, then all of the elements that can be ghosted are considered not ghosted.
if (!supportedElements)
return NO;
// If the client includes an empty Supported element in the initial Sync command request for
// a folder, then all elements that can be ghosted are considered ghosted.
if (![supportedElements count])
return YES;
// If the client includes a Supported element that contains child elements in the initial
// Sync command request for a folder, then each child element of that Supported element is
// considered not ghosted. All elements that can be ghosted that are not included as child
// elements of the Supported element are considered ghosted.
if (!([supportedElements indexOfObject: element] == NSNotFound))
return YES;
return NO;
}
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr;
NSMutableString *s;
NSMutableString *s, *a;
NSString *url;
id o;
@@ -63,7 +99,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [n flattenedValueAtIndex: 0 forKey: @""]))
[s appendFormat: @"<LastName xmlns=\"Contacts:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
@@ -146,16 +182,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
homeAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<HomeState xmlns=\"Contacts:\">%@</HomeState>", [o activeSyncRepresentationInContext: context]];
if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<HomePostalCode xmlns=\"Contacts:\">%@</HomePostalCode>", [o activeSyncRepresentationInContext: context]];
@@ -171,9 +213,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([addresses count])
{
workAdr = [addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""]))
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [o activeSyncRepresentationInContext: context]];
[a appendString: o];
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", o];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
@@ -190,7 +238,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Other, less important fields
if ((o = [self birthday]))
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>", [o activeSyncRepresentationInContext: context]];
{
NSTimeZone *userTimeZone;
userTimeZone = [[[context activeUser] userDefaults] timeZone];
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>",
[[o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: ([userTimeZone secondsFromGMTForDate: o])*-1]
activeSyncRepresentationInContext: context]];
}
if ((o = [self note]))
{
@@ -217,6 +274,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inContext: (WOContext *) context
{
CardElement *element;
NSMutableArray *addressLines;
id o;
// Contact's note
@@ -226,6 +284,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Categories
if ((o = [theValues objectForKey: @"Categories"]) && [o length])
[self setCategories: o];
else
[[self children] removeObjectsInArray: [self childrenWithTag: @"Categories"]];
// Birthday
if ((o = [theValues objectForKey: @"Birthday"]))
@@ -233,6 +293,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
o = [o calendarDate];
[self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d" timeZone: nil locale: nil]];
}
else if (![self _isGhosted: @"Birthday" inContext: context])
{
[self setBday: @""];
}
//
// Business address information
@@ -244,18 +309,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// BusinessCountry
//
element = [self elementWithTag: @"adr" ofType: @"work"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"BusinessStreet"]) || ![self _isGhosted: @"BusinessStreet" 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 than 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: @"BusinessCity"]) || ![self _isGhosted: @"BusinessCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessState"]) || ![self _isGhosted: @"BusinessState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessPostalCode"]) || ![self _isGhosted: @"BusinessPostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"BusinessCountry"]) || ![self _isGhosted: @"BusinessCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"BusinessCountry"]
atIndex: 6 forKey: @""];
}
//
// Home address information
@@ -267,35 +362,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// HomeCountry
//
element = [self elementWithTag: @"adr" ofType: @"home"];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeStreet"]
atIndex: 2 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
if ((o = [theValues objectForKey: @"HomeStreet"]) || ![self _isGhosted: @"HomeStreet" 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: @"HomeCity"]) || ![self _isGhosted: @"HomeCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeState"]) || ![self _isGhosted: @"HomeState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomePostalCode"]) || ![self _isGhosted: @"HomePostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomePostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"HomeCountry"]) || ![self _isGhosted: @"HomeCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"HomeCountry"]
atIndex: 6 forKey: @""];
}
// Company's name
if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil];
else if (![self _isGhosted: @"CompanyName" inContext: context])
[self setOrg: @"" units: nil];
// Department
if ((o = [theValues objectForKey: @"Department"]))
[self setOrg: nil units: [NSArray arrayWithObjects:o,nil]];
else if (![self _isGhosted: @"Department" inContext: context])
[self setOrg: nil units: [NSArray arrayWithObjects:@"",nil]];
// Email addresses
if ((o = [theValues objectForKey: @"Email1Address"]))
if ((o = [theValues objectForKey: @"Email1Address"]) || ![self _isGhosted: @"Email1Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"work"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
if ((o = [theValues objectForKey: @"Email2Address"]))
if ((o = [theValues objectForKey: @"Email2Address"]) || ![self _isGhosted: @"Email2Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"home"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -303,7 +432,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
// FIXME: revise this when the GUI revamp is done in SOGo
if ((o = [theValues objectForKey: @"Email3Address"]))
if ((o = [theValues objectForKey: @"Email3Address"]) || ![self _isGhosted: @"Email3Address" inContext: context])
{
element = [self elementWithTag: @"email" ofType: @"three"];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
@@ -313,45 +442,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// MiddleName
// Suffix (II)
// Title (Mr.)
[self setFn: [theValues objectForKey: @"FileAs"]];
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
[self setFn: [theValues objectForKey: @"FileAs"]];
[self setNWithFamily: [theValues objectForKey: @"LastName"]
given: [theValues objectForKey: @"FirstName"]
additional: nil prefixes: nil suffixes: nil];
// IM information
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
[[self uniqueChildWithTag: @"x-aim"]
setSingleValue: [theValues objectForKey: @"IMAddress"]
forKey: @""];
//
// Phone numbrrs
//
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessPhoneNumber"]) || ![self _isGhosted: @"BusinessPhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"work"];
[element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"HomePhoneNumber"]) || ![self _isGhosted: @"HomePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"home"];
[element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"MobilePhoneNumber"]) || ![self _isGhosted: @"MobilePhoneNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"cell"];
[element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"BusinessFaxNumber"]) || ![self _isGhosted: @"BusinessFaxNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"fax"];
[element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""];
}
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
if ((o = [theValues objectForKey: @"PagerNumber"]) || ![self _isGhosted: @"PagerNumber" inContext: context])
{
element = [self elementWithTag: @"tel" ofType: @"pager"];
[element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""];
}
// Job's title
if ((o = [theValues objectForKey: @"JobTitle"]))
if ((o = [theValues objectForKey: @"JobTitle"]) || ![self _isGhosted: @"JobTitle" inContext: context])
[self setTitle: o];
// WebPage (work)
if ((o = [theValues objectForKey: @"WebPage"]))
if ((o = [theValues objectForKey: @"WebPage"]) || ![self _isGhosted: @"WebPage" inContext: context])
[[self elementWithTag: @"url" ofType: @"work"]
setSingleValue: o forKey: @""];
if ((o = [theValues objectForKey: @"NickName"]))
if ((o = [theValues objectForKey: @"NickName"]) || ![self _isGhosted: @"NickName" inContext: context])
[self setNickname: o];
if ((o = [theValues objectForKey: @"Picture"]))
+1 -1
View File
@@ -75,7 +75,7 @@ static NSArray *easCommandParameters = nil;
escapeCount++;
break;
default:
if (chars[i] > 127)
if (chars[i] < 0x20 || chars[i] > 127)
escapeCount++;
break;
}
+475 -130
View File
@@ -78,6 +78,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoCacheGCSObject.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentObject.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentFolders.h>
@@ -112,13 +113,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher (Sync)
- (void) _setOrUnsetSyncRequest: (BOOL) set
collections: (NSArray *) collections
{
SOGoCacheGCSObject *o;
NSNumber *processIdentifier;
NSString *key;
int i;
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
if (set)
{
RELEASE(syncRequest);
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
RETAIN(syncRequest);
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
for (i = 0; i < [collections count]; i++)
{
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
[[o properties] setObject: processIdentifier forKey: key];
}
}
else
{
[[o properties] removeObjectForKey: @"SyncRequest"];
for (i = 0; i < [collections count]; i++)
{
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
[[o properties] removeObjectForKey: key];
}
}
[o save];
}
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
forKey: (NSString *) theFolderKey
{
NSNumber *processIdentifier, *processIdentifierInCache;
SOGoCacheGCSObject *o;
NSDictionary *values;
NSString *key;
if ([theFolderKey hasPrefix: @"folder"])
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
else
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
// Don't update the cache if another request is processing the same collection.
if (!([processIdentifierInCache isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
return;
}
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
values = [theFolderMetadata copy];
@@ -132,6 +193,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
@@ -404,38 +466,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
}
break;
case ActiveSyncEventFolder:
case ActiveSyncTaskFolder:
{
o = [sogoObject component: NO secure: NO];
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
if (theFolderType == ActiveSyncEventFolder &&
[(iCalEvent *)o userIsAttendee: [context activeUser]])
{
[o changeParticipationStatus: allChanges inContext: context component: sogoObject];
}
else
{
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
}
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
}
break;
case ActiveSyncMailFolder:
default:
{
NSDictionary *result;
NSString *modseq;
NSNumber *modseq;
[sogoObject takeActiveSyncValues: allChanges inContext: context];
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
if (modseq)
[syncCache setObject: modseq forKey: serverId];
if (modseq && [syncCache objectForKey: serverId])
[syncCache setObject: [modseq stringValue] forKey: serverId];
}
}
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[theBuffer appendString: @"<Change>"];
@@ -512,6 +583,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// FIXME: handle errors here
if (deletesAsMoves && theFolderType == ActiveSyncMailFolder)
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context];
else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder)
{
[sogoObject prepareDelete];
[sogoObject delete];
}
else
[sogoObject delete];
}
@@ -552,6 +628,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
// The complete item is then returned to the client in a server response.
[context setObject: @"8" forKey: @"MIMETruncation"];
o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
inContext: context
acquire: NO];
@@ -578,36 +659,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFilterType: (NSCalendarDate *) theFilterType
inBuffer: (NSMutableString *) theBuffer
lastServerKey: (NSString **) theLastServerKey
defaultInterval: (unsigned int) theDefaultInterval
{
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
NSString *davCollectionTagToStore;
NSAutoreleasePool *pool;
NSMutableString *s;
BOOL more_available;
BOOL cleanup_needed, more_available;
int i, max;
s = [NSMutableString string];
more_available = NO;
cleanup_needed = more_available = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
// If this is a new sync operation, DateCache and SyncCache need to be deleted
if ([theSyncKey isEqualToString: @"-1"])
{
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
}
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
{
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
// We need to cleanup this mess.
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
cleanup_needed = YES;
}
syncCache = [folderMetadata objectForKey: @"SyncCache"];
dateCache = [folderMetadata objectForKey: @"DateCache"];
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
theFilterType)
(cleanup_needed ||
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
theFilterType
)
{
NSArray *allKeys;
NSString *key;
@@ -623,14 +712,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
{
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
if ([syncCache objectForKey:key])
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete %@", key];
[syncCache removeObjectForKey: key];
[dateCache removeObjectForKey: key];
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
[syncCache removeObjectForKey: key];
//[dateCache removeObjectForKey: key];
softdelete_count++;
softdelete_count++;
}
else if (cleanup_needed)
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
// With this we make sure that a SoftDelete is set again on next sync.
[syncCache setObject: @"0" forKey: key];
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
// Now we are save to remove the dateCache entry.
[dateCache removeObjectForKey: key];
}
}
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
@@ -657,8 +768,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
return;
more_available = NO;
davCollectionTagToStore = [theCollection davCollectionTag];
@@ -687,7 +796,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
initialLoadInProgress = NO;
if ([theSyncKey isEqualToString: @"-1"])
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
{
@@ -697,13 +806,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
}
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
allComponents = [theCollection syncTokenFieldsWithProperties: nil
matchingSyncToken: theSyncKey
fromDate: theFilterType
initialLoad: initialLoadInProgress];
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
// Check for the WindowSize
max = [allComponents count];
//
// Cleanup the mess
//
if (cleanup_needed)
{
for (i = 0; i < max; i++)
{
component = [allComponents objectAtIndex: i];
deleted = [[component objectForKey: @"c_deleted"] intValue];
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
continue;
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
if (deleted)
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
[syncCache setObject: @"0" forKey: uid];
}
else
{
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
// Cleanup the cache to make sure the add is sent again.
[syncCache removeObjectForKey: uid];
[dateCache removeObjectForKey: uid];
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
// Update cache entry to make sure the change is sent again.
[syncCache setObject: @"0" forKey: uid];
}
}
}
}
return_count = 0;
for (i = 0; i < max; i++)
@@ -767,35 +926,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (theFolderType == ActiveSyncContactFolder)
componentObject = [sogoObject vCard];
else
componentObject = [sogoObject component: NO secure: NO];
//
// We do NOT synchronize NEW events that are in fact, invitations
// to events. This is due to the fact that Outlook 2013 creates
// "phantom" events in the calendar that are mapped to invitations mails.
// If we synchronize these events too, it'll interfere with the whole thing
// and prevent Outlook from properly calling MeetingResponse.
//
if (!updated && theFolderType == ActiveSyncEventFolder)
{
iCalPersonPartStat partstat;
iCalPerson *attendee;
NSString *email;
email = [[[context activeUser] allEmails] objectAtIndex: 0];
attendee = [componentObject findAttendeeWithEmail: email];
if (attendee)
{
partstat = [attendee participationStatus];
if (partstat == iCalPersonPartStatNeedsAction)
{
DESTROY(pool);
continue;
}
}
}
componentObject = [sogoObject component: NO secure: NO];
[syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid];
@@ -862,11 +993,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoMailObject *mailObject;
NSArray *allMessages, *a;
NSString *firstUIDAdded;
int j, k, return_count, highestmodseq;
BOOL found_in_cache, initialLoadInProgress;
initialLoadInProgress = NO;
initialLoadInProgress = NO;
found_in_cache = NO;
firstUIDAdded = nil;
if ([theSyncKey isEqualToString: @"-1"])
{
@@ -897,7 +1031,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (i = 0; i < max; i++)
{
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
}
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
@@ -906,15 +1040,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
if (debugOn)
{
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
}
lastCacheObject = [sortedBySequence lastObject];
//
// Cleanup the mess
//
if (cleanup_needed)
{
NSMutableArray *sortedByUID;
int uidnextFromCache;
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
[sortedByUID sortUsingSelector: @selector(compareUID:)];
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
uidnextFromCache = [[a objectAtIndex: 0] intValue];
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
// Remove all entries from cache beginning with the first uid added by the last sync request.
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
}
RELEASE(sortedByUID);
for (j = 0; j < [allCacheObjects count]; j++)
{
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
//and we would skip the cache when generating the response.
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
}
}
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
if (!cleanup_needed &&
[folderMetadata objectForKey: @"MoreAvailable"] &&
lastCacheObject &&
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
{
for (j = 0; j < [allCacheObjects count]; j++)
{
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
{
// We need to continue with adds or deletes from here.
found_in_cache = YES;
j--;
break;
}
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
{
// Found out where we're at, let's continue from there...
@@ -929,12 +1130,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (found_in_cache)
k = j+1;
else
{
k = 0;
j = 0;
}
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
j = k = 0;
if (debugOn)
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
return_count = 0;
@@ -948,20 +1147,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSString *lastSequence;
more_available = YES;
if (!firstUIDAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
firstUIDAdded = [a objectAtIndex: 0];
RETAIN(firstUIDAdded);
}
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
if (debugOn)
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
DESTROY(pool);
break;
}
aCacheObject = [allCacheObjects objectAtIndex: k];
// If found in cache, it's either a Change or a Delete
if (debugOn)
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
// If found in cache, it's either a Change or a Delete operation.
if ([syncCache objectForKey: [aCacheObject uid]])
{
if ([[aCacheObject sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - DELETE!"];
// Deleted
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
@@ -979,9 +1193,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mailObject = [theCollection lookupName: [aCacheObject uid]
inContext: context
acquire: 0];
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
{
if (debugOn)
[self logWithFormat: @"EAS - CHANGE!"];
[s appendString: @"<Change xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
@@ -997,6 +1214,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - ADD!"];
// Added
if (![[aCacheObject sequence] isEqual: [NSNull null]])
{
@@ -1029,11 +1249,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
// Save the frist UID we add. We will use it for the synckey late.
if (!firstUIDAdded)
{
firstUIDAdded = [aCacheObject uid];
RETAIN(firstUIDAdded);
if (debugOn)
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
}
return_count++;
}
else
{
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
if (debugOn)
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
}
}
@@ -1042,16 +1273,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (more_available)
{
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
}
else
{
[folderMetadata removeObjectForKey: @"MoreAvailable"];
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
if (firstUIDAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
RELEASE(firstUIDAdded);
}
else
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
}
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
RELEASE(*theLastServerKey);
} // default:
@@ -1083,8 +1323,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inBuffer: (NSMutableString *) theBuffer
processed: (BOOL *) processed
{
id <DOMNodeList> aCommandDetails;
id <DOMElement> aCommand, element;
id <DOMNodeList> aCommandDetails;
NSAutoreleasePool *pool;
NSArray *allCommands;
int i, j;
@@ -1097,6 +1338,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [(id)aCommandDetails count]; j++)
{
pool = [[NSAutoreleasePool alloc] init];
element = [aCommandDetails objectAtIndex: j];
if ([element nodeType] == DOM_ELEMENT_NODE)
@@ -1138,6 +1380,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*processed = YES;
}
}
DESTROY(pool);
}
}
}
@@ -1150,15 +1393,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
changeDetected: (BOOL *) changeDetected
maxSyncResponseSize: (int) theMaxSyncResponseSize
{
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
SOGoMicrosoftActiveSyncFolderType folderType;
id collection, value;
NSMutableString *changeBuffer, *commandsBuffer;
BOOL getChanges, first_sync;
unsigned int windowSize, v, status;
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *lastServerKey, *syncKeyInCache, *folderKey;
NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableArray *supportedElements, *supportedElementNames;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
SOGoMicrosoftActiveSyncFolderType folderType;
unsigned int windowSize, v, status, i;
BOOL getChanges, first_sync;
changeBuffer = [NSMutableString string];
commandsBuffer = [NSMutableString string];
@@ -1181,7 +1425,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[theBuffer appendString: @"</Collection>"];
return;
}
//
// First check if we have any concurrent Sync requests going on for this device.
// If we do and we are still within our maximumSyncInterval, we let our EAS
// device know to retry.
//
folderKey = [self _getNameInCache: collection withType: folderType];
folderMetadata = [self _folderMetadataForKey: folderKey];
// We check for a window size, default to 100 if not specfied or out of bounds
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
@@ -1206,13 +1458,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
first_sync = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
if ([syncKey isEqualToString: @"0"])
{
davCollectionTag = @"-1";
first_sync = YES;
*changeDetected = YES;
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
value = [theDocumentElement getElementsByTagName: @"Supported"];
if ([value count])
{
supportedElements = (id)[[value lastObject] childNodes];
if ([supportedElements count])
{
for (i = 0; i < [supportedElements count]; i++)
{
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
}
}
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
[self _setFolderMetadata: folderMetadata forKey: folderKey];
if (debugOn)
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
}
}
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
{
@@ -1231,42 +1505,61 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
if (!bodyPreferenceType)
{
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
{
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
// By default, send MIME mails. See #3146 for details.
if (!bodyPreferenceType)
bodyPreferenceType = @"4";
}
// By default, send MIME mails. See #3146 for details.
if (!bodyPreferenceType)
bodyPreferenceType = @"4";
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
if (!mimeSupport)
mimeSupport = @"1";
if (!mimeTruncation)
mimeTruncation = @"8";
}
else
{
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
{
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
mimeTruncation = [[(id)[theDocumentElement getElementsByTagName: @"MIMETruncation"] lastObject] textValue];
if (!mimeSupport)
if (!mimeSupport)
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
if (!mimeSupport)
if (!mimeSupport)
mimeSupport = @"0";
if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"])
if (!mimeTruncation)
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
if (!mimeTruncation)
mimeTruncation = @"8";
if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"])
bodyPreferenceType = @"2";
else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"])
else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"])
bodyPreferenceType = @"4";
else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"])
else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"])
bodyPreferenceType = @"2";
// Avoid writing to cache if there is nothing to change.
if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] ||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport])
{
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
}
}
// Avoid writing to cache if there is nothing to change.
if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] ||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport] ||
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"] isEqualToString: mimeTruncation])
{
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", mimeTruncation, @"MIMETruncation", bodyPreferenceType, @"BodyPreferenceType", nil];
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
[self _setFolderMetadata: folderMetadata forKey: folderKey];
}
}
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
[context setObject: mimeSupport forKey: @"MIMESupport"];
[context setObject: mimeTruncation forKey: @"MIMETruncation"];
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
//
// We process the commands from the request
@@ -1305,10 +1598,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFolderType: folderType
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
inBuffer: changeBuffer
lastServerKey: &lastServerKey];
lastServerKey: &lastServerKey
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]];
}
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
folderMetadata = [self _folderMetadataForKey: folderKey];
// If we got any changes or if we have applied any commands
// let's regenerate our SyncKey based on the collection tag.
@@ -1467,19 +1761,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoSystemDefaults *defaults;
id <DOMElement> aCollection;
NSMutableString *output, *s;
NSMutableDictionary *globalMetadata;
NSNumber *syncRequestInCache, *processIdentifier;
NSString *key;
NSArray *allCollections;
NSData *d;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
BOOL changeDetected;
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
// We initialize our output buffer
output = [[NSMutableString alloc] init];
defaults = [SOGoSystemDefaults sharedSystemDefaults];
defaultInterval = [defaults maximumSyncInterval];
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
@@ -1494,12 +1793,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
RELEASE(output);
return;
}
// Let other requests know about the collections we are dealing with.
[self _setOrUnsetSyncRequest: YES collections: allCollections];
defaults = [SOGoSystemDefaults sharedSystemDefaults];
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
defaultInterval = [defaults maximumSyncInterval];
internalInterval = [defaults internalSyncInterval];
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
@@ -1509,17 +1812,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// We check to see if our heartbeat interval falls into the supported ranges.
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
{
int limit;
// Interval is too long, inform the client.
heartbeatInterval = defaultInterval;
// Outlook doesn't like this...
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
// When Status = 14, the Wait interval is specified in minutes while
// defaultInterval is specifed in seconds. Adjust accordinlgy.
limit = defaultInterval/60;
if (limit < 1) limit = 1;
if (limit > 59) limit = 59;
//[output appendFormat: @"<Limit>%d</Limit>", limit];
//[output appendFormat: @"<Status>%d</Status>", 14];
}
[output appendString: @"<Collections>"];
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
// We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
@@ -1529,25 +1835,62 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [allCollections count]; j++)
{
aCollection = [allCollections objectAtIndex: j];
[self processSyncCollection: aCollection
inBuffer: s
changeDetected: &changeDetected
maxSyncResponseSize: maxSyncResponseSize];
if (maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize)
// Don't return a response if another Sync is waiting.
globalMetadata = [self globalMetadataForDevice];
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
[theResponse setStatus: 503];
RELEASE(output);
return;
}
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
break;
}
if (changeDetected)
{
[self logWithFormat: @"Change detected, we push the content."];
[self logWithFormat: @"Change detected during Sync, we push the content."];
break;
}
else if (heartbeatInterval > 1)
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
sleep(internalInterval);
total_sleep = 0;
while (total_sleep < internalInterval)
{
// We check if we must break the current synchronization since an other Sync
// has just arrived.
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
{
if (debugOn)
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
// Make sure we end the heardbeat-loop.
heartbeatInterval = internalInterval = 1;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
sleep(5);
total_sleep += 5;
}
}
}
else
{
@@ -1555,8 +1898,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
//
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
// otherwise send an empty response.
//
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
{
// We always return the last generated response.
+10 -1
View File
@@ -1,6 +1,6 @@
/*
Copyright (c) 2014, Inverse inc.
Copyright (c) 2014-2015, Inverse inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -31,16 +31,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncConstants.h"
@class NSCalendarDate;
@class NSException;
@class NSMutableDictionary;
@class NSURL;
@class NSNumber;
@interface SOGoActiveSyncDispatcher : NSObject
{
NSURL *folderTableURL;
NSDictionary *imapFolderGUIDS;
id context;
NSNumber *syncRequest;
BOOL debugOn;
}
- (NSMutableDictionary *) globalMetadataForDevice;
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
+341 -86
View File
@@ -30,8 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncDispatcher.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h>
#if GNUSTEP_BASE_MINOR_VERSION >= 21
#import <Foundation/NSLocale.h>
#endif
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSURL.h>
@@ -134,6 +138,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#endif
@interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@@ -143,8 +153,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher
static BOOL debugOn = NO;
- (id) init
{
[super init];
@@ -152,6 +160,7 @@ static BOOL debugOn = NO;
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
folderTableURL = nil;
imapFolderGUIDS = nil;
syncRequest = nil;
return self;
}
@@ -159,6 +168,7 @@ static BOOL debugOn = NO;
{
RELEASE(folderTableURL);
RELEASE(imapFolderGUIDS);
RELEASE(syncRequest);
[super dealloc];
}
@@ -171,16 +181,16 @@ static BOOL debugOn = NO;
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
[[o properties] removeAllObjects];
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
[[o properties] setObject: theSyncKey
forKey: @"FolderSyncKey"];
[o save];
}
- (NSMutableDictionary *) _globalMetadataForDevice
- (NSMutableDictionary *) globalMetadataForDevice
{
SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -204,7 +214,7 @@ static BOOL debugOn = NO;
if (theFilter)
{
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setObjectType: ActiveSyncFolderCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
@@ -377,7 +387,7 @@ static BOOL debugOn = NO;
[[o properties ] setObject: [[newFolder nameInContainer] substringFromIndex: 6] forKey: @"displayName"];
[o save];
nameInContainer = [[NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]] stringByEscapingURL];
nameInContainer = [NSString stringWithFormat: @"mail/%@", [nameInContainer substringFromIndex: 6]];
}
else
{
@@ -393,17 +403,24 @@ static BOOL debugOn = NO;
SOGoAppointmentFolders *appointmentFolders;
SOGoCacheGCSObject *o;
NSString *key;
id newFolder;
nameInContainer = nil;
appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context];
[appointmentFolders newFolderWithName: displayName
nameInContainer: &nameInContainer];
newFolder = [appointmentFolders lookupName: nameInContainer
inContext: context
acquire: NO];
[newFolder setSynchronize: YES];
if (type == 13)
nameInContainer = [NSString stringWithFormat: @"vevent/%@", nameInContainer];
else
nameInContainer = [NSString stringWithFormat: @"vtodo/%@", nameInContainer];
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
[o setObjectType: ActiveSyncFolderCacheObject];
@@ -418,12 +435,19 @@ static BOOL debugOn = NO;
SOGoContactFolders *contactFolders;
SOGoCacheGCSObject *o;
NSString *key;
id newFolder;
nameInContainer = nil;
contactFolders = [userFolder privateContacts: @"Contacts" inContext: context];
[contactFolders newFolderWithName: displayName
nameInContainer: &nameInContainer];
newFolder = [contactFolders lookupName: nameInContainer
inContext: context
acquire: NO];
[newFolder setSynchronize: YES];
nameInContainer = [NSString stringWithFormat: @"vcard/%@", nameInContainer];
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], nameInContainer ];
@@ -697,26 +721,25 @@ static BOOL debugOn = NO;
- (void) processFolderSync: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType;
NSString *key, *cKey, *nkey, *name, *serverId, *parentId, *nameInCache, *personalFolderName, *syncKey, *folderType, *operation;
NSMutableDictionary *cachedGUIDs, *metadata;
NSMutableArray *folders, *processedFolders;
NSDictionary *folderMetadata, *imapGUIDs;
NSArray *allFoldersMetadata, *allKeys;
NSMutableDictionary *cachedGUIDs, *metadata;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
NSMutableString *s, *commands;
SOGoUserFolder *userFolder;
NSMutableArray *folders, *processedFolders;
SoSecurityManager *sm;
SOGoCacheGCSObject *o;
id currentFolder;
NSData *d;
int status, command_count, i, type, fi, count;
BOOL first_sync;
sm = [SoSecurityManager sharedSecurityManager];
metadata = [self _globalMetadataForDevice];
metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string];
@@ -830,32 +853,38 @@ static BOOL debugOn = NO;
}
else
{
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([cKey rangeOfString: @"vevent" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[cKey rangeOfString: @"vtodo" options: NSCaseInsensitiveSearch].location != NSNotFound)
folderType = @"Calendar";
else
folderType = @"Contacts";
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
if ([ cKey rangeOfString: @"/"].location != NSNotFound)
currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO]
lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO];
// remove the folder from device if it doesn't exists or it has not the proper permissions
if (!currentFolder ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
// Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions
if (!currentFolder ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: currentFolder
inContext: context])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [cKey stringByEscapingURL] ];
command_count++;
[o destroy];
}
}
}
}
// Handle addition and changes
for (i = 0; i < [allFoldersMetadata count]; i++)
@@ -943,6 +972,7 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
[o save];
@@ -953,17 +983,29 @@ static BOOL debugOn = NO;
personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer];
folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy];
[folders autorelease];
[folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]];
// Inside this loop we remove all the folder without write/delete permissions
// We remove all the folders that aren't GCS-ones, that we don't want to synchronize and
// the ones without write/delete permissions.
count = [folders count]-1;
for (; count >= 0; count--)
{
if ([sm validatePermission: SoPerm_DeleteObjects
onObject: [folders objectAtIndex: count]
currentFolder = [folders objectAtIndex: count];
// We skip personal GCS folders - we always want to synchronize these
if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] &&
[[currentFolder nameInContainer] isEqualToString: @"personal"])
continue;
if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] ||
![currentFolder synchronize] ||
[sm validatePermission: SoPerm_DeleteObjects
onObject: currentFolder
inContext: context] ||
[sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: [folders objectAtIndex: count]
onObject: currentFolder
inContext: context])
{
[folders removeObjectAtIndex: count];
@@ -971,7 +1013,6 @@ static BOOL debugOn = NO;
}
count = [folders count]-1;
NSString *operation;
for (fi = 0; fi <= count ; fi++)
{
@@ -1029,6 +1070,7 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
@@ -1053,6 +1095,7 @@ static BOOL debugOn = NO;
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
[[o properties] removeObjectForKey: @"SupportedElements"];
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
}
@@ -1104,17 +1147,18 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
SOGoMailObject *mailObject;
NSMutableArray *a;
NSArray *partKeys;
int p;
NSRange r1, r2;
r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
[a autorelease];
pathToPart = [a lastObject];
[a removeLastObject];
messageName = [a lastObject];
[a removeLastObject];
folderName = [a componentsJoinedByString: @"/"];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
@@ -1263,7 +1307,7 @@ static BOOL debugOn = NO;
- (void) processItemOperations: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId;
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
NSMutableString *s;
NSArray *fetchRequests;
id aFetch;
@@ -1311,20 +1355,21 @@ static BOOL debugOn = NO;
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
SOGoMailObject *mailObject;
NSMutableArray *a;
if ([fileReference length])
{
// fetch attachment
NSRange r1, r2;
NSArray *partKeys;
int p;
r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
[a autorelease];
pathToPart = [a lastObject];
[a removeLastObject];
messageName = [a lastObject];
[a removeLastObject];
folderName = [a componentsJoinedByString: @"/"];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
@@ -1340,13 +1385,13 @@ static BOOL debugOn = NO;
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
for (p = 1; p < [partKeys count]; p++)
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
[s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", [fileReference stringByEscapingURL]];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">mail/%@/%@/%@</FileReference>", [folderName stringByEscapingURL], messageName, pathToPart];
[s appendString: @"<Properties>"];
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
@@ -1376,6 +1421,13 @@ static BOOL debugOn = NO;
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
// The complete item is then returned to the client in a server response.
[context setObject: @"8" forKey: @"MIMETruncation"];
currentCollection = [self collectionFromId: realCollectionId type: folderType];
@@ -1947,7 +1999,7 @@ static BOOL debugOn = NO;
NSMutableString *s;
id collection;
NSData *d;
NSAutoreleasePool *pool;
int i, j, heartbeatInterval, defaultInterval, internalInterval, status;
@@ -1967,6 +2019,9 @@ static BOOL debugOn = NO;
}
else
{
if (heartbeatInterval < internalInterval)
heartbeatInterval = internalInterval;
status = 1;
}
@@ -2006,6 +2061,7 @@ static BOOL debugOn = NO;
// We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
{
pool = [[NSAutoreleasePool alloc] init];
for (j = 0; j < [allFoldersID count]; j++)
{
collectionId = [allFoldersID objectAtIndex: j];
@@ -2030,16 +2086,17 @@ static BOOL debugOn = NO;
[foldersWithChanges addObject: collectionId];
}
}
DESTROY(pool);
if ([foldersWithChanges count])
{
[self logWithFormat: @"Change detected, we push the content."];
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
status = 2;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
sleep(internalInterval);
}
}
@@ -2103,6 +2160,123 @@ static BOOL debugOn = NO;
[theResponse setContent: d];
}
//
//
//
#ifdef HAVE_OPENSSL
- (unsigned int) validateCert: (NSString *) theCert
{
NSData *d;
const unsigned char *data;
X509_STORE_CTX *ctx;
X509_LOOKUP *lookup;
X509_STORE *store;
X509 *cert;
BOOL success;
size_t len;
int rc;
success = NO;
d = [theCert dataByDecodingBase64];
data = (unsigned char *)[d bytes];
len = [d length];
cert = d2i_X509(NULL, &data, len);
if (!cert)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
return 17;
}
store = X509_STORE_new();
OpenSSL_add_all_algorithms();
if (store)
{
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if (lookup)
{
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (lookup)
{
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();
success = YES;
}
}
}
if (!success)
{
if (store)
{
X509_STORE_free(store);
store = NULL;
}
}
ctx = X509_STORE_CTX_new();
if (!ctx)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
return 17;
}
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
X509_STORE_CTX_free(ctx);
return 17;
}
rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
X509_free(cert);
if (rc)
{
return 1;
}
else
{
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
return 17;
}
}
#else
- (unsigned int) validateCert: (NSString *) theCert
{
return 17;
}
#endif
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSMutableString *s;
NSString *cert;
NSData *d;
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
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: @"<ValidateCert xmlns=\"ValidateCert:\">"];
[s appendString: @"<Status>1</Status><Certificate>"];
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
[s appendString: @"</Certificate></ValidateCert>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@@ -2419,39 +2593,100 @@ static BOOL debugOn = NO;
NGMimeMessageParser *parser;
NGMimeMessage *message;
NSException *error;
NSData *data;
NGMutableHashMap *map;
NGMimeMessage *messageToSend;
NGMimeMessageGenerator *generator;
NSMutableData *data;
NSData *new_from_header;
NSDictionary *identity;
NSString *fullName, *email;
const char *bytes;
int i, e, len;
BOOL found_header;
// We get the mail's data
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
// We extract the recipients
parser = [[NGMimeMessageParser alloc] init];
message = [parser parsePartFromData: data];
RELEASE(parser);
map = [NGHashMap hashMapWithDictionary: [message headers]];
identity = [[context activeUser] primaryIdentity];
fullName = [identity objectForKey: @"fullName"];
email = [identity objectForKey: @"email"];
if ([fullName length])
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", [fullName asQPSubjectString: @"utf-8"], email] dataUsingEncoding:NSUTF8StringEncoding];
else
[map setObject: email forKey: @"from"];
new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
bytes = [data bytes];
len = [data length];
i = 0;
found_header = NO;
[messageToSend setBody: [message body]];
// Search for the from-header
while (i < len)
{
if (i == 0 &&
(*bytes == 'f' || *bytes == 'F') &&
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
(*(bytes+4) == ':'))
{
found_header = YES;
break;
}
if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
(*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
(*(bytes+6) == ':'))
{
found_header = YES;
i = i + 2; // \r\n
bytes = bytes + 2;
break;
}
bytes++;
i++;
}
// We search for the first \r\n AFTER the From: header to get the length of the string to replace.
e = i;
while (e < len)
{
if ((*bytes == '\r') && (*(bytes+1) == '\n'))
{
e = e + 2;
break;
}
bytes++;
e++;
}
// Update/Add the From header in the MIMEBody of the SendMail request.
// Any other way to modify the mail body would break s/mime emails.
if (found_header)
{
// Change the From header
[data replaceBytesInRange: NSMakeRange(i, (NSUInteger)(e-i))
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
else
{
// Add a From header
[data replaceBytesInRange: NSMakeRange(0, 0)
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [generator generateMimeFromPart: messageToSend];
error = [self _sendMail: data
recipients: [message allRecipients]
saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)];
@@ -2627,7 +2862,12 @@ static BOOL debugOn = NO;
{
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] mutableCopy] autorelease];
// If there is no References: header, initialize it with In-Reply-To.
if ([mailObject inReplyTo] && ![references count])
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
if ([references count] > 0)
{
// If there are more than ten identifiers listed, we eliminate the second one.
@@ -2943,9 +3183,25 @@ static BOOL debugOn = NO;
options: NSCaseInsensitiveSearch].location == NSNotFound)
{
NSString *value;
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
#if GNUSTEP_BASE_MINOR_VERSION < 21
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: nil];
#else
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
locale: [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects: @"Jan", @"Feb", @"Mar", @"Apr",
@"May", @"Jun", @"Jul", @"Aug",
@"Sep", @"Oct", @"Nov", @"Dec", nil],
@"NSShortMonthNameArray",
[NSArray arrayWithObjects: @"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
@"Fri", @"Sat", nil],
@"NSShortWeekDayNameArray",
nil]];
#endif
s = [NSString stringWithFormat: @"Date: %@\r\n%@", value, [theRequest contentAsString]];
}
else
{
@@ -3011,7 +3267,7 @@ static BOOL debugOn = NO;
[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"];
if (debugOn && [[theResponse content] length])
if (debugOn && [[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.wbxml"] && [[theResponse content] length])
[self logWithFormat: @"EAS - response for device %@: %@", [context objectForKey: @"DeviceId"], [[[NSString alloc] initWithData: [[theResponse content] wbxml2xml] encoding: NSUTF8StringEncoding] autorelease]];
RELEASE(context);
@@ -3035,8 +3291,7 @@ static BOOL debugOn = NO;
return nil;
urlString = [[user domainDefaults] folderInfoURL];
parts = [[urlString componentsSeparatedByString: @"/"]
mutableCopy];
parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy];
[parts autorelease];
if ([parts count] == 5)
{
+150 -33
View File
@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
#import <Foundation/NSCharacterSet.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
@@ -472,18 +473,46 @@ struct GlobalObjectId {
return d;
}
//
//
//
- (BOOL) _sanitinizeNeeded: (NSArray *) theParts
{
NSString *encoding, *charset;
int i;
for (i = 0; i < [theParts count]; i++)
{
encoding = [[theParts objectAtIndex: i ] objectForKey: @"encoding"];
charset = [[[theParts objectAtIndex: i ] objectForKey: @"parameterList"] objectForKey: @"charset"];
if (encoding && [encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame &&
charset && ![charset caseInsensitiveCompare: @"utf-8"] == NSOrderedSame
&& ![charset caseInsensitiveCompare: @"us-ascii"] == NSOrderedSame)
{
return YES;
}
if ([self _sanitinizeNeeded: [[theParts objectAtIndex: i ] objectForKey: @"parts"]])
return YES;
}
return NO;
}
//
//
//
- (NSData *) _preferredBodyDataUsingType: (int) theType
mimeSupport: (int) theMimeSupport
nativeType: (int *) theNativeType
{
NSString *type, *subtype, *encoding;
NSData *d;
BOOL isSMIME, sanitinizeNeeded;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
isSMIME = NO;
d = nil;
// We determine the native type
@@ -494,8 +523,14 @@ struct GlobalObjectId {
else if ([type isEqualToString: @"multipart"])
*theNativeType = 4;
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
{
*theNativeType = 4;
isSMIME = YES;
}
// We get the right part based on the preference
if (theType == 1 || theType == 2)
if ((theType == 1 || theType == 2) && !isSMIME)
{
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
{
@@ -536,12 +571,12 @@ struct GlobalObjectId {
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
}
}
else if (theType == 4)
else if (theType == 4 || isSMIME)
{
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
// We sanitize the content if the content-transfer-encoding is 8bit and charset is not utf-8 or us-ascii.
sanitinizeNeeded = [self _sanitinizeNeeded: [NSArray arrayWithObject: [self bodyStructure]]];
if (sanitinizeNeeded && !isSMIME)
d = [self _sanitizedMIMEMessage];
else
d = [self content];
@@ -572,6 +607,35 @@ struct GlobalObjectId {
}
- (NSString *) _truncateContent: (NSString *) theContent
limit: (int) theLimit
truncated: (int *) wasTruncated
{
if ([theContent length] > theLimit)
{
int i, len;
theContent = [theContent substringToIndex: theLimit];
*wasTruncated = 1;
// We search for the first "space" character starting from the
// end and we truncate the string once more. We do this to avoid
// truncating the content in the middle of a XML entity
len = theLimit-1;
for (i = len; i >= 0; i--)
{
if (isspace([theContent characterAtIndex: i]))
break;
}
return [theContent substringToIndex: i];
}
*wasTruncated = 0;
return theContent;
}
//
//
//
@@ -656,16 +720,19 @@ struct GlobalObjectId {
{
NSData *d, *globalObjId;
NSArray *attachmentKeys;
NSMutableString *s;
uint32_t v;
NSString *p;
id value;
iCalCalendar *calendar;
NSString *p, *subtype;
NSMutableString *s;
id value;
int preferredBodyType, nativeBodyType;
int preferredBodyType, mimeSupport, mimeTruncation, nativeBodyType;
uint32_t v;
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
mimeTruncation = [[context objectForKey: @"MIMETruncation"] intValue];
s = [NSMutableString string];
@@ -862,7 +929,12 @@ struct GlobalObjectId {
else
{
// MesssageClass and ContentClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
if ([subtype isEqualToString: @"signed"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
else if ([subtype isEqualToString: @"pkcs7-mime"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
else
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
}
@@ -876,10 +948,8 @@ struct GlobalObjectId {
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
// Body - namespace 17
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
nativeBodyType = 1;
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
if (calendar && !d)
{
@@ -968,9 +1038,50 @@ struct GlobalObjectId {
AUTORELEASE(content);
content = [content activeSyncRepresentationInContext: context];
len = [content length];
truncated = 0;
len = [content length];
// We handle MIMETruncation
switch (mimeTruncation)
{
case 0:
{
content = @"";
len = 0; truncated = 1;
}
break;
case 1:
content = [self _truncateContent: content limit: 4096 truncated: &truncated];
len = [content length];
break;
case 2:
content = [self _truncateContent: content limit: 5120 truncated: &truncated];
len = [content length];
break;
case 3:
content = [self _truncateContent: content limit: 7168 truncated: &truncated];
len = [content length];
break;
case 4:
content = [self _truncateContent: content limit: 10240 truncated: &truncated];
len = [content length];
break;
case 5:
content = [self _truncateContent: content limit: 20480 truncated: &truncated];
len = [content length];
break;
case 6:
content = [self _truncateContent: content limit: 51200 truncated: &truncated];
len = [content length];
break;
case 7:
content = [self _truncateContent: content limit: 102400 truncated: &truncated];
len = [content length];
break;
case 8:
default:
truncated = 0;
}
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
{
@@ -978,30 +1089,30 @@ struct GlobalObjectId {
[s appendFormat: @"<BodyTruncated xmlns=\"Email:\">%d</BodyTruncated>", truncated];
}
else
{
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
// Set the correct type if client requested text/html but we got text/plain
// Set the correct type if client requested text/html but we got text/plain.
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
if (preferredBodyType == 2 && nativeBodyType == 1)
[s appendString: @"<Type>1</Type>"];
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
[s appendString: @"<Type>4</Type>"];
else
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
[s appendFormat: @"<Truncated>%d</Truncated>", truncated];
[s appendFormat: @"<Preview></Preview>"];
if (!truncated)
{
[s appendFormat: @"<Data>%@</Data>", content];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
}
[s appendFormat: @"<Data>%@</Data>", content];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
[s appendString: @"</Body>"];
}
}
// Attachments -namespace 16
attachmentKeys = [self fetchFileAttachmentKeys];
if ([attachmentKeys count])
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
{
int i;
@@ -1071,15 +1182,21 @@ struct GlobalObjectId {
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
{
id value;
NSString *reference;
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
value = [[self mailHeaders] objectForKey: @"references"];
if ([value isKindOfClass: [NSArray class]])
reference = [[[value objectAtIndex: 0] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] objectAtIndex: 0];
else
reference = [[value componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] objectAtIndex: 0];
if ([reference length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self inReplyTo])
else if ([[self inReplyTo] length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self inReplyTo] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
else if ([self messageId])
else if ([[self messageId] length] > 0)
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self messageId] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
}
+1
View File
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@interface SOGoSyncCacheObject : NSObject
{
@public
id _uid;
id _sequence;
}
+22 -13
View File
@@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSNull.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
static Class NSNullK;
@implementation SOGoSyncCacheObject
+ (void) initialize
{
NSNullK = [NSNull class];
}
- (id) init
{
if ((self = [super init]))
@@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return self;
}
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
{
id o;
o = [[self alloc] init];
[o setUID: theUID];
[o setSequence: theSequence];
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
return [o autorelease];
}
@@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) uid
{
return _uid;
return [_uid description];
}
- (void) setUID: (id) theUID
@@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) sequence
{
return _sequence;
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
}
- (void) setSequence: (id) theSequence
@@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
{
return [[self uid] compare: [theSyncCacheObject uid]];
return [self->_uid compare: theSyncCacheObject->_uid];
}
//
@@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
{
if ([[self sequence] isEqual: [NSNull null]] &&
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] &&
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
return [self compareUID: theSyncCacheObject];
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedDescending;
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedAscending;
// Must check this here, to avoid comparing NSNull objects
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
return [self compareUID: theSyncCacheObject];
return [[self sequence] compare: [theSyncCacheObject sequence]];
return [self->_sequence compare: theSyncCacheObject->_sequence];
}
- (NSString *) description
@@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
@end
+1 -1
View File
@@ -58,7 +58,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", delta];
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
return s;
+3 -1
View File
@@ -41,7 +41,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context;
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context;
- (void) changeParticipationStatus: (NSDictionary *) theValues
inContext: (WOContext *) context
component: (id) component;
@end
#endif
+337 -37
View File
@@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
@@ -45,11 +47,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/NSCalendarDate+NGCards.h>
#import <NGCards/NSString+NGCards.h>
#import <NGCards/iCalCalendar.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/WORequest+SOGo.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentObject.h>
#include "iCalAlarm+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h"
@@ -98,10 +106,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else if ([self created])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]];
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([self startDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<StartTime xmlns=\"Calendar:\">%@</StartTime>",
[[[self startDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -114,7 +125,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([self endDate])
{
if ([self isAllDay])
if ([self isAllDay] && !tz)
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>",
[[[self endDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
@@ -124,20 +135,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<EndTime xmlns=\"Calendar:\">%@</EndTime>", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
// Timezone
tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone];
if (!tz)
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
if (![self recurrenceId])
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
// Organizer and other invitations related properties
if ((organizer = [self organizer]))
{
meetingStatus = 1; // meeting and the user is the meeting organizer.
o = [organizer rfc822Email];
if ([o length])
if (![self recurrenceId] && [o length])
{
[s appendFormat: @"<Organizer_Email xmlns=\"Calendar:\">%@</Organizer_Email>", o];
@@ -226,9 +235,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[s appendFormat: @"<Importance xmlns=\"Calendar:\">%d</Importance>", v];
// UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx
if ([[self uid] length])
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [self uid]];
if (![self recurrenceId] && [[self uid] length])
[s appendFormat: @"<UID xmlns=\"Calendar:\">%@</UID>", [[self uid] activeSyncRepresentationInContext: context]];
// Recurrence rules
if ([self isRecurrent])
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
// Comment
o = [self comment];
//if (![self recurrenceId] && [o length])
if ([o length])
{
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
o = [o activeSyncRepresentationInContext: context];
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
{
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
}
else
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
}
// Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"])
v = 2;
@@ -251,7 +288,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"</Categories>"];
}
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
// TODO: improve this to handle more alarm types
if ([self hasAlarms])
@@ -262,36 +298,79 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: [alarm activeSyncRepresentationInContext: context]];
}
// Recurrence rules
// Exceptions
if ([self isRecurrent])
{
[s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]];
}
NSMutableArray *components, *exdates;
iCalEvent *current_component;
NSString *recurrence_id;
// Comment
o = [self comment];
if ([o length])
{
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
o = [o activeSyncRepresentationInContext: context];
unsigned int count, max, i;
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
components = [NSMutableArray arrayWithArray: [[self parent] events]];
max = [components count];
if (max > 1 || [self hasExceptionDates])
{
[s appendFormat: @"<Body xmlns=\"Calendar:\">%@</Body>", o];
[s appendString: @"<BodyTruncated xmlns=\"Calendar:\">0</BodyTruncated>"];
exdates = [NSMutableArray arrayWithArray: [self exceptionDates]];
[s appendString: @"<Exceptions xmlns=\"Calendar:\">"];
for (count = 1; count < max; count++)
{
current_component = [components objectAtIndex: count] ;
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[current_component recurrenceId] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[current_component recurrenceId]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[current_component recurrenceId]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendString: @"<Exception>"];
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendFormat: @"%@", [current_component activeSyncRepresentationInContext: context]];
[s appendString: @"</Exception>"];
}
for (i = 0; i < [exdates count]; i++)
{
[s appendString: @"<Exception>"];
[s appendString: @"<Exception_Deleted>1</Exception_Deleted>"];
if ([self isAllDay])
{
recurrence_id = [NSString stringWithFormat: @"%@",
[[[[exdates objectAtIndex: i] asCalendarDate] dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: -[userTimeZone secondsFromGMTForDate:
[[exdates objectAtIndex: i] asCalendarDate]]]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
else
{
recurrence_id = [NSString stringWithFormat: @"%@", [[[exdates objectAtIndex: i] asCalendarDate]
activeSyncRepresentationWithoutSeparatorsInContext: context]];
}
[s appendFormat: @"<Exception_StartTime xmlns=\"Calendar:\">%@</Exception_StartTime>", recurrence_id];
[s appendString: @"</Exception>"];
}
[s appendString: @"</Exceptions>"];
}
else
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
}
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
if (![self recurrenceId])
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", 1];
return s;
}
@@ -337,13 +416,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) takeActiveSyncValues: (NSDictionary *) theValues
inContext: (WOContext *) context
{
iCalDateTime *start, *end;
iCalDateTime *start, *end; //, *oldstart;
NSCalendarDate *oldstart;
NSTimeZone *userTimeZone;
iCalTimeZone *tz;
id o;
int deltasecs;
BOOL isAllDay;
NSMutableArray *occurences;
occurences = [NSMutableArray arrayWithArray: [[self parent] events]];
if ((o = [theValues objectForKey: @"UID"]))
[self setUid: o];
@@ -356,6 +441,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
isAllDay = YES;
}
else if ([occurences count] && !([theValues objectForKey: @"AllDayEvent"]))
{
// If the occurence has no AllDay tag use it from master.
isAllDay = [[occurences objectAtIndex: 0] isAllDay];
}
//
// 0- free, 1- tentative, 2- busy and 3- out of office
@@ -422,6 +512,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
o = [o calendarDate];
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
oldstart = [start dateTime];
[start setTimeZone: tz];
if (isAllDay)
@@ -433,6 +524,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[start setDateTime: o];
}
// Calculate delta if start date has been changed.
if (oldstart)
deltasecs = [[start dateTime ] timeIntervalSinceDate: oldstart] * -1;
else
deltasecs = 0;
}
if ((o = [theValues objectForKey: @"EndTime"]))
@@ -491,6 +588,177 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
RELEASE(rule);
[rule takeActiveSyncValues: o inContext: context];
// Exceptions
if ((o = [theValues objectForKey: @"Exceptions"]) && [o isKindOfClass: [NSArray class]])
{
NSCalendarDate *recurrenceId, *adjustedRecurrenceId, *currentId;
NSMutableArray *exdates, *exceptionstouched;
iCalEvent *currentOccurence;
NSDictionary *exception;
unsigned int i, count, max;
[self removeAllExceptionDates];
exdates = [NSMutableArray array];
exceptionstouched = [NSMutableArray array];
for (i = 0; i < [o count]; i++)
{
exception = [o objectAtIndex: i];
if (![[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// When moving the calendar entry (i.e. changing the startDate) iOS and Android sometimes send a value for
// Exception_StartTime which is not what we expect.
// With this we make sure the recurrenceId is always correct.
//
// iOS problem - If the master is a no-allday-event but the exception is, an invalid Exception_StartTime is in the request.
// e.g. it sends 20150409T040000Z instead of 20150409T060000Z;
// This might be a special case since iPhone doesn't allow an allday-exception on a non-allday-event.
if (!([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay] minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
recurrenceId = [recurrenceId hour:[[start dateTime] hourOfDay] minute:[[start dateTime] minuteOfHour]];
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
adjustedRecurrenceId = [recurrenceId dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0 seconds: deltasecs];
// search for an existing occurence and update it
max = [occurences count];
currentOccurence = nil;
count = 1;
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
if ([currentId compare: adjustedRecurrenceId] == NSOrderedSame)
{
[exceptionstouched addObject: adjustedRecurrenceId];
[currentOccurence takeActiveSyncValues: exception inContext: context];
break;
}
count++;
currentOccurence = nil;
}
// Create a new occurence if we found none to update.
if (!currentOccurence)
{
iCalDateTime *recid;
currentOccurence = [self mutableCopy];
[currentOccurence removeAllRecurrenceRules];
[currentOccurence removeAllExceptionRules];
[currentOccurence removeAllExceptionDates];
[[self parent] addToEvents: currentOccurence];
[currentOccurence takeActiveSyncValues: exception inContext: context];
recid = (iCalDateTime *)[currentOccurence uniqueChildWithTag: @"recurrence-id"];
if (isAllDay)
[recid setDate: adjustedRecurrenceId];
else
[recid setDateTime: adjustedRecurrenceId];
[exceptionstouched addObject: [recid dateTime]];
}
}
else if ([[exception objectForKey: @"Exception_Deleted"] intValue])
{
recurrenceId = [[exception objectForKey: @"Exception_StartTime"] asCalendarDate];
if (isAllDay)
recurrenceId = [recurrenceId dateByAddingYears: 0 months: 0
days: 0 hours: 0 minutes: 0
seconds: [userTimeZone secondsFromGMTForDate:recurrenceId]];
// We add only valid exception dates.
if ([self doesOccurOnDate: recurrenceId] &&
([[start dateTime] compare: [[start dateTime] hour:[recurrenceId hourOfDay]
minute:[recurrenceId minuteOfHour]]] == NSOrderedSame))
{
// We need to store the recurrenceIds and exception dates adjusted by deltasecs.
// This ensures that the adjustment in SOGoCalendarComponent->updateComponent->_updateRecurrenceIDsWithEvent gives the correct dates.
[exdates addObject: [recurrenceId dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: deltasecs]];
}
}
}
// Update exception dates in master event.
max = [exdates count];
if (max > 0)
{
for (count = 0; count < max; count++)
{
if (([exceptionstouched indexOfObject: [exdates objectAtIndex: count]] == NSNotFound))
[self addToExceptionDates: [exdates objectAtIndex: count]];
}
}
// Remove all exceptions included in the request.
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count] ;
currentId = [currentOccurence recurrenceId];
// Delete all occurences which are not touched (modified/added).
if (([exceptionstouched indexOfObject: currentId] == NSNotFound))
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
else if ([self isRecurrent])
{
// We have no exceptions in request but there is a recurrence rule.
// Remove all excpetions and exception dates.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
}
else if ([self isRecurrent])
{
// We have no recurrence rule in request.
// Remove all exceptions, exception dates and recurrence rules.
iCalEvent *currentOccurence;
unsigned int count, max;
[self removeAllRecurrenceRules];
[self removeAllExceptionRules];
[self removeAllExceptionDates];
max = [occurences count];
count = 1; // skip the master event
while (count < max)
{
currentOccurence = [occurences objectAtIndex: count];
[[[self parent] children] removeObject: currentOccurence];
count++;
}
}
// Organizer - we don't touch the value unless we're the organizer
@@ -498,7 +766,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o]))
{
iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: o];
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
@@ -546,7 +814,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
// Windows phones sens sometimes an empty Attendees tag.
// We check it's an array before processing it.
if ((o = [theValues objectForKey: @"Attendees"])&& [o isKindOfClass: [NSArray class]])
if ((o = [theValues objectForKey: @"Attendees"]) && [o isKindOfClass: [NSArray class]])
{
NSMutableArray *attendees;
NSDictionary *attendee;
@@ -595,4 +863,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
- (void) changeParticipationStatus: (NSDictionary *) theValues
inContext: (WOContext *) context
component: (id) component
{
NSString *status;
id o;
// See: https://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
//
// 0 == Free
// 1 == Tentative
// 2 == Busy
// 3 == Out of Office
// 4 == Working elsehwere
//
if ((o = [theValues objectForKey: @"BusyStatus"]))
{
// We translate >= 2 into ACCEPTED
if ([o intValue] == 0)
status = @"NEEDS-ACTION";
else if ([o intValue] >= 2)
status = @"ACCEPTED";
else
status = @"TENTATIVE";
// There's no delegate in EAS
[(SOGoAppointmentObject *) component changeParticipationStatus: status
withDelegate: nil
alarm: nil];
}
}
@end
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Simple reccurrence rule of type "Monthly"
type = 2;
[s appendFormat: @"<Recurrence_DayOfMonth xmlns=\"Calendar:\">%d</Recurrence_DayOfMonth>",
[[[self parent] startDate] dayOfMonth]];
(int)[[[self parent] startDate] dayOfMonth]];
}
}
else if ([self frequency] == iCalRecurrenceFrequenceYearly)
+1 -1
View File
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", (int)[o length]];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
+5 -4
View File
@@ -57,8 +57,6 @@ ProxyPreserveHost On
# Allow from your-cas-host-addr
# </Proxy>
ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
# Enable to use Microsoft ActiveSync support
# Note that you MUST have many sogod workers to use ActiveSync.
# See the SOGo Installation and Configuration guide for more details.
@@ -67,11 +65,13 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
# http://127.0.0.1:20000/SOGo/Microsoft-Server-ActiveSync \
# retry=60 connectiontimeout=5 timeout=360
ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<Proxy http://127.0.0.1:20000/SOGo>
## adjust the following to your configuration
RequestHeader set "x-webobjects-server-port" "443"
RequestHeader set "x-webobjects-server-name" "yourhostname"
RequestHeader set "x-webobjects-server-url" "https://yourhostname"
# RequestHeader set "x-webobjects-server-name" "yourhostname"
# RequestHeader set "x-webobjects-server-url" "https://yourhostname"
## When using proxy-side autentication, you need to uncomment and
## adjust the following line:
@@ -90,5 +90,6 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
<IfModule rewrite_module>
RewriteEngine On
RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301]
RewriteRule ^/.well-known/carddav/?$ /SOGo/dav [R=301]
</IfModule>
+4145
View File
File diff suppressed because it is too large. Load diff
+33 -6
View File
@@ -1,5 +1,7 @@
Installation and Configuration Guide
====================================
:toc: left
:icons: font
////
@@ -54,7 +56,7 @@ at http://www.sogo.nu/
Architecture and Compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
image::images/architecture.png[System Architecture]
image::images/architecture.png[System Architecture, 400, 964]
Standard protocols such as CalDAV, CardDAV, GroupDAV, HTTP, IMAP and
SMTP are used to communicate with the SOGo platform or its
@@ -170,7 +172,7 @@ supported by SOGo:
* Red Hat Enterprise Linux (RHEL) Server 5, 6 and 7
* Community ENTerprise Operating System (CentOS) 5, 6 and 7
* Debian GNU/Linux 6.0 (Squeeze) to 8.0 (Jessie)
* Ubuntu 10.04 (Lucid) to 14.04 (Trusty)
* Ubuntu 12.04 (Precise) to 14.04 (Trusty)
Make sure the required components are started automatically at boot time
and that they are running before proceeding with the SOGo configuration.
@@ -179,7 +181,7 @@ standard distribution. For example, if you are using Red Hat Enterprise
Linux 5, you have to be subscribed to the Red Hat Network before
continuing with the SOGo software installation.
This document covers the installation of SOGo under RHEL 6.
NOTE: This document covers the installation of SOGo under RHEL 6.
For installation instructions on Debian and Ubuntu, please refer
directly to the SOGo website at http://www.sogo.nu/.
@@ -294,7 +296,7 @@ associated to a domain is limited to access only the users data from the
same domain. Consequently, the configuration parameters of SOGo are
defined on three levels:
image::images/preferences-hierarchy.png[Preferences Hierarchy]
image::images/preferences-hierarchy.png[Preferences Hierarchy, 400, 400]
Each level inherits the preferences of the parent level. Therefore,
domain preferences define the defaults values of the user preferences,
@@ -1125,7 +1127,7 @@ fax number, one could map it to the _facsimiletelephonenumber_ attribute
like this:
----
mapping = \{
mapping = {
  facsimiletelephonenumber = ("fax", "facsimiletelephonenumber");
};
----
@@ -1628,6 +1630,16 @@ Defaults to `Trash` when unset.
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
For example: `INBOX/Trash`.
|U |SOGoJunkFolderName
|Parameter used to set the IMAP folder name used to store junk
messages.
Defaults to `Junk` when unset.
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
For example: `INBOX/Junk`. Also see the SOGoMailJunkSettings for
more options regarding junk/not-junk actions.
|D |SOGoIMAPCASServiceName
|Parameter used to set the CAS service name (URL) of the imap service.
This is useful if SOGo is connecting to the IMAP service through a
@@ -2082,7 +2094,21 @@ Defaults to `%{FolderName} (%{UserName} <%{Email}>)` when unset.
the template named `UIxAdditionalPreferences.wox`. This template should
be put under `~sogo/GNUstep/Library/SOGo/Templates/PreferencesUI/`.
Defaults to `NO` when unset.
|D |SOGoMailJunkSettings
|Parameter used to enable email junk settings. The value is a dictionary
and the follow keys are supported: `vendor` (which must be set to "generic"
for now), `junkEmailAddress` which sets the email address to whom SOGo will
send junk mails to, `notJunkEmailAddress` which sets the email address to
whome SOGo will send non-junk mails to and `limit`, which is an integer value
and sets the maximum number of mails that will be attached to a
junk/not junk report sent by SOGo. Example: `SOGoMailJunkSettings = {
vendor = "generic"; junkEmailAddress = "spam@foo.com";
notJunkEmailAddress = "ham@foo.com"; limit = 10;
};`
|D |SOGoMailKeepDraftsAfterSend
|Parameter used to keep mails in the drafts folder once they have been
sent by SOGo. Defaults to `NO` when unset.
|=======================================================================
SOGo Configuration Summary
@@ -2111,6 +2137,7 @@ like this:
SOGoDraftsFolderName = Drafts;
SOGoSentFolderName = Sent;
SOGoTrashFolderName = Trash;
SOGoJunkFolderName = Junk;
SOGoMailingMechanism = smtp;
SOGoSMTPServer = 127.0.0.1;
SOGoUserSources = (
@@ -388,7 +388,7 @@ Run the following commands as root: 
----
samba-tool domain provision --realm=example.com \
--domain=OPENCHANGE \
--domain=EXAMPLE \
--adminpass='%1OpenChange' \
--server-role='domain controller'
@@ -818,9 +818,8 @@ can be ignored for now. This feature is currently not supported.
remove any data associated with the user from the SOGo server and
recreate a Microsoft Outlook profile.
To remove any data associated to a user, use
the `openchange_user_cleanup` script distributed with SOGo. The script
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
RHEL).
the `openchange_user_cleanup` script distributed with OpenChange. The script
can be found in `/usr/share/openchange/`.
To reset a user, run the script as root:
`openchange_user_cleanup username`. See the usage output for additional options.
* The "Out of Office Assistant" will not currently work. This feature
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 2.3.2 - September 2015</releaseinfo>
<subtitle>for version 2.3.</subtitle>
<date>2015-09-16</date>
<releaseinfo>Version 2.3.9 - March 2016</releaseinfo>
<subtitle>for version 2.3.9</subtitle>
<date>2016-03-16</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-2015 Inverse inc.
Copyright (C) 2008-2016 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: 2.3.2
:release_version: 2.3.9
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
+1 -1
View File
@@ -267,7 +267,7 @@
NSObject <DOMNodeList> *list;
NSObject <DOMNode> *valueNode;
NSArray *elements;
NSString *property, *match;
NSString *property=nil, *match=nil;
list = [searchElement getElementsByTagName: @"prop"];
if ([list length])
+150
View File
@@ -1,3 +1,153 @@
2.3.9 (2016-03-16)
------------------
New features
- you can now limit the file upload size using the WOMaxUploadSize
configuration parameter (integer value in kilobytes) (#3510, #3135)
Enhancements
- allow resources to prevent invitations (#3410)
- now support EAS MIME truncation
- [web] added Lithuanan (lt) translation - thanks to Mantas Liobė
Bug fixes
- allow EAS attachments get on 2nd-level mailboxes (#3505)
- fixed EAS bday shift (#3518)
- prefer SOGoRefreshViewCheck to SOGoMailMessageCheck (#3465)
- properly unfold long mail headers (#3152)
2.3.8 (2016-02-05)
------------------
Enhancements
- updated CKEditor to version 4.5.7
Bug fixes
- correctly encode filename of attachments over EAS (#3491)
- correctly encode square brackets for IMAP folder names (#3321)
- add shared/public namespaces in the list or returned folders
2.3.7 (2016-01-25)
------------------
New features
- new junk/not junk capability with generic SMTP integration
Enhancements
- newly created folders using EAS are always sync'ed by default (#3454)
- added Croatian (hr_HR) translation - thanks to Jens Riecken
Bug fixes
- now always generate invitation updates when using EAS
- rewrote the string sanitization to be 32-bit Unicode safe
- do not try to decode non-wbxml responses for debug output (#3444)
2.3.6 (2016-01-18)
------------------
New features
- now able to sync only default mail folders when using EAS
Enhancements
- unit testing for RTFHandler
- JUnit output for sogo-tests
Bug fixes
- don't unescape twice mail folder names (#3423)
- don't consider mobile Outlook EAS clients as DAV ones (#3431)
- we now follow 301 redirects when fetching ICS calendars
- when deleting an event using EAS, properly invoke the auto-scheduling code
- do not include failure attachments (really long filenames)
- fix encoding of email subjects with non-ASCII characters
- fix appointment notification mails using SOGoEnableDomainBasedUID configuration
- fix shifts in event times on Outlook
2.3.5 (2016-01-05)
------------------
Enhancements
- return an error to openchange if mail message delivery fails
- return the requested elements on complex requests from Outlook when downloading changes
- user sources can be loaded dynamically
- unify user sources API
- updated Russian translation (#3383)
Bug fixes
- properly compute the last week number for the year (#1010)
- share calendar, tasks and contacts folders in Outlook 2013 with editor permissions
- priorize filename in Content-Disposition against name in Content-Type to get the filename of an attachment in mail
- request all contacts when there is no filter in Contacts menu in Webmail
- personal contacts working properly on Outlook
- fixes on RTF parsing used by event/contact description and mail as RTF to read non-ASCII characters:
better parsing of font table, when using a font, switch to its character set, correct parsing of
escaped characters and Unicode character command word support for unicode characters greater than 32767
- no crash resolving recipients after reconnecting LDAP connection
- avoid creation of phantom contacts in SOGo from distribution list synced from Outlook.
- accepted & updated event names are now shown correctly in Outlook
- provide safe guards in mail and calendar to avoid exceptions while syncing
2.3.4 (2015-12-15)
------------------
New features
- initial support for EAS calendar exceptions
Enhancements
- limit the maximum width of toolbar buttons (#3381)
- updated CKEditor to version 4.5.6
Bug fixes
- JavaScript exception when printing events from calendars with no assigned color (#3203)
- EAS fix for wrong charset being used (#3392)
- EAS fix on qp-encoded subjects (#3390)
- correctly handle all-day event exceptions when the master event changes
- prevent characters in calendar component UID causing issues during import process
- avoid duplicating attendees when accepting event using a different identity over CalDAV
2.3.3a (2015-11-18)
-------------------
Bug fixes
- expanded mail folders list is not saved (#3386)
- cleanup translations
2.3.3 (2015-11-11)
------------------
New features
- initial S/MIME support for EAS (#3327)
- now possible to choose which folders to sync over EAS
Enhancements
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
- support for ghosted elements on contacts over EAS
- added Macedonian (mk_MK) translation - thanks to Miroslav Jovanovic
- added Portuguese (pt) translation - thanks to Eduardo Crispim
Bug fixes
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
- correctly handle the References header over EAS (#3365)
- make sure English is always used when generating Date headers using EAS (#3356)
- don't escape quoted strings during versit generation
- we now return all cards when we receive an empty addressbook-query REPORT
- avoid crash when replying to a mail with no recipients (#3359)
- inline images sent from SOGo webmail are not displayed in Mozilla Thunderbird (#3271)
- prevent postal address showing on single line over EAS (#2614)
- display missing events when printing working hours only
- fix corner case making server crash when syncing hard deleted messages when clear offline items was set up (Zentyal)
- avoid infinite Outlook client loops trying to set read flag when it is already set (Zentyal)
- avoid crashing when calendar metadata is missing in the cache (Zentyal)
- fix recurrence pattern event corner case created by Mozilla Thunderbird which made server crash (Zentyal)
- fix corner case that removes attachments on sending messages from Outlook (Zentyal)
- freebusy on web interface works again in multidomain environments (Zentyal)
- fix double creation of folders in Outlook when the folder name starts with a digit (Zentyal)
- avoid crashing Outlook after setting a custom view in a calendar folder (Zentyal)
- handle emails having an attachment as their content
- fixed JavaScript syntax error in attendees editor
- fixed wrong comparison of meta vs. META tag in HTML mails
- fixed popup menu position when moved to the left (#3381)
- fixed dialog position when at the bottom of the window (#2646, #3378)
2.3.2 (2015-09-16)
------------------
+2 -1
View File
@@ -145,7 +145,8 @@ $(DBMSGREADER_TOOL)_LIB_DIRS += \
-L../SoObjects/SOGo/SOGo.framework/sogo -lSOGo \
-L../SOPE/GDLContentStore/obj/ -lGDLContentStore \
-L../SOPE/NGCards/obj/ -lNGCards \
-lNGObjWeb
-lNGObjWeb \
$(LIBMAPI_LIBS)
TEST_TOOL_NAME += $(PLREADER_TOOL) $(DBMSGREADER_TOOL)
+2 -3
View File
@@ -24,6 +24,7 @@
#define MAPISTORECALENDARWRAPPER_H
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTimeZone.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import "MAPIStoreObjectProxy.h"
@@ -42,7 +43,7 @@
iCalCalendar *calendar;
iCalEvent *firstEvent;
iCalEvent *event;
NSTimeZone *timeZone;
iCalTimeZone *timeZone;
SOGoUser *user;
NSString *senderEmail;
NSData *globalObjectId;
@@ -57,12 +58,10 @@
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
- (id) initWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo;
/* getters */
+49 -106
View File
@@ -37,7 +37,9 @@
#import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTrigger.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/NSString+NGCards.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
@@ -80,7 +82,6 @@ static NSCharacterSet *hexCharacterSet = nil;
+ (id) wrapperWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
{
MAPIStoreAppointmentWrapper *wrapper;
@@ -88,7 +89,6 @@ static NSCharacterSet *hexCharacterSet = nil;
wrapper = [[self alloc] initWithICalEvent: newEvent
andUser: newUser
andSenderEmail: newSenderEmail
inTimeZone: newTimeZone
withConnectionInfo: newConnInfo];
[wrapper autorelease];
@@ -182,10 +182,10 @@ static NSCharacterSet *hexCharacterSet = nil;
- (id) initWithICalEvent: (iCalEvent *) newEvent
andUser: (SOGoUser *) newUser
andSenderEmail: (NSString *) newSenderEmail
inTimeZone: (NSTimeZone *) newTimeZone
withConnectionInfo: (struct mapistore_connection_info *) newConnInfo
{
NSArray *events;
iCalTimeZone *tz;
if ((self = [self init]))
{
@@ -194,9 +194,20 @@ static NSCharacterSet *hexCharacterSet = nil;
event = newEvent;
events = [calendar events];
firstEvent = [events objectAtIndex: 0];
ASSIGN (timeZone, newTimeZone);
ASSIGN (user, newUser);
ASSIGN (senderEmail, newSenderEmail);
/* If newEvent comes from the client, we set its time zone in
updateFromMAPIProperties. If it is not present, we use the
time zone of the user */
tz = (iCalTimeZone *) [calendar firstChildWithTag: @"vtimezone"];
if (!tz)
{
tz = [iCalTimeZone timeZoneForName: [[[user userDefaults] timeZone] name]];
if (!tz)
[self logWithFormat: @"no time zone could be set"];
}
ASSIGN (timeZone, tz);
[self _setupITIPContext];
}
@@ -266,7 +277,7 @@ static NSCharacterSet *hexCharacterSet = nil;
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
{
@@ -367,7 +378,7 @@ static NSCharacterSet *hexCharacterSet = nil;
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
{
@@ -721,22 +732,15 @@ static NSCharacterSet *hexCharacterSet = nil;
inMemCtx: (TALLOC_CTX *) memCtx
{
NSCalendarDate *dateValue;
NSInteger offset;
// if ([event isRecurrent])
// dateValue = [event firstRecurrenceStartDate];
// else
dateValue = [event startDate];
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -749,22 +753,14 @@ static NSCharacterSet *hexCharacterSet = nil;
exceptions, where it is the normal start date for the day of the
exception. */
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [event recurrenceId];
if (!dateValue)
dateValue = [event startDate];
[dateValue setTimeZone: timeZone];
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -772,19 +768,12 @@ static NSCharacterSet *hexCharacterSet = nil;
inMemCtx: (TALLOC_CTX *) memCtx
{
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [firstEvent startDate];
if ([firstEvent isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -804,8 +793,8 @@ static NSCharacterSet *hexCharacterSet = nil;
month: [start monthOfYear]
day: [start dayOfMonth]
hour: 0 minute: 0 second: 0
timeZone: timeZone];
[dateValue setTimeZone: utcTZ];
timeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
@@ -829,7 +818,7 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [event startDate];
offset = [event durationAsTimeInterval];
if ([event isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
@@ -847,15 +836,14 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [event recurrenceId];
if (!dateValue)
dateValue = [event startDate];
[dateValue setTimeZone: timeZone];
offset = [firstEvent durationAsTimeInterval];
if ([firstEvent isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
*data = [dateValue asFileTimeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
@@ -871,7 +859,7 @@ static NSCharacterSet *hexCharacterSet = nil;
dateValue = [firstEvent startDate];
offset = [firstEvent durationAsTimeInterval];
if ([event isAllDay])
offset -= [timeZone secondsFromGMTForDate: dateValue];
offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
@@ -885,23 +873,14 @@ static NSCharacterSet *hexCharacterSet = nil;
{
enum mapistore_error rc;
NSCalendarDate *dateValue;
NSInteger offset;
iCalRecurrenceRule *rrule;
if ([event isRecurrent])
{
rrule = [[event recurrenceRules] objectAtIndex: 0];
dateValue = [rrule untilDate];
if (dateValue)
{
if ([event isAllDay])
offset = -[timeZone secondsFromGMTForDate: dateValue];
else
offset = 0;
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
if (dateValue && [event isAllDay])
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
else
dateValue = [NSCalendarDate dateWithYear: 4500 month: 8 day: 31
hour: 23 minute: 59 second: 00
@@ -932,7 +911,7 @@ static NSCharacterSet *hexCharacterSet = nil;
if (contactInfos)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, username);
entryId = MAPIStoreInternalEntryId (connInfo, username);
}
else
entryId = MAPIStoreExternalEntryId (cn, email);
@@ -1338,21 +1317,14 @@ static NSCharacterSet *hexCharacterSet = nil;
{
enum mapistore_error rc;
NSCalendarDate *dateValue;
NSInteger offset;
dateValue = [event recurrenceId];
if (dateValue)
{
rc = MAPISTORE_SUCCESS;
if ([event isAllDay])
{
offset = -[timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
}
[dateValue setTimeZone: utcTZ];
dateValue = [timeZone shiftedCalendarDateForDate: dateValue];
*data = [dateValue asFileTimeInMemCtx: memCtx];
}
else
@@ -1377,7 +1349,6 @@ static NSCharacterSet *hexCharacterSet = nil;
iCalEventChanges *changes;
NSArray *changedProperties;
NSCalendarDate *dateValue;
NSInteger offset;
changes = [iCalEventChanges changesFromEvent: event toEvent: exceptionEvent];
@@ -1385,28 +1356,17 @@ static NSCharacterSet *hexCharacterSet = nil;
memset (extendedException, 0, sizeof (struct ExtendedException));
extendedException->ChangeHighlight.Size = sizeof (uint32_t);
dateValue = [exceptionEvent startDate];
offset = [timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent startDate]];
exceptionInfo->StartDateTime = [dateValue asMinutesSince1601];
extendedException->ChangeHighlight.Value = BIT_CH_START;
extendedException->StartDateTime = exceptionInfo->StartDateTime;
dateValue = [exceptionEvent endDate];
offset = [timeZone secondsFromGMTForDate: dateValue];
dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent endDate]];
exceptionInfo->EndDateTime = [dateValue asMinutesSince1601];
extendedException->ChangeHighlight.Value |= BIT_CH_END;
extendedException->EndDateTime = exceptionInfo->EndDateTime;
dateValue = [[exceptionEvent recurrenceId]
dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: offset];
dateValue = [timeZone computedDateForDate: [exceptionEvent recurrenceId]];
exceptionInfo->OriginalStartDate = [dateValue asMinutesSince1601];
extendedException->OriginalStartDate = exceptionInfo->OriginalStartDate;
@@ -1464,7 +1424,6 @@ static NSCharacterSet *hexCharacterSet = nil;
arp = talloc_zero (NULL, struct AppointmentRecurrencePattern);
[rule fillRecurrencePattern: &arp->RecurrencePattern
withEvent: event
inTimeZone: timeZone
inMemCtx: arp];
arp->ReaderVersion2 = 0x00003006;
arp->WriterVersion2 = 0x00003008; /* 0x3008 for compatibility with
@@ -1475,7 +1434,7 @@ static NSCharacterSet *hexCharacterSet = nil;
fields are relative to midnight of those days ([MS-OXOCAL] 2.2.1.44.5),
so no time zone adjustment is needed */
if (![event isAllDay])
[firstStartDate setTimeZone: timeZone];
firstStartDate = [timeZone computedDateForDate: firstStartDate];
startMinutes = ([firstStartDate hourOfDay] * 60
+ [firstStartDate minuteOfHour]);
arp->StartTimeOffset = startMinutes;
@@ -1701,15 +1660,16 @@ ReservedBlockEE2Size: 00 00 00 00
fromDate: (NSCalendarDate *) instanceDate;
{
uint16_t year;
NSCalendarDate *dateValue;
if (instanceDate)
{
[instanceDate setTimeZone: timeZone];
year = [instanceDate yearOfCommonEra];
dateValue = [timeZone computedDateForDate: instanceDate];
year = [dateValue yearOfCommonEra];
newGlobalId->YH = year >> 8;
newGlobalId->YL = year & 0xff;
newGlobalId->Month = [instanceDate monthOfYear];
newGlobalId->D = [instanceDate dayOfMonth];
newGlobalId->Month = [dateValue monthOfYear];
newGlobalId->D = [dateValue dayOfMonth];
}
}
@@ -1974,7 +1934,6 @@ ReservedBlockEE2Size: 00 00 00 00
if (alarm)
{
alarmDate = [alarm nextAlarmDate];
[alarmDate setTimeZone: utcTZ];
*data = [alarmDate asFileTimeInMemCtx: memCtx];
}
else
@@ -2036,8 +1995,7 @@ ReservedBlockEE2Size: 00 00 00 00
enum mapistore_error rc;
NSString *tzid;
tzid = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"]
value: 0 ofAttribute: @"tzid"];
tzid = [timeZone tzId];
if ([tzid length] > 0)
{
*data = [tzid asUnicodeInMemCtx: memCtx];
@@ -2053,16 +2011,9 @@ ReservedBlockEE2Size: 00 00 00 00
inMemCtx: (TALLOC_CTX *) memCtx
{
enum mapistore_error rc;
iCalTimeZone *icalTZ;
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
if (icalTZ)
{
*data = [icalTZ asTimeZoneStructInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
rc = MAPISTORE_ERR_NOT_FOUND;
*data = [timeZone asTimeZoneStructInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
return rc;
}
@@ -2071,24 +2022,16 @@ ReservedBlockEE2Size: 00 00 00 00
inMemCtx: (TALLOC_CTX *) memCtx
{
enum mapistore_error rc;
iCalTimeZone *icalTZ;
/* [MS-OXOCAL] 3.1.5.5.1: This property is used in floating (all-day) events,
specified in floating time, to convert the start date from UTC to the user's
time zone */
if ([event isAllDay])
icalTZ = [iCalTimeZone timeZoneForName: [timeZone timeZoneName]];
else if ([event isRecurrent])
icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone];
else
icalTZ = nil;
if (icalTZ)
if ([event isAllDay] | [event isRecurrent])
{
/* [MS-OXOCAL] 2.2.1.42: This property can only have the E flag set in the
TimeZoneDefinition struct */
*data = [icalTZ asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
inMemCtx: memCtx];
*data = [timeZone asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG
inMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
+3
View File
@@ -46,6 +46,9 @@
forURL: (NSURL *) server
forceRenew: (BOOL) renew;
- (NSString *) passwordInContext: (WOContext *) context;
@end
#endif /* MAPISTOREAUTHENTICATOR_H */
+4
View File
@@ -72,4 +72,8 @@
return imapPassword;
}
- (NSString *) passwordInContext: (WOContext *) context
{
return password;
}
@end
@@ -58,7 +58,6 @@
wrapperWithICalEvent: [newContainer event]
andUser: [userContext sogoUser]
andSenderEmail: nil
inTimeZone: [userContext timeZone]
withConnectionInfo: [context connectionInfo]];
[self addProxy: appointmentWrapper];
}
+14 -3
View File
@@ -77,6 +77,17 @@
- (NSArray *) rolesForExchangeRights: (uint32_t) rights
{
/* Limitations
Following rights are not supported by SOGo specifically:
- DeleteOwned : Delete only own objects
- EditOwned : Edit only own objects
- CreateSubfolders: No calendar subfolders
- FolderOwner: No sharing folder ownership?
- FolderContact: No support to store this information
- FolderVisible: It is inferred by other rights when extracting
*/
NSMutableArray *roles;
roles = [NSMutableArray arrayWithCapacity: 6];
@@ -117,11 +128,11 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
rights |= RightsReadItems | RightsEditAll;
rights |= RightsReadItems | RightsEditAll | RightsEditOwn | RightsFreeBusySimple | RightsFreeBusyDetailed;
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
@@ -135,7 +146,7 @@
if ([roles containsObject: SOGoCalendarRole_ConfidentialDAndTViewer])
rights |= RightsFreeBusyDetailed;
if (rights != 0)
if ((rights & RightsReadItems) != 0 || (rights & RightsCreateItems) != 0 || (rights & RightsDeleteAll) != 0)
rights |= RoleNone; /* actually "folder visible" */
// [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights];
-1
View File
@@ -197,7 +197,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
= [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent
andUser: [userContext sogoUser]
andSenderEmail: nil
inTimeZone: [userContext timeZone]
withConnectionInfo: [context connectionInfo]];
[self addProxy: appointmentWrapper];
}
+2 -2
View File
@@ -96,9 +96,9 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoRole_ObjectEditor])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: SOGoRole_ObjectViewer])
rights |= RightsReadItems;
if (rights != 0)
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -102,7 +102,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
{
value = get_SPropValue_SRow (aRow, PidTagDisplayName_string8);
if (value)
folderName = [NSString stringWithUTF8String: value->value.lpszA];
folderName = [NSString stringWithUTF8String: (const char *) value->value.lpszA];
else
folderName = nil;
}
@@ -321,9 +321,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact";
if ([roles containsObject: MAPIStoreRightDeleteOwn])
rights |= RightsDeleteOwn;
if ([roles containsObject: MAPIStoreRightEditAll])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: MAPIStoreRightDeleteAll])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: MAPIStoreRightCreateSubfolders])
rights |= RightsCreateSubfolders;
if ([roles containsObject: MAPIStoreRightFolderOwner])
+91 -14
View File
@@ -45,16 +45,28 @@
@implementation MAPIStoreDBMessage
+ (int) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
+ (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
struct SPropTagArray *properties;
NSUInteger count;
enum MAPITAGS faiProperties[] = { 0x68350102, 0x683c0102, 0x683e0102,
0x683f0102, 0x68410003, 0x68420102,
0x68450102, 0x68460003,
// PR_VD_NAME_W, PR_VD_FLAGS, PR_VD_VERSION, PR_VIEW_CLSID
0x7006001F, 0x70030003, 0x70070003, 0x68330048 };
enum MAPITAGS faiProperties[] = {
0x68330048, /* PR_VIEW_CLSID */
0x68350102, /* PR_VIEW_STATE */
0x683c0102,
0x683d0040,
0x683e0102,
0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */
0x68410003,
0x68420102,
0x68450102,
0x68460003,
0x7006001F, /* PR_VD_NAME_W */
0x70030003, /* PR_VD_FLAGS */
0x70070003 /* PR_VD_VERSION */
};
size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS);
properties = talloc_zero (memCtx, struct SPropTagArray);
@@ -76,6 +88,13 @@
return MAPISTORE_SUCCESS;
}
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
inMemCtx: (TALLOC_CTX *) memCtx
{
return [[self class] getAvailableProperties: propertiesP
inMemCtx: memCtx];
}
- (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer
{
@@ -90,18 +109,70 @@
return self;
}
- (NSString *) description
{
id key, value;
NSEnumerator *propEnumerator;
NSMutableString *description;
description = [NSMutableString stringWithFormat: @"%@ %@. Properties: {", NSStringFromClass ([self class]),
[self url]];
propEnumerator = [properties keyEnumerator];
while ((key = [propEnumerator nextObject]))
{
uint32_t proptag = 0;
if ([key isKindOfClass: [NSString class]] && [(NSString *)key intValue] > 0)
proptag = [(NSString *)key intValue];
else if ([key isKindOfClass: [NSNumber class]])
proptag = [key unsignedLongValue];
if (proptag > 0)
{
const char *propTagName = get_proptag_name ([key unsignedLongValue]);
NSString *propName;
if (propTagName)
propName = [NSString stringWithCString: propTagName
encoding: NSUTF8StringEncoding];
else
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
[description appendFormat: @"'%@': ", propName];
}
else
[description appendFormat: @"'%@': ", key];
value = [properties objectForKey: key];
[description appendFormat: @"%@ (%@), ", value, NSStringFromClass ([value class])];
}
[description appendString: @"}\n"];
return description;
}
- (uint64_t) objectVersion
{
NSNumber *versionNbr;
/* Return the global counter from CN structure.
See [MS-OXCFXICS] Section 2.2.2.1 */
NSNumber *versionNbr, *cn;
uint64_t objectVersion;
[(SOGoMAPIDBMessage *) sogoObject reloadIfNeeded];
versionNbr = [properties objectForKey: @"version"];
versionNbr = [properties objectForKey: @"version_number"];
if (versionNbr)
objectVersion = (([versionNbr unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
objectVersion = exchange_globcnt ([versionNbr unsignedLongLongValue]);
else
objectVersion = ULLONG_MAX;
{
/* Old version which stored the CN structure not useful for searching */
cn = [properties objectForKey: @"version"];
if (cn)
objectVersion = (([cn unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
else
objectVersion = ULLONG_MAX;
}
return objectVersion;
}
@@ -264,13 +335,19 @@
[properties setObject: attachmentParts forKey: @"attachments"];
newVersion = [[self context] getNewChangeNumber];
newVersion = exchange_globcnt ((newVersion >> 16) & 0x0000ffffffffffffLL);
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
forKey: @"version"];
forKey: @"version_number"];
/* Remove old version */
[properties removeObjectForKey: @"version"];
/* Update PredecessorChangeList accordingly */
[self _updatePredecessorChangeList];
[self logWithFormat: @"%d props in dict", [properties count]];
// [self logWithFormat: @"Saving %@", [self description]];
// [self logWithFormat: @"%d props in dict", [properties count]];
[sogoObject save];
}
+5 -4
View File
@@ -61,14 +61,15 @@ static Class MAPIStoreDBMessageK = Nil;
if ((uint32_t) res->ulPropTag == PidTagChangeNumber)
{
SEL operator;
value = NSObjectFromMAPISPropValue (&res->lpProp);
cVersion = exchange_globcnt (([value unsignedLongLongValue] >> 16)
& 0x0000ffffffffffffLL);
version = [NSNumber numberWithUnsignedLongLong: cVersion];
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
[self logWithFormat: @" version: %.16lx", cVersion];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
operator = [self operatorFromRestrictionOperator: res->relop];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"version_number"
operatorSelector: operator
value: version];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
+2 -3
View File
@@ -1549,7 +1549,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
struct Binary_r bin32;
struct AddressBookEntryId *entryId;
NSString *username;
struct ldb_context *samCtx;
if (bin && bin->cb)
{
@@ -1559,8 +1558,8 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
entryId = get_AddressBookEntryId (NULL, &bin32);
if (entryId)
{
samCtx = [[self context] connectionInfo]->sam_ctx;
username = MAPIStoreSamDBUserAttribute (samCtx, @"legacyExchangeDN",
username = MAPIStoreSamDBUserAttribute ([[self context] connectionInfo],
@"legacyExchangeDN",
[NSString stringWithUTF8String: entryId->X500DN],
@"sAMAccountName");
}
+1
View File
@@ -41,6 +41,7 @@
/* synchronisation */
- (BOOL) synchroniseCache;
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer;
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl;
+112 -3
View File
@@ -548,6 +548,115 @@ static Class NSNumberK;
return rc;
}
- (BOOL) synchroniseCacheFor: (NSString *) nameInContainer
{
/* Try to synchronise old messages in versions.plist cache using an
specific c_name. It returns a boolean indicating if the
synchronisation was carried out succesfully.
It should be used as last resort, keeping synchroniseCache as the
main sync entry point. */
uint64_t changeNumber;
NSString *changeNumberStr;
NSData *changeKey;
NSNumber *cLastModified, *cDeleted, *cVersion;
EOFetchSpecification *fs;
EOQualifier *searchQualifier, *fetchQualifier;
NSArray *fetchResults;
NSDictionary *result;
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
GCSFolder *ocsFolder;
static NSArray *fields;
[versionsMessage reloadIfNeeded];
currentProperties = [versionsMessage properties];
messages = [currentProperties objectForKey: @"Messages"];
if (!messages)
{
messages = [NSMutableDictionary new];
[currentProperties setObject: messages forKey: @"Messages"];
[messages release];
}
messageEntry = [messages objectForKey: nameInContainer];
if (!messageEntry)
{
/* Fetch the message by its name */
if (!fields)
fields = [[NSArray alloc]
initWithObjects: @"c_name", @"c_version", @"c_lastmodified",
@"c_deleted", nil];
searchQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_name"
operatorSelector: EOQualifierOperatorEqual
value: nameInContainer];
fetchQualifier = [[EOAndQualifier alloc]
initWithQualifiers: searchQualifier,
[self contentComponentQualifier],
nil];
[fetchQualifier autorelease];
[searchQualifier release];
ocsFolder = [sogoObject ocsFolder];
fs = [EOFetchSpecification
fetchSpecificationWithEntityName: [ocsFolder folderName]
qualifier: fetchQualifier
sortOrderings: nil];
fetchResults = [ocsFolder fetchFields: fields
fetchSpecification: fs
ignoreDeleted: NO];
if ([fetchResults count] == 1)
{
result = [fetchResults objectAtIndex: 0];
cLastModified = [result objectForKey: @"c_lastmodified"];
cDeleted = [result objectForKey: @"c_deleted"];
if ([cDeleted isKindOfClass: NSNumberK] && [cDeleted intValue])
cVersion = [NSNumber numberWithInt: -1];
else
cVersion = [result objectForKey: @"c_version"];
changeNumber = [[self context] getNewChangeNumber];
changeNumberStr = [NSString stringWithUnsignedLongLong: changeNumber];
/* Create new message entry in Messages dict */
messageEntry = [NSMutableDictionary new];
[messages setObject: messageEntry forKey: nameInContainer];
[messageEntry release];
/* Store cLastModified, cVersion and the change number */
[messageEntry setObject: cLastModified forKey: @"c_lastmodified"];
[messageEntry setObject: cVersion forKey: @"c_version"];
[messageEntry setObject: changeNumberStr forKey: @"version"];
/* Store the change key */
changeKey = [self getReplicaKeyFromGlobCnt: changeNumber >> 16];
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
/* Store the changeNumber -> cLastModified mapping */
mapping = [currentProperties objectForKey: @"VersionMapping"];
if (!mapping)
{
mapping = [NSMutableDictionary new];
[currentProperties setObject: mapping forKey: @"VersionMapping"];
[mapping release];
}
[mapping setObject: cLastModified forKey: changeNumberStr];
/* Save the message */
[versionsMessage save];
return YES;
}
else
return NO;
}
/* If message entry exists, then synchroniseCache did its job */
return YES;
}
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
withChangeKey: (NSData *) oldChangeKey
andPredecessorChangeList: (NSData *) pcl
@@ -572,12 +681,12 @@ static Class NSNumberK;
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber
{
NSDictionary *mapping;
NSNumber *modseq;
NSNumber *lastModified;
mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"];
modseq = [mapping objectForKey: changeNumber];
lastModified = [mapping objectForKey: changeNumber];
return modseq;
return lastModified;
}
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey
+33 -13
View File
@@ -140,9 +140,16 @@
[parentFolder synchroniseCache];
changeKey = [parentFolder changeKeyForMessageWithKey: nameInContainer];
}
if (!changeKey)
abort ();
*data = [changeKey asBinaryInMemCtx: memCtx];
if (changeKey)
*data = [changeKey asBinaryInMemCtx: memCtx];
else
{
[self warnWithFormat: @"No change key for %@ in folder %@",
nameInContainer,
[parentFolder url]
];
rc = MAPISTORE_ERR_NOT_FOUND;
}
}
return rc;
@@ -180,7 +187,8 @@
{
uint64_t version = ULLONG_MAX;
NSString *changeNumber;
BOOL synced;
if (!isNew)
{
changeNumber = [(MAPIStoreGCSFolder *) container
@@ -189,16 +197,28 @@
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising folder..."];
[(MAPIStoreGCSFolder *) container synchroniseCache];
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
if (changeNumber)
[self logWithFormat: @"got one"];
else
synced = [(MAPIStoreGCSFolder *) container synchroniseCache];
if (synced)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self warnWithFormat: @"attempting to get change number"
@" by synchronising this specific message..."];
synced = [(MAPIStoreGCSFolder *) container
synchroniseCacheFor: [self nameInContainer]];
if (synced)
{
changeNumber = [(MAPIStoreGCSFolder *) container
changeNumberForMessageWithKey: [self nameInContainer]];
}
if (!changeNumber)
{
[self errorWithFormat: @"still nothing. We crash!"];
abort();
}
}
}
version = [changeNumber unsignedLongLongValue] >> 16;
+14 -4
View File
@@ -27,6 +27,7 @@
#import <Foundation/NSString.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <EOControl/EOFetchSpecification.h>
#import <EOControl/EOQualifier.h>
@@ -38,7 +39,6 @@
#import "MAPIStoreTypes.h"
#import "MAPIStoreGCSFolder.h"
#import "MAPIStoreGCSMessageTable.h"
#undef DEBUG
@@ -89,21 +89,31 @@
if (res->ulPropTag == PidTagChangeNumber)
{
NSString *changeNumber;
value = NSObjectFromMAPISPropValue (&res->lpProp);
changeNumber = [NSString stringWithUnsignedLongLong: [(NSNumber *)value unsignedLongLongValue]];
lastModified = [(MAPIStoreGCSFolder *)
container lastModifiedFromMessageChangeNumber: value];
container lastModifiedFromMessageChangeNumber: changeNumber];
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
//[self logWithFormat: @" c_lastmodified: %@", lastModified];
if (lastModified)
{
SEL operator;
operator = [self operatorFromRestrictionOperator: res->relop];
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_lastmodified"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
operatorSelector: operator
value: lastModified];
[*qualifier autorelease];
rc = MAPIRestrictionStateNeedsEval;
}
else
rc = MAPIRestrictionStateAlwaysTrue;
{
[self logWithFormat: @"No last modified found for: 0x%.16"PRIx64". Then no restriction applied",
[value unsignedLongLongValue]];
rc = MAPIRestrictionStateAlwaysTrue;
}
}
else
{
+13 -11
View File
@@ -160,23 +160,25 @@ MakeDisplayFolderName (NSString *folderName)
for (count = 0; count < max; count++)
{
context = talloc_zero (memCtx, struct mapistore_contexts_list);
// secondaryFolders has the names (1) Imap4Encoded and (2) asCSSIdentifier
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// secondaryFolders has the names (1) Imap4Encoded ,(2) asCSSIdentifier and (3) "folder"-prefixed
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
currentName = [secondaryFolders objectAtIndex: count];
// To get the real name we have to revert that (applying the decode functions)
// in reverse order
// To get the real name we have to revert that (applying the decode functions
// in reverse order)
// e.g.: Problèmes de synchronisation
realName = [[currentName fromCSSIdentifier]
stringByDecodingImap4FolderName];
realName = [[[currentName substringFromIndex: 6]
fromCSSIdentifier]
stringByDecodingImap4FolderName];
// And finally to represent that as URI we have to (1) asCSSIdentifier,
// (2) Imap4Encode and (3) AddPercentEscapes
// e.g.: Probl&AOg-mes_SP_de_SP_synchronisation
// (2) Imap4Encode (3) AddPercentEscapes and (4) add the "folder" prefix
// e.g.: folderProbl&AOg-mes_SP_de_SP_synchronisation
// In the example there are no percent escapes added because is already ok
stringData = [[[realName asCSSIdentifier]
stringByEncodingImap4FolderName]
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
stringData = [NSString stringWithFormat: @"folder%@", stringData];
context->url = [[NSString stringWithFormat: @"%@%@", urlBase, stringData] asUnicodeInMemCtx: context];
context->name = [[realName substringFromIndex: 6] asUnicodeInMemCtx: context];
context->name = [realName asUnicodeInMemCtx: context];
context->main_folder = false;
context->role = MAPISTORE_MAIL_ROLE;
context->tag = "tag";
@@ -200,7 +202,7 @@ MakeDisplayFolderName (NSString *folderName)
andTDBIndexing: NULL];
accountFolder = [[userContext rootFolders] objectForKey: @"mail"];
folderName = [NSString stringWithFormat: @"folder%@",
[newFolderName asCSSIdentifier]];
[[newFolderName stringByEncodingImap4FolderName] asCSSIdentifier]];
newFolder = [SOGoMailFolder objectWithName: folderName
inContainer: accountFolder];
if ([newFolder create])
@@ -209,7 +211,7 @@ MakeDisplayFolderName (NSString *folderName)
withString: @"%40"],
[userName stringByReplacingOccurrencesOfString: @"@"
withString: @"%40"],
[[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
[folderName stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
else
mapistoreURI = nil;
+100 -35
View File
@@ -74,6 +74,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
#include <util/attr.h>
#include <libmapi/libmapi.h>
#include <libmapiproxy.h>
#include <limits.h>
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>
@@ -181,7 +182,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME_UNICODE)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszW];
else if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszA];
folderName = [NSString stringWithUTF8String: (const char *) aRow->lpProps[i].value.lpszA];
}
if (folderName)
@@ -257,6 +258,67 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
return MAPISTORE_SUCCESS;
}
- (EOQualifier *) simplifyQualifier: (EOQualifier *) qualifier
{
/* Hack: Reduce the number of MODSEQ constraints to a single one as
we assume the difference among MODSEQs will be small enough to
return a small number of UIDs.
This is the only case we do simplify:
MODSEQ >= x | MODSEQ >= y | MODSEQ >= z => MODSEQ >= min(x,y,z)
*/
if (qualifier && [qualifier isKindOfClass: [EOOrQualifier class]])
{
EOQualifier *simplifiedQualifier;
NSArray *quals;
NSNumber *minModseq;
NSUInteger i, count;
quals = [(EOOrQualifier *)qualifier qualifiers];
count = [quals count];
if (count < 2)
return qualifier;
minModseq = [NSNumber numberWithUnsignedLongLong: ULLONG_MAX];
for (i = 0; i < count; i++)
{
EOQualifier *subQualifier;
subQualifier = [quals objectAtIndex: i];
if ([subQualifier isKindOfClass: [EOAndQualifier class]]
&& [[(EOAndQualifier *)subQualifier qualifiers] count] == 1)
subQualifier = [[(EOAndQualifier *)subQualifier qualifiers] objectAtIndex: 0];
if ([subQualifier isKindOfClass: [EOKeyValueQualifier class]]
&& [[(EOKeyValueQualifier *)subQualifier key] isEqualToString: @"MODSEQ"])
{
NSNumber *value;
value = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
if ([minModseq compare: value] == NSOrderedDescending
&& [value unsignedLongLongValue] > 0)
minModseq = (NSNumber *)[(EOKeyValueQualifier *)subQualifier value];
}
else
return qualifier;
}
if ([minModseq unsignedLongLongValue] > 0 && [minModseq unsignedLongLongValue] < ULLONG_MAX)
{
simplifiedQualifier = [[EOKeyValueQualifier alloc]
initWithKey: @"MODSEQ"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
value: minModseq];
[simplifiedQualifier autorelease];
return simplifiedQualifier;
}
}
return qualifier;
}
- (EOQualifier *) nonDeletedQualifier
{
static EOQualifier *nonDeletedQualifier = nil;
@@ -281,7 +343,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
andSortOrderings: (NSArray *) sortOrderings
{
NSArray *uidKeys;
EOQualifier *fetchQualifier;
EOQualifier *fetchQualifier, *simplifiedQualifier;
if ([self ensureFolderExists])
{
@@ -290,9 +352,10 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
if (qualifier)
{
simplifiedQualifier = [self simplifyQualifier: qualifier];
fetchQualifier
= [[EOAndQualifier alloc] initWithQualifiers:
[self nonDeletedQualifier], qualifier,
[self nonDeletedQualifier], simplifiedQualifier,
nil];
[fetchQualifier autorelease];
}
@@ -594,13 +657,12 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{
BOOL rc = YES;
uint64_t newChangeNum;
NSNumber *ti, *modseq, *initialLastModseq, *lastModseq,
*nextModseq;
NSNumber *ti, *modseq, *lastModseq, *nextModseq;
NSString *changeNumber, *uid, *messageKey;
uint64_t lastModseqNbr;
EOQualifier *searchQualifier;
NSArray *uids, *changeNumbers;
NSUInteger count, max;
NSUInteger count, max, nFetched;
NSArray *fetchResults;
NSDictionary *result;
NSData *changeKey;
@@ -634,7 +696,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
initialLastModseq = lastModseq;
if (lastModseq)
{
lastModseqNbr = [lastModseq unsignedLongLongValue];
@@ -680,6 +741,13 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
nFetched = [fetchResults count];
if (nFetched != max) {
[self errorWithFormat: @"Error fetching UIDs. Asked: %d Received: %d."
@"Check the IMAP conversation for details", max, nFetched];
return NO;
}
for (count = 0; count < max; count++)
{
result = [fetchResults objectAtIndex: count];
@@ -718,40 +786,37 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
}
/* 2. we synchronise expunged UIDs */
if (initialLastModseq)
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
{
fetchResults = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: lastModseqNbr];
max = [fetchResults count];
changeNumber = nil;
for (count = 0; count < max; count++)
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
{
uid = [[fetchResults objectAtIndex: count] stringValue];
if ([messages objectForKey: uid])
if (!changeNumber)
{
if (!changeNumber)
{
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
else
{
[self logWithFormat:@"Message entry not found for UID %@", uid];
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSString stringWithUnsignedLongLong: newChangeNum];
}
[messages removeObjectForKey: uid];
[self logWithFormat: @"Removed message entry for UID %@", uid];
}
if (changeNumber)
else
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
[self logWithFormat:@"Message entry not found for UID %@", uid];
}
}
if (changeNumber)
{
[currentProperties setObject: changeNumber
forKey: @"SyncLastDeleteChangeNumber"];
[mapping setObject: lastModseq forKey: changeNumber];
foundChange = YES;
}
if (foundChange)
{
@@ -1646,10 +1711,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser]
&& [roles containsObject: SOGoRole_FolderEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoRole_ObjectEditor])
rights |= RightsEditAll;
rights |= RightsEditAll | RightsEditOwn;
if ([roles containsObject: SOGoRole_ObjectViewer])
rights |= RightsReadItems;
if ([roles containsObject: SOGoRole_FolderCreator])
+73 -33
View File
@@ -340,7 +340,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
wrapperWithICalEvent: event
andUser: [context activeUser]
andSenderEmail: senderEmail
inTimeZone: [[self userContext] timeZone]
withConnectionInfo: [context connectionInfo]];
[appointmentWrapper retain];
}
@@ -522,6 +521,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSUInteger colIdx;
NSString *stringValue;
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
or less characters followed by a colon at the beginning of
the subject, we can assume that's the subject prefix */
subject = [self subject];
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
@@ -537,17 +539,45 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
- (int) getPidTagNormalizedSubject: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
NSString *subject;
NSUInteger colIdx;
NSString *stringValue;
NSString *stringValue, *subject;
NSUInteger quoteStartIdx, quoteEndIdx, colIdx;
NSRange quoteRange;
if (!headerSetup)
[self _fetchHeaderData];
subject = [self subject];
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
stringValue = [[subject substringFromIndex: colIdx + 1]
stringByTrimmingLeadSpaces];
if (mailIsMeetingRequest)
{
/* SOGo "spices up" the invitation/update mail's subject, but
the client uses it to name the attendee's event, so we keep
only what's inside the quotes */
quoteStartIdx = [subject rangeOfString: @"\""].location;
quoteEndIdx = [subject rangeOfString: @"\""
options: NSBackwardsSearch].location;
if (quoteStartIdx != NSNotFound
&& quoteEndIdx != NSNotFound
&& quoteStartIdx != quoteEndIdx)
{
quoteRange = NSMakeRange(quoteStartIdx + 1, quoteEndIdx - quoteStartIdx - 1);
stringValue = [subject substringWithRange: quoteRange];
}
else stringValue = subject;
}
else
stringValue = subject;
{
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
or less characters followed by a colon at the beginning of
the subject, we can assume that's the subject prefix */
colIdx = [subject rangeOfString: @":"].location;
if (colIdx != NSNotFound && colIdx < 4)
stringValue = [[subject substringFromIndex: colIdx + 1]
stringByTrimmingLeadSpaces];
else
stringValue = subject;
}
if (!stringValue)
stringValue = @"";
*data = [stringValue asUnicodeInMemCtx: memCtx];
@@ -780,7 +810,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSDictionary *contactInfos;
NGMailAddress *ngAddress;
NSData *entryId;
struct ldb_context *samCtx;
int rc;
if (fullMail)
@@ -803,8 +832,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
if (contactInfos)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
samCtx = [[self context] connectionInfo]->sam_ctx;
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId([[self context] connectionInfo], username);
}
else
entryId = MAPIStoreExternalEntryId (cn, email);
@@ -1447,11 +1475,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
NSData *entryId;
NSDictionary *contactInfos;
SOGoUserManager *mgr;
struct ldb_context *samCtx;
struct mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
samCtx = [[self context] connectionInfo]->sam_ctx;
[super getMessageData: &msgData inMemCtx: memCtx];
if (!headerSetup)
@@ -1505,7 +1531,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
}
else
{
@@ -1645,27 +1671,11 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
- (enum mapistore_error) setReadFlag: (uint8_t) flag
{
BOOL modified = NO;
BOOL alreadyRead = NO;
NSString *imapFlag = @"\\Seen";
alreadyRead = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"]
containsObject: @"seen"];
/* TODO: notifications should probably be emitted from here */
if (flag & CLEAR_READ_FLAG)
{
[sogoObject removeFlags: imapFlag];
modified = alreadyRead;
}
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
else
{
[sogoObject addFlags: imapFlag];
modified = !alreadyRead;
}
if (modified)
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
return MAPISTORE_SUCCESS;
}
@@ -1708,7 +1718,10 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
- (void) save: (TALLOC_CTX *) memCtx
{
BOOL modified = NO;
BOOL seen, storedSeenFlag;
NSNumber *value;
NSString *imapFlag = @"\\Seen";
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
if (value)
@@ -1718,8 +1731,35 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
[sogoObject addFlags: @"\\Flagged"];
else /* 0: unflagged, 1: follow up complete */
[sogoObject removeFlags: @"\\Flagged"];
modified = YES;
}
/* Manage seen flag on save */
value = [properties objectForKey: @"read_flag_set"];
if (value)
{
seen = [value boolValue];
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
/* We modify the flags anyway to generate a new change number */
if (seen)
{
if (storedSeenFlag)
[sogoObject removeFlags: imapFlag];
[sogoObject addFlags: imapFlag];
}
else
{
if (!storedSeenFlag)
[sogoObject addFlags: imapFlag];
[sogoObject removeFlags: imapFlag];
}
modified = YES;
}
if (modified)
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
if (mailIsSharingObject)
[[self _sharingObject] saveWithMessage: self
andSOGoObject: sogoObject];
+2 -2
View File
@@ -181,8 +181,8 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
else
{
/* Ignore other operations as IMAP only support MODSEQ >= X */
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
[self operatorFromRestrictionOperator: res->relop]];
[self warnWithFormat: @"Ignoring '%@' as only supported operators are > and >=",
NSStringFromSelector ([self operatorFromRestrictionOperator: res->relop])];
rc = MAPIRestrictionStateAlwaysTrue;
}
}
+25 -16
View File
@@ -167,9 +167,9 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
- (BOOL) hasContentId
{
return ([properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
!= nil);
NSString *contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
return contentId && [contentId length] > 0;
}
- (NGMimeBodyPart *) asMIMEBodyPart
@@ -233,7 +233,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
[map addObject: contentDisposition forKey: @"content-disposition"];
contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
if (contentId)
if (contentId && [contentId length] > 0)
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
forKey: @"content-id"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
@@ -338,13 +338,10 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
NSData *entryId;
NSDictionary *allRecipients, *dict, *contactInfos;
SOGoUserManager *mgr;
struct ldb_context *samCtx;
struct mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
enum ulRecipClass type;
samCtx = [[self context] connectionInfo]->sam_ctx;
// [super getMessageData: &msgData inMemCtx: memCtx];
msgData = talloc_zero (memCtx, struct mapistore_message);
@@ -389,7 +386,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
{
username = [contactInfos objectForKey: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (samCtx, username);
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], username);
}
else
{
@@ -682,6 +679,14 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
if (!fromResolved)
{
TALLOC_CTX *local_mem_ctx;
local_mem_ctx = talloc_new(NULL);
if (!local_mem_ctx)
{
NSLog (@"%s: Out of memory", __PRETTY_FUNCTION__);
return;
}
NSLog (@"Message without an orig from, try to guess it from PidTagSenderEntryId");
senderEntryId = [mailProperties objectForKey: MAPIPropertyKey (PR_SENDER_ENTRYID)];
if (senderEntryId)
@@ -695,12 +700,12 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
bin32.cb = [senderEntryId length];
bin32.lpb = (uint8_t *) [senderEntryId bytes];
addrBookEntryId = get_AddressBookEntryId (connInfo->sam_ctx, &bin32);
addrBookEntryId = get_AddressBookEntryId (local_mem_ctx, &bin32);
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
hasSuffix: @"08002b2fe182"])
{
/* TODO: better way to distinguish local and other ones */
username = MAPIStoreSamDBUserAttribute (connInfo->sam_ctx, @"legacyExchangeDN",
username = MAPIStoreSamDBUserAttribute (connInfo, @"legacyExchangeDN",
[NSString stringWithUTF8String: addrBookEntryId->X500DN], @"sAMAccountName");
if (username)
{
@@ -721,7 +726,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
/* Try with One-Off EntryId */
struct OneOffEntryId *oneOffEntryId;
oneOffEntryId = get_OneOffEntryId (connInfo->sam_ctx, &bin32);
oneOffEntryId = get_OneOffEntryId (local_mem_ctx, &bin32);
if (oneOffEntryId && [[NSString stringWithGUID: &oneOffEntryId->ProviderUID]
hasSuffix: @"00dd010f5402"])
{
@@ -730,9 +735,7 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->EmailAddress.lpszW]
forKey: @"email"];
}
talloc_free (oneOffEntryId);
}
talloc_free (addrBookEntryId);
if ([[fromRecipient allKeys] count] > 0)
{
@@ -742,6 +745,8 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers,
}
}
/* Free entryId */
talloc_free(local_mem_ctx);
}
if (!recipients)
@@ -1008,7 +1013,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
- (NGMimeMessage *) _generateMessageWithBcc: (BOOL) withBcc
{
NSString *contentType;
NGMimeMessage *message;
NGMimeMessage *message;
NGMutableHashMap *headers;
id messageBody;
@@ -1048,6 +1053,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
- (int) submitWithFlags: (enum SubmitFlags) flags
{
enum mapistore_error rc = MAPISTORE_SUCCESS;
NSDictionary *recipients;
NSData *messageData;
NSMutableArray *recipientEmails;
@@ -1094,7 +1100,10 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
withAuthenticator: authenticator
inContext: woContext];
if (error)
[self logWithFormat: @"an error occurred: '%@'", error];
{
[self errorWithFormat: @"an error occurred: '%@'", error];
rc = MAPISTORE_ERR_MSG_SEND;
}
// mapping = [self mapping];
// [mapping unregisterURLWithID: [self objectId]];
@@ -1106,7 +1115,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
[self logWithFormat: @"skipping submit of message with class '%@'",
msgClass];
return MAPISTORE_SUCCESS;
return rc;
}
- (void) save: (TALLOC_CTX *) memCtx
+1 -1
View File
@@ -103,7 +103,7 @@
else
{
connInfo = [(MAPIStoreContext *) [container context] connectionInfo];
entryId = MAPIStoreInternalEntryId (connInfo->sam_ctx, userId);
entryId = MAPIStoreInternalEntryId (connInfo, userId);
}
*data = [entryId asBinaryInMemCtx: memCtx];
+2 -6
View File
@@ -25,12 +25,9 @@
#include <talloc.h>
#import <Foundation/NSTimeZone.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalRecurrenceRule.h>
@class NSTimeZone;
#import <NGCards/iCalTimeZone.h>
@class iCalEvent;
@class iCalRepeatableEntityObject;
@@ -46,7 +43,7 @@
fromRecurrencePattern: (struct RecurrencePattern *) rp
withExceptions: (struct ExceptionInfo *) exInfos
andExceptionCount: (uint16_t) exInfoCount
inTimeZone: (NSTimeZone *) tz;
inTimeZone: (iCalTimeZone *) tz;
@end
@@ -54,7 +51,6 @@
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
withEvent: (iCalEvent *) event
inTimeZone: (NSTimeZone *) timeZone
inMemCtx: (TALLOC_CTX *) memCtx;
@end
+6 -8
View File
@@ -24,7 +24,6 @@
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -35,6 +34,7 @@
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGCards/iCalRecurrenceRule.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import "NSDate+MAPIStore.h"
#import "MAPIStoreRecurrenceUtils.h"
@@ -51,7 +51,7 @@
fromRecurrencePattern: (struct RecurrencePattern *) rp
withExceptions: (struct ExceptionInfo *) exInfos
andExceptionCount: (uint16_t) exInfoCount
inTimeZone: (NSTimeZone *) tz
inTimeZone: (iCalTimeZone *) tz
{
NSCalendarDate *startDate, *olEndDate, *untilDate, *exDate;
@@ -63,7 +63,7 @@
iCalWeekOccurrence weekOccurrence;
iCalWeekOccurrences dayMaskDays;
NSUInteger count, max;
NSInteger bySetPos;
NSInteger bySetPos, tzOffset;
unsigned char maskValue;
[entity removeAllRecurrenceRules];
@@ -242,9 +242,10 @@
{
/* The OriginalStartDate is in local time */
exDate = [NSDate dateFromMinutesSince1601: exInfos[count].OriginalStartDate];
tzOffset = -[[tz periodForDate: exDate] secondsOffsetFromGMT];
exDate = [exDate dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: - [tz secondsFromGMT]];
seconds: tzOffset];
[exceptionDates removeObject: exDate];
}
}
@@ -263,7 +264,6 @@
- (void) fillRecurrencePattern: (struct RecurrencePattern *) rp
withEvent: (iCalEvent *) event
inTimeZone: (NSTimeZone *) timeZone
inMemCtx: (TALLOC_CTX *) memCtx
{
iCalRecurrenceFrequency freq;
@@ -279,10 +279,8 @@
NSMutableArray *deletedDates, *modifiedDates;
startDate = [event firstRecurrenceStartDate];
[startDate setTimeZone: timeZone];
endDate = [event lastPossibleRecurrenceStartDate];
[endDate setTimeZone: timeZone];
rp->ReaderVersion = 0x3004;
rp->WriterVersion = 0x3004;
+43 -43
View File
@@ -64,11 +64,11 @@ static BOOL initialization_done = NO;
} \
OC_DEBUG(5, "[SOGo] --->");
#define NS_CURRENT_THREAD_TRY_UNREGISTER() \
#define NS_CURRENT_THREAD_TRY_UNREGISTER(rc) \
if (__nsrct_thread_registered) { \
GSUnregisterCurrentThread(); \
} \
OC_DEBUG(6, "[SOGo] <---");
OC_DEBUG(6, "[SOGo] <--- [%s]", mapistore_errstr (rc));
#define TRYCATCH_START @try {
#define TRYCATCH_END(pool) \
@@ -76,7 +76,7 @@ static BOOL initialization_done = NO;
enum mapistore_error ret = sogo_backend_handle_objc_exception(e, __PRETTY_FUNCTION__, __LINE__); \
mapiapp_cleanup(); \
[pool release]; \
NS_CURRENT_THREAD_TRY_UNREGISTER(); \
NS_CURRENT_THREAD_TRY_UNREGISTER(ret); \
return ret; \
} \
mapiapp_cleanup();
@@ -253,7 +253,7 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx,
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -291,7 +291,7 @@ sogo_backend_create_root_folder (const char *username,
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -322,7 +322,7 @@ sogo_backend_list_contexts(const char *username, struct indexing_context *indexi
rc = MAPISTORE_ERROR;
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
return rc;
}
@@ -363,7 +363,7 @@ sogo_context_get_path(void *backend_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -397,7 +397,7 @@ sogo_context_get_root_folder(void *backend_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -439,7 +439,7 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -480,7 +480,7 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -519,7 +519,7 @@ sogo_folder_delete(void *folder_object)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -549,7 +549,7 @@ sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -588,7 +588,7 @@ sogo_folder_open_message(void *folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -627,7 +627,7 @@ sogo_folder_create_message(void *folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -657,7 +657,7 @@ sogo_folder_delete_message(void *folder_object, uint64_t mid, uint8_t flags)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -705,7 +705,7 @@ sogo_folder_move_copy_messages(void *folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -753,7 +753,7 @@ sogo_folder_move_folder(void *folder_object, void *target_folder_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -795,7 +795,7 @@ sogo_folder_copy_folder(void *folder_object, void *target_folder_object, TALLOC_
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -831,7 +831,7 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -869,7 +869,7 @@ sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -903,7 +903,7 @@ sogo_folder_modify_permissions(void *folder_object, uint8_t flags,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -934,7 +934,7 @@ sogo_folder_preload_message_bodies(void *folder_object, enum mapistore_table_typ
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -967,8 +967,8 @@ sogo_message_get_message_data(void *message_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
rc = MAPISTORE_SUCCESS;
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1001,7 +1001,7 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1035,7 +1035,7 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1069,7 +1069,7 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1104,7 +1104,7 @@ sogo_message_modify_recipients (void *message_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1134,7 +1134,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1164,7 +1164,7 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1194,7 +1194,7 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1234,7 +1234,7 @@ sogo_message_attachment_open_embedded_message (void *attachment_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1272,7 +1272,7 @@ sogo_message_attachment_create_embedded_message (void *attachment_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1302,7 +1302,7 @@ static enum mapistore_error sogo_table_get_available_properties(void *table_obje
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1333,7 +1333,7 @@ sogo_table_set_columns (void *table_object, uint16_t count, enum MAPITAGS *prope
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1366,7 +1366,7 @@ sogo_table_set_restrictions (void *table_object, struct mapi_SRestriction *restr
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1399,7 +1399,7 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1432,7 +1432,7 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1465,7 +1465,7 @@ sogo_table_get_row_count (void *table_object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1495,8 +1495,8 @@ sogo_table_handle_destructor (void *table_object, uint32_t handle_id)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
rc = MAPISTORE_SUCCESS;
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1527,7 +1527,7 @@ static enum mapistore_error sogo_properties_get_available_properties(void *objec
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1562,7 +1562,7 @@ sogo_properties_get_properties (void *object,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1592,7 +1592,7 @@ sogo_properties_set_properties (void *object, struct SRow *aRow)
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(rc);
}
else
{
@@ -1646,7 +1646,7 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx,
TRYCATCH_END(pool)
[pool release];
NS_CURRENT_THREAD_TRY_UNREGISTER();
NS_CURRENT_THREAD_TRY_UNREGISTER(MAPISTORE_SUCCESS);
return MAPISTORE_SUCCESS;
}
+3 -1
View File
@@ -197,6 +197,7 @@ static Class MAPIStoreFolderK;
inMemCtx: (TALLOC_CTX *) memCtx
{
int rc;
struct mapistore_connection_info *connInfo;
uint64_t obVersion;
obVersion = [self objectVersion];
@@ -204,8 +205,9 @@ static Class MAPIStoreFolderK;
rc = MAPISTORE_ERR_NOT_FOUND;
else
{
connInfo = [[self context] connectionInfo];
*data = MAPILongLongValue (memCtx, ((obVersion << 16)
| 0x0001));
| connInfo->repl_id));
rc = MAPISTORE_SUCCESS;
}
+3 -3
View File
@@ -25,13 +25,13 @@
@class NSString;
struct ldb_context;
#include <mapistore/mapistore.h>
NSString *MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
NSString *MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
NSString *userKey,
NSString *value,
NSString *attributeName);
NSData *MAPIStoreInternalEntryId (struct ldb_context *, NSString *username);
NSData *MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username);
NSData *MAPIStoreExternalEntryId (NSString *cn, NSString *email);
#endif /* MAPISTORESAMDBUTILS_H */
+19 -4
View File
@@ -24,13 +24,18 @@
#import <Foundation/NSString.h>
#include <talloc.h>
#include <ldb.h>
#include <libmapiproxy.h>
#include <samba/version.h>
#import "NSData+MAPIStore.h"
#import "MAPIStoreSamDBUtils.h"
NSString *
MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
MAPIStoreSamDBUserAttribute (struct mapistore_connection_info *connInfo,
NSString *userKey,
NSString *value,
NSString *attributeName)
@@ -48,10 +53,20 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
attrs[0] = [attributeName UTF8String];
searchFormat
= [NSString stringWithFormat: @"(&(objectClass=user)(%@=%%s))", userKey];
ret = ldb_search (samCtx, memCtx, &res, ldb_get_default_basedn(samCtx),
#if SAMBA_VERSION_MAJOR <= 4 && SAMBA_VERSION_MINOR < 3
ret = ldb_search (connInfo->sam_ctx, memCtx, &res,
ldb_get_default_basedn(connInfo->sam_ctx),
LDB_SCOPE_SUBTREE, attrs,
[searchFormat UTF8String],
[value UTF8String]);
#else
ret = safe_ldb_search (&connInfo->sam_ctx, memCtx, &res,
ldb_get_default_basedn(connInfo->sam_ctx),
LDB_SCOPE_SUBTREE, attrs,
[searchFormat UTF8String],
[value UTF8String]);
#endif
if (ret == LDB_SUCCESS && res->count == 1)
{
result = ldb_msg_find_attr_as_string (res->msgs[0], attrs[0], NULL);
@@ -65,7 +80,7 @@ MAPIStoreSamDBUserAttribute (struct ldb_context *samCtx,
}
NSData *
MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
MAPIStoreInternalEntryId (struct mapistore_connection_info *connInfo, NSString *username)
{
static const uint8_t const providerUid[] = { 0xdc, 0xa7, 0x40, 0xc8,
0xc0, 0x42, 0x10, 0x1a,
@@ -82,7 +97,7 @@ MAPIStoreInternalEntryId (struct ldb_context *samCtx, NSString *username)
type: 32
X500DN: variable */
legacyDN = MAPIStoreSamDBUserAttribute (samCtx, @"sAMAccountName", username,
legacyDN = MAPIStoreSamDBUserAttribute (connInfo, @"sAMAccountName", username,
@"legacyExchangeDN");
if (legacyDN)
{
+2 -2
View File
@@ -107,11 +107,11 @@
if ([roles containsObject: SOGoRole_ObjectCreator])
rights |= RightsCreateItems;
if ([roles containsObject: SOGoRole_ObjectEraser])
rights |= RightsDeleteAll;
rights |= RightsDeleteAll | RightsDeleteOwn;
if ([roles containsObject: SOGoCalendarRole_PublicModifier]
&& [roles containsObject: SOGoCalendarRole_PrivateModifier]
&& [roles containsObject: SOGoCalendarRole_ConfidentialModifier])
rights |= RightsReadItems | RightsEditAll;
rights |= RightsReadItems | RightsEditAll | RightsEditOwn;
else if ([roles containsObject: SOGoCalendarRole_PublicViewer]
&& [roles containsObject: SOGoCalendarRole_PrivateViewer]
&& [roles containsObject: SOGoCalendarRole_ConfidentialViewer])
+1 -1
View File
@@ -198,7 +198,7 @@ NSObjectFromSPropValue (const struct SPropValue *value)
break;
case PT_STRING8:
result = (value->value.lpszA
? [NSString stringWithUTF8String: value->value.lpszA]
? [NSString stringWithUTF8String: (const char *) value->value.lpszA]
: (id) @"");
break;
case PT_SYSTIME:
+2
View File
@@ -73,6 +73,8 @@ static NSMapTable *contextsTable = nil;
[userContext autorelease];
[contextsTable setObject: userContext forKey: username];
}
else
[userContext activate];
return userContext;
}
+1 -1
View File
@@ -228,7 +228,7 @@
mvResult = [NSMutableArray arrayWithCapacity: mvString->cValues];
for (count = 0; count < mvString->cValues; count++)
{
subObject = [NSString stringWithUTF8String: mvString->lppszA[count]];
subObject = [NSString stringWithUTF8String: (const char *) mvString->lppszA[count]];
[mvResult addObject: subObject];
}
+2
View File
@@ -37,6 +37,8 @@
- (BOOL) isNever; /* occurs on 4500-12-31 */
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
andRuleYear: (uint16_t) rYear;
@end
NSComparisonResult NSDateCompare (id date1, id date2, void *);
+60 -1
View File
@@ -24,6 +24,7 @@
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import "MAPIStoreTypes.h"
#import "NSDate+MAPIStore.h"
#undef DEBUG
@@ -48,7 +49,7 @@ _setupRefDate ()
refDate = [[NSCalendarDate alloc]
initWithYear: 1601 month: 1 day: 1
hour: 0 minute: 0 second: 0
timeZone: [NSTimeZone timeZoneWithName: @"UTC"]];
timeZone: utcTZ];
}
+ (NSCalendarDate *) dateFromMinutesSince1601: (uint32_t) minutes
@@ -128,6 +129,64 @@ _setupRefDate ()
return [calDate yearOfCommonEra] == 4500;
}
+ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date
andRuleYear: (uint16_t) rYear
{
NSCalendarDate *result;
NSInteger daysToDate;
NSUInteger firstDayOfWeek, year;
/* ([MS-OXOCAL] 2.2.1.41.1) When we're provided an absolute date (i.e., it
happens once), the SYSTEMTIME structure is enough to fill the date.
When we're parsing a SYSTEMTIME field from a time zone rule, however, a
relative date can be provided for the peroidicity of its periods. In this
scenario, the wYear field is empty and we have to use the wYear field in
the parent rule */
if (date.wYear != 0)
year = date.wYear;
else
year = rYear;
/* The wDay field indicates the occurrence of the wDayOfWeek within the month.
The 5th occurrence means the last one, even if it is the 4th. */
if (date.wDay < 5)
{
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth day: 1
hour: date.wHour minute: date.wMinute second: date.wSecond
timeZone: utcTZ];
[result autorelease];
firstDayOfWeek = [result dayOfWeek];
daysToDate = 7 * (date.wDay - 1) + date.wDayOfWeek - firstDayOfWeek;
if (date.wDayOfWeek < firstDayOfWeek)
daysToDate += 7;
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
hours: 0 minutes: 0
seconds: 0];
}
else
{
result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth + 1 day: 1
hour: date.wHour minute: date.wMinute second: date.wSecond
timeZone: utcTZ];
[result autorelease];
firstDayOfWeek = [result dayOfWeek];
daysToDate = date.wDayOfWeek - firstDayOfWeek;
if (date.wDayOfWeek >= firstDayOfWeek)
daysToDate -= 7;
result = [result dateByAddingYears: 0 months: 0 days: daysToDate
hours: 0 minutes: 0
seconds: 0];
}
return result;
}
@end
NSComparisonResult
+11 -2
View File
@@ -28,6 +28,8 @@
//
//
//
@class RTFFontTable;
@interface RTFHandler : NSObject
{
NSMapTable *_charsets;
@@ -41,6 +43,11 @@
- (id) initWithData: (NSData *) theData;
- (NSMutableData *) parse;
- (RTFFontTable *) parseFontTable;
- (void) mangleInternalStateWithBytesPtr: (const char*) newBytes
andCurrentPos: (int) newCurrentPos;
@end
//
@@ -67,6 +74,7 @@
int font_index;
int color_index;
int start_pos;
const unsigned short *charset;
}
@end
@@ -77,12 +85,13 @@
{
@public
NSString *family;
NSString *charset;
unsigned char charset;
NSString *name;
unsigned int pitch;
unsigned int index;
}
- (NSString *) description;
@end
//
@@ -96,8 +105,8 @@
- (void) addFontInfo: (RTFFontInfo *) theFontInfo
atIndex: (unsigned int ) theIndex;
- (RTFFontInfo *) fontInfoAtIndex: (unsigned int ) theIndex;
- (NSString *) description;
@end
+728 -295
View File
File diff suppressed because it is too large. Load diff
+42 -5
View File
@@ -1,8 +1,8 @@
/* dbmsgreader.m - this file is part of SOGo
*
* Copyright (C) 2011-2012 Inverse inc
* 2015 Enrique J. Hernandez
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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
@@ -33,21 +33,58 @@
#import <SOGo/SOGoProductLoader.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <libmapi/libmapi.h>
#import "MAPIStoreUserContext.h"
#import <SOGo/SOGoCacheGCSObject.h>
#import <SOGo/BSONCodec.h>
#import "NSObject+PropertyList.h"
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK;
Class MAPIStoreUserContextK, SOGoCacheGCSObjectK, NSStringK;
static void
DumpBSONData(NSData *data)
{
id key, value;
NSEnumerator *dictEnum;
NSDictionary *dvalue;
NSMutableString *outStr;
NSUInteger max;
dvalue = [data BSONValue];
[dvalue displayWithIndentation:0];
printf("\n");
max = [dvalue count];
dictEnum = [dvalue keyEnumerator];
NSStringK = [NSString class];
outStr = [NSMutableString stringWithFormat: @"{ %d items\n", max];
while ((key = [dictEnum nextObject]))
{
uint32_t proptag = 0;
if ([key isKindOfClass: NSStringK] && [(NSString *)key intValue] > 0)
proptag = [(NSString *)key intValue];
if (proptag > 0)
{
const char *propTagName = get_proptag_name (proptag);
NSString *propName;
if (propTagName)
propName = [NSString stringWithCString: propTagName
encoding: NSUTF8StringEncoding];
else
propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]];
[outStr appendFormat: @" %@ = ", propName];
}
else
[outStr appendFormat: @" %@ = ", key];
value = [dvalue objectForKey: key];
[outStr appendFormat: @"(%@) %@,\n", NSStringFromClass ([value class]), value];
}
[outStr appendFormat: @"}\n"];
printf ("%s\n", [outStr UTF8String]);
}
static void
@@ -67,7 +104,7 @@ DbDumpObject (NSString *username, NSString *path)
{
printf("record found: %p\n", record);
content = [[record objectForKey: @"c_content"] dataByDecodingBase64];
DumpBSONData(content);
DumpBSONData (content);
}
else
NSLog (@"record not found");
+33 -32
View File
@@ -36,6 +36,7 @@
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalTrigger.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
@@ -70,11 +71,12 @@
#include <mapistore/mapistore_nameid.h>
#import "iCalEvent+MAPIStore.h"
#import "iCalTimeZone+MAPIStore.h"
@implementation iCalEvent (MAPIStoreProperties)
- (void) _setupEventRecurrence: (NSData *) mapiRecurrenceData
inTimeZone: (NSTimeZone *) tz
inTimeZone: (iCalTimeZone *) tz
inMemCtx: (TALLOC_CTX *) memCtx
{
struct Binary_r *blob;
@@ -250,10 +252,8 @@
BOOL isAllDay;
iCalDateTime *start, *end;
iCalTimeZone *tz;
NSTimeZone *userTimeZone;
NSString *priority, *class = nil;
NSString *priority, *class = nil, *tzDescription = nil;
NSUInteger responseStatus = 0;
NSInteger tzOffset;
SOGoUser *ownerUser;
id value;
@@ -274,7 +274,31 @@
[self setAccessClass: @"PUBLIC"];
}
userTimeZone = [userContext timeZone];
/* Time zone = PidLidAppointmentTimeZoneDefinitionRecur
or PidLidAppointmentTimeZoneDefinition[Start|End]Display */
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionStartDisplay)];
if (!value)
{
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionEndDisplay)];
if (!value)
{
/* If PidLidtimeZoneStruct, TZID SHOULD come from PidLidTimeZoneDescription,
if PidLidAppointmentTimeZoneDefinition[Start|End]Display it MUST be derived from KeyName
(MS-OXCICAL] 2.1.3.1.1.19.1) */
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionRecur)];
tzDescription = [properties objectForKey: MAPIPropertyKey (PidLidTimeZoneDescription)];
}
}
if (value)
{
tz = [[iCalTimeZone alloc] iCalTimeZoneFromDefinition: value
withDescription: tzDescription
inMemCtx: memCtx];
}
else
/* The client is more likely to have the webmail's time zone than any other */
tz = [iCalTimeZone timeZoneForName: [[userContext timeZone] name]];
[(iCalCalendar *) parent addTimeZone: tz];
/* CREATED */
value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)];
@@ -306,20 +330,13 @@
objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)];
if (value)
isAllDay = [value boolValue];
if (!isAllDay)
{
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[(iCalCalendar *) parent addTimeZone: tz];
}
else
tz = nil;
// recurrence-id
value
= [properties objectForKey: MAPIPropertyKey (PidLidExceptionReplaceTime)];
if (value)
[self setRecurrenceId: value];
// start
value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)];
if (!value)
@@ -330,15 +347,7 @@
[start setTimeZone: tz];
if (isAllDay)
{
/* when user TZ is positive (East) all-day events were not
shown properly in SOGo UI. This day delay fixes it */
tzOffset = [userTimeZone secondsFromGMTForDate: value];
if (tzOffset > 0)
{
value = [value dateByAddingYears: 0 months: 0 days: 1
hours: 0 minutes: 0
seconds: 0];
}
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
[start setDate: value];
[start setTimeZone: nil];
}
@@ -356,15 +365,7 @@
[end setTimeZone: tz];
if (isAllDay)
{
/* when user TZ is positive (East) all-day events were not
shown properly in SOGo UI. This day delay fixes it */
tzOffset = [userTimeZone secondsFromGMTForDate: value];
if (tzOffset > 0)
{
value = [value dateByAddingYears: 0 months: 0 days: 1
hours: 0 minutes: 0
seconds: 0];
}
/* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */
[end setDate: value];
[end setTimeZone: nil];
}
@@ -467,7 +468,7 @@
value = [properties
objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)];
if (value)
[self _setupEventRecurrence: value inTimeZone: userTimeZone inMemCtx: memCtx];
[self _setupEventRecurrence: value inTimeZone: tz inMemCtx: memCtx];
/* alarm */
[self _setupEventAlarmFromProperties: properties];
+5
View File
@@ -30,6 +30,11 @@
- (struct Binary_r *) asTimeZoneStructInMemCtx: (TALLOC_CTX *) memCtx;
- (struct Binary_r *) asZoneTimeDefinitionWithFlags: (enum TZRuleFlag) flags
inMemCtx: (TALLOC_CTX *) memCtx;
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
withDescription: (NSString *) description
inMemCtx: (TALLOC_CTX *) memCtx;
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date;
@end
+212 -5
View File
@@ -23,11 +23,15 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import <NGCards/iCalByDayMask.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalRecurrenceRule.h>
#import "NSString+MAPIStore.h"
#import "NSData+MAPIStore.h"
#import "NSDate+MAPIStore.h"
#include <stdbool.h>
#include <stdint.h>
@@ -36,6 +40,7 @@
#include <libmapi/libmapi.h>
#import "iCalTimeZone+MAPIStore.h"
#import "MAPIStoreTypes.h"
@interface iCalTimeZonePeriod (MAPIStorePropertiesPrivate)
@@ -103,18 +108,18 @@
{
iCalTimeZonePeriod *period;
struct TimeZoneStruct tz;
int lBias, dlBias;
int32_t lBias, dlBias;
memset (&tz, 0, sizeof (struct TimeZoneStruct));
period = [self _mostRecentPeriodWithName: @"STANDARD"];
lBias = -[period secondsOffsetFromGMT] / 60;
tz.lBias = (uint32_t) lBias;
tz.lBias = lBias;
[period _fillTZDate: &tz.stStandardDate];
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
if (!period)
tz.stStandardDate.wMonth = 0;
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
tz.lDaylightBias = (uint32_t) (dlBias);
tz.lDaylightBias = dlBias;
[period _fillTZDate: &tz.stDaylightDate];
tz.wStandardYear = tz.stStandardDate.wYear;
tz.wDaylightYear = tz.stDaylightDate.wYear;
@@ -153,18 +158,220 @@
period = [self _mostRecentPeriodWithName: @"STANDARD"];
rule.wYear = [[period startDate] yearOfCommonEra];
lBias = -[period secondsOffsetFromGMT] / 60;
rule.lBias = (uint32_t) lBias;
rule.lBias = lBias;
[period _fillTZDate: &rule.stStandardDate];
period = [self _mostRecentPeriodWithName: @"DAYLIGHT"];
if (!period)
rule.stStandardDate.wMonth = 0;
dlBias = -([period secondsOffsetFromGMT] / 60) - lBias;
rule.lDaylightBias = (uint32_t) (dlBias);
rule.lDaylightBias = dlBias;
[period _fillTZDate: &rule.stDaylightDate];
return set_TimeZoneDefinition (memCtx, &definition);
}
- (NSString *) _offsetStringFromOffset: (NSInteger) offset
{
NSInteger offsetHours, offsetMins;
NSString *offsetSign;
/* The offset format is, eg, "+0200" for 2 hours 0 minutes ahead */
if (offset < 0)
offsetSign = @"-";
else
offsetSign = @"+";
offsetHours = abs (offset) / 60;
offsetMins = abs (offset) % 60;
return [NSString stringWithFormat: @"%@%d%d%d%d",
offsetSign, offsetHours / 10, offsetHours % 10,
offsetMins / 10, offsetMins % 10];
}
- (NSString *) _rRuleStringFromSystemTime: (struct SYSTEMTIME) date
{
NSString *result, *byDay;
/* The conversion tables between the SYSTEMTIME fields and the RRULE ones
can be found at [MS-OXCICAL] 2.1.3.2.1 */
if (date.wDay == 5)
byDay = @"-1";
else
byDay = [NSString stringWithFormat: @"%d", date.wDay];
switch (date.wDayOfWeek)
{
case iCalWeekDaySunday:
byDay = [byDay stringByAppendingString: @"SU"];
break;
case iCalWeekDayMonday:
byDay = [byDay stringByAppendingString: @"MO"];
break;
case iCalWeekDayTuesday:
byDay = [byDay stringByAppendingString: @"TU"];
break;
case iCalWeekDayWednesday:
byDay = [byDay stringByAppendingString: @"WE"];
break;
case iCalWeekDayThursday:
byDay = [byDay stringByAppendingString: @"TH"];
break;
case iCalWeekDayFriday:
byDay = [byDay stringByAppendingString: @"FR"];
break;
case iCalWeekDaySaturday:
byDay = [byDay stringByAppendingString: @"SA"];
break;
}
result = [NSString stringWithFormat: @"FREQ=YEARLY;BYDAY=%@;BYMONTH=%d", byDay, date.wMonth];
return result;
}
- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value
withDescription: (NSString *) description
inMemCtx: (TALLOC_CTX *) memCtx
{
BOOL daylightDefined = NO, ruleFound = NO;
iCalDateTime *daylightStart, *standardStart;
iCalRecurrenceRule *daylightRRule, *standardRRule;
iCalTimeZone *tz = nil;
iCalTimeZonePeriod *daylight, *standard;
NSCalendarDate *dlStartValue, *stStartValue;
NSString *strOffsetFrom, *strOffsetTo, *tzID;
char *keyName;
struct Binary_r *binValue;
struct SYSTEMTIME initDate;
struct TimeZoneDefinition *definition;
struct TZRule rule;
uint16_t count;
binValue = [value asBinaryInMemCtx: memCtx];
definition = get_TimeZoneDefinition (memCtx, binValue);
if (!definition)
return nil;
if (!definition->cRules)
goto end;
for (count = 0; count < definition->cRules; count++)
{
/* ([MS-OXCICAL] 2.1.3.1.1.19) The TZRule with the
TZRULE_FLAG_EFFECTIVE_TZREG bit set in the TZRule flags field
is the one that MUST be exported */
if (definition->TZRules[count].flags & TZRULE_FLAG_EFFECTIVE_TZREG)
{
rule = definition->TZRules[count];
ruleFound = YES;
break;
}
}
if (!ruleFound)
goto end;
if (!description)
{
/* The cbHeader field contains the size, in bytes of the Reserved (2b),
cchKeyName (2b) keyName (variable Unicode string) and cRules (2b)
([MS-OXOCAL] 2.2.1.41). The keyName field is a non-NULL-terminated
char array. */
keyName = talloc_strndup (memCtx, definition->keyName, (definition->cbHeader - 6) / 2);
tzID = [NSString stringWithCString: keyName
encoding: [NSString defaultCStringEncoding]];
talloc_free (keyName);
}
else
tzID = [NSString stringWithString: description];
tz = [iCalTimeZone groupWithTag: @"vtimezone"];
[tz addChild: [CardElement simpleElementWithTag: @"tzid"
value: tzID]];
if (rule.stStandardDate.wMonth != 0)
daylightDefined = YES;
/* STANDARD TIME ([MS-OXCICAL] 2.1.3.1.1.19.2) */
standard = [iCalTimeZonePeriod groupWithTag: @"standard"];
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
strOffsetFrom = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lDaylightBias)];
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
value: strOffsetFrom]];
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
strOffsetTo = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lStandardBias)];
[standard addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
value: strOffsetTo]];
/* DTSTART & RRULE are derived from the stStandardDate and wYear properties */
standardStart = [iCalDateTime elementWithTag: @"dtstart"];
initDate = rule.stStandardDate;
stStartValue = [NSCalendarDate dateFromSystemTime: initDate
andRuleYear: rule.wYear];
[standardStart setDateTime: stStartValue];
[standard addChild: standardStart];
if (daylightDefined)
{
standardRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
[standard addChild: standardRRule];
/* DAYLIGHT SAVING TIME ([MS-OXCICAL] 2.1.3.1.1.19.3) */
daylight = [iCalTimeZonePeriod groupWithTag: @"daylight"];
/* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom"
value: strOffsetTo]];
/* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */
[daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetto"
value: strOffsetFrom]];
/* DTSTART & RRULE are derived from the stDaylightDate and wYear properties */
daylightStart = [iCalDateTime elementWithTag: @"dtstart"];
initDate = rule.stDaylightDate;
dlStartValue = [NSCalendarDate dateFromSystemTime: initDate
andRuleYear: rule.wYear];
[daylightStart setDateTime: dlStartValue];
[daylight addChild: daylightStart];
daylightRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]];
[daylight addChild: daylightRRule];
[tz addChild: daylight];
}
[tz addChild: standard];
end:
talloc_free (definition);
return tz;
}
/**
* Adjust a date in this vTimeZone to its representation in UTC
* Example: Timezone is +0001, the date is 2015-12-15 00:00:00 +0000
* it returns 2015-12-14 23:00:00 +0000
* @param date the date to adjust to the timezone.
* @return a new GMT date adjusted with the offset of the timezone.
*/
- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date
{
NSCalendarDate *tmpDate;
tmpDate = [date copy];
[tmpDate autorelease];
[tmpDate setTimeZone: utcTZ];
return [tmpDate addYear: 0 month: 0 day: 0
hour: 0 minute: 0
second: -[[self periodForDate: tmpDate] secondsOffsetFromGMT]];
}
@end
+1 -1
View File
@@ -500,7 +500,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60;
ms = [NSMutableString stringWithCapacity: 256];
[ms appendFormat: @"<0x%p[%@]: ", self, NSStringFromClass ([self class])];
[ms appendFormat: @" #adaptors=%d", [urlToAdaptor count]];
[ms appendFormat: @" #adaptors=%d", (int)[urlToAdaptor count]];
[ms appendString: @">"];
return ms;
+1 -1
View File
@@ -414,7 +414,7 @@
if (group)
[str appendFormat: @"%@ (group: %@)\n", tag, group];
else
[str appendFormat: @"%@\n", tag, group];
[str appendFormat: @"%@\n", tag];
[str appendString: [self versitString]];
+1 -1
View File
@@ -409,7 +409,7 @@ static NGCardsSaxHandler *sax = nil;
max = [children count];
if (max > 0)
{
[str appendFormat: @"\n %d children: {\n", [children count]];
[str appendFormat: @"\n %d children: {\n", (int)[children count]];
for (count = 0; count < max; count++)
[str appendFormat: @" %@\n",
[[children objectAtIndex: count] description]];
+5 -5
View File
@@ -32,16 +32,16 @@
- (NSString *) iCalFormattedDateTimeString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2dT%.2d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth], [self hourOfDay],
[self minuteOfHour], [self secondOfMinute]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth], (int)[self hourOfDay],
(int)[self minuteOfHour], (int)[self secondOfMinute]];
}
- (NSString *) iCalFormattedDateString
{
return [NSString stringWithFormat: @"%.4d%.2d%.2d",
[self yearOfCommonEra], [self monthOfYear],
[self dayOfMonth]];
(int)[self yearOfCommonEra], (int)[self monthOfYear],
(int)[self dayOfMonth]];
}
@end
+2 -2
View File
@@ -53,10 +53,10 @@
[aString appendString: @","];
subValue = [self objectAtIndex: count];
/* We MUST quote attribute values that have a ":" in them
/* We MUST quote attribute values that have a ":" or "," in them
and that not already quoted */
if (asAttributes && [subValue length] > 2
&& [subValue rangeOfString: @":"].length
&& ([subValue rangeOfString: @":"].length || [subValue rangeOfString: @","].length)
&& [subValue characterAtIndex: 0] != '"'
&& ![subValue hasSuffix: @"\""])
subValue = [NSString stringWithFormat: @"\"%@\"", subValue];
+15
View File
@@ -158,16 +158,31 @@
NSMutableString *string;
unsigned int len, i;
unichar c;
BOOL isQuoted;
len = [self length];
string = [NSMutableString stringWithCapacity: len * 1.5];
isQuoted = NO;
for (i = 0; i < len; i++)
{
c = [self characterAtIndex: i];
if (isQuoted)
{
if (c == '"')
isQuoted = NO;
[string appendFormat: @"%C", c];
continue;
}
switch (c)
{
case '"':
isQuoted = YES;
[string appendFormat: @"%C", c];
break;
case '\\':
[string appendString: @"\\\\"];
break;
+5 -1
View File
@@ -27,6 +27,7 @@
#import "NSCalendarDate+NGCards.h"
#import "iCalAlarm.h"
#import "iCalCalendar.h"
#import "iCalDateTime.h"
#import "iCalEntityObject.h"
#import "iCalEvent.h"
@@ -268,9 +269,12 @@
- (void) setRecurrenceId: (NSCalendarDate *) newRecId
{
iCalDateTime* recurrenceId;
BOOL isMasterAllDay;
isMasterAllDay = [[[[self parent] events] objectAtIndex: 0] isAllDay];
recurrenceId = (iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"];
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
if ([self isKindOfClass: [iCalEvent class]] && isMasterAllDay)
[recurrenceId setDate: newRecId];
else
[recurrenceId setDateTime: newRecId];
@@ -348,7 +348,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES;
currentPos++;
}
@@ -362,7 +362,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", currentPos]])
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES;
currentPos--;
}
+1 -1
View File
@@ -256,7 +256,7 @@
- (BOOL)isEqual:(id)_other {
if(_other == nil)
return NO;
if([_other class] != self->isa)
if([_other class] != object_getClass(self))
return NO;
if([_other hash] != [self hash])
return NO;
+2 -2
View File
@@ -309,10 +309,10 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
else if ([frequency isEqualToString:@"SECONDLY"])
freq = iCalRecurrenceFrequenceSecondly;
else
freq = NSNotFound;
freq = (iCalRecurrenceFrequency)NSNotFound;
}
else
freq = NSNotFound;
freq = (iCalRecurrenceFrequency)NSNotFound;
return freq;
}
@@ -1045,7 +1045,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to decode data (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _data, [_data length]);
__PRETTY_FUNCTION__, _data, (int)[_data length]);
}
if ((len = [_data length]) == 0)
@@ -1164,7 +1164,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
if (debugOn)
{
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _source, [_source length]);
__PRETTY_FUNCTION__, _source, (int)[_source length]);
}
if (!_sysId) _sysId = @"<string>";
[self _parseString: _source];
-309
View File
@@ -1,309 +0,0 @@
#!/usr/bin/env python
import getopt
import imaplib
import ldb
import plistlib
import os
import re
import shutil
import subprocess
import sys
from samba.param import LoadParm
imaphost = '127.0.0.1'
imapport = 143
samba_lp = LoadParm()
sambaprivate = samba_lp.get("private dir")
mapistorefolder = samba_lp.private_path("mapistore")
sogoSysDefaultsFile = "/etc/sogo/sogo.conf"
sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaults")
# - takes a username and optionally its password
# - removes the entry in samba's ldap tree via ldbedit (NOTYET)
# - remove the user's directory under mapistore/ and mapistore/SOGo
# - cleanup Junk Folders and Sync Issues imap folders
# - Delete the sogo_cache_folder_ table for the username.
def usage():
print """
%s [-i imaphost] ] [-p imapport] [-s sambaprivate] username [password]
-i imaphost IMAP host to connect to [%s]
-p imappost IMAP port to use [%d]
-s sambaprivate samba private directory [%s]
""" % (os.path.basename(sys.argv[0]), imaphost, imapport, sambaprivate)
def main():
global sambaprivate
global mapistorefolder
global imaphost
global imapport
try:
opts, args = getopt.getopt(sys.argv[1:], "i:p:s:")
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for o, a in opts:
if o == "-i":
imaphost = a
elif o == "-p":
imapport = a
elif o == "-s":
sambaprivate = a
mapistorefolder = "%s/mapistore" % (sambaprivate)
else:
assert False, "unhandled option"
argslen = len(args)
if (argslen == 2):
username = args[0]
userpass = args[1]
elif (argslen == 1):
username = args[0]
userpass = username
print "Using username as password"
else:
usage()
print "Specify a user (and optionally the password)"
sys.exit(2)
# cleanup starts here
try:
imapCleanup(imaphost, imapport, username, userpass)
except Exception as e:
print "Error during imapCleanup, continuing: %s" % str(e)
try:
mapistoreCleanup(mapistorefolder, username)
except (shutil.Error, OSError) as e:
print "Error during mapistoreCleanup, continuing: %s" % str(e)
try:
ldbCleanup(sambaprivate, username)
except ldb.LdbError as e:
print "Error during ldbCleanup, continuing: %s" % str(e)
try:
sqlCleanup(username)
except Exception as e:
print "Error during sqlCleanup, continuing: %s" % str(e)
def getsep(client):
seq = None
(code, data) = client.list("", "")
if code == "OK" and data is not None:
# yes this is ugly but it works on cyrus and dovecot.
# (\\Noinferiors \\HasNoChildren) "/" INBOX
m = re.search(".*\s+[\"\']?(.)[\"\']?\s+[\"\']?.*[\"\']?$", data[0])
sep = m.group(1)
return sep
def extractmb(si):
inparen = False
inquote = False
part = []
parts = []
for char in si:
if inparen:
if char == ")":
inparen = False
parts.append("".join(part))
else:
part.append(char)
elif inquote:
if char == "\"":
inquote = False
parts.append("".join(part))
else:
part.append(char)
else:
if char == "(":
inparen = True
elif char == "\"":
inquote = True
elif char == " ":
part = []
else:
part.append(char)
return parts[-1]
def cleanupmb(mb, sep, client):
(code, data) = client.list("%s%s" % (mb, sep), "%")
if code == "OK":
for si in data:
if si is not None:
submb = extractmb(si)
cleanupmb(submb, sep, client)
else:
raise Exception, "Failure while cleaning up '%s'" % mb
client.unsubscribe(mb)
(code, data) = client.delete(mb)
if code == "OK":
print "mailbox '%s' deleted" % mb
else:
print "mailbox '%s' coult NOT be deleted (code = '%s')" % (mb, code)
def imapCleanup(imaphost, imapport, username, userpass):
print "Starting IMAP cleanup"
client = imaplib.IMAP4(imaphost, imapport)
(code, data) = client.login(username, userpass)
if code != "OK":
raise Exception, "Login failure"
print "Logged in as '%s'" % username
sep = getsep(client)
if not sep:
client.logout()
return
for foldername in ("Sync Issues", "Junk E-mail",
"INBOX%sSync Issues" % sep, "INBOX%sJunk E-mail" % sep,
"Probl&AOg-mes de synchronisation"):
(code, data) = client.list(foldername, "%")
if code == "OK":
for si in data:
if si is not None:
mb = extractmb(si)
cleanupmb(mb, sep, client)
client.logout()
def mapistoreCleanup(mapistorefolder, username):
print "Starting MAPIstore cleanup"
# delete the user's folder under the mapistore and under mapistore/SOGo
mapistoreUserDir = "%s/%s" % (mapistorefolder, username)
for dirpath, dirnames, filenames in os.walk(mapistoreUserDir):
for f in filenames:
if f != "password":
os.unlink("%s/%s" % (dirpath,f))
break #one level only
shutil.rmtree("%s/SOGo/%s" % (mapistorefolder, username), ignore_errors=True)
def ldbCleanup(sambaprivate, username):
conn = ldb.Ldb("%s/openchange.ldb" % (sambaprivate))
# find the DN of the user
entries = conn.search(None, expression="(cn=%s)" % (username), scope=ldb.SCOPE_SUBTREE)
if not entries:
print "cn = %s not found in openchange.ldb" %(username)
return
for entry in entries:
# search again, but with the user's DN as a base
subentries = conn.search(entry.dn.extended_str(), expression="(distinguishedName=*)", scope=ldb.SCOPE_SUBTREE)
for subentry in subentries:
print "Deleting %s" % (subentry.dn)
conn.delete(subentry.dn)
def mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import MySQLdb
except ImportError:
msg ="""The python 'MySQLdb' module is not available
On Debian based distro, install it using 'apt-get install python-mysqlbd'
On RHEL, install it using 'yum install MySQL-python'"""
raise Exception(msg)
conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname)
c=conn.cursor()
tablename="sogo_cache_folder_%s" % (username)
c.execute("TRUNCATE TABLE %s" % tablename)
print "Table %s emptied" % tablename
def postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, username):
try:
import pg
except ImportError:
msg ="""The python 'pg' module is not available
On Debian based distro, install it using 'apt-get install python-pygresql'
On RHEL, install it using 'yum install python-pgsql'"""
raise Exception(msg)
conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname)
tablename = "sogo_cache_folder_%s" % username
conn.query("DELETE FROM %s" % tablename)
print "Table '%s' emptied" % tablename
def getOCSFolderInfoURL():
global sogoSysDefaultsFile, sogoUserDefaultsFile
OCSFolderInfoURL = ""
# read defaults from defaults files
# order is important, user defaults must have precedence
for f in [sogoSysDefaultsFile, sogoUserDefaultsFile]:
if os.path.exists(f):
# FIXME: this is ugly, we should have a python plist parser
# plistlib only supports XML plists.
# the following magic replaces this shell pipeline:
# sogo-tool dump-defaults -f %s | awk -F\\" '/ OCSFolderInfoURL =/ {print $2}'
p1 = subprocess.Popen(["sogo-tool", "dump-defaults", "-f", f], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk", "-F\"", "/ OCSFolderInfoURL =/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
tmp = p2.communicate()[0]
if tmp: OCSFolderInfoURL = tmp
return OCSFolderInfoURL
def asCSSIdentifier(inputString):
cssEscapingCharMap = {"_" : "_U_",
"." : "_D_",
"#" : "_H_",
"@" : "_A_",
"*" : "_S_",
":" : "_C_",
"," : "_CO_",
" " : "_SP_",
"'" : "_SQ_",
"&" : "_AM_",
"+" : "_P_"}
newChars = []
for c in inputString:
if c in cssEscapingCharMap:
newChars.append(cssEscapingCharMap[c])
else:
newChars.append(c)
return "".join(newChars)
def sqlCleanup(username):
print "Starting SQL cleanup"
OCSFolderInfoURL = getOCSFolderInfoURL()
if OCSFolderInfoURL is None:
raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username))
# postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info
m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL)
if not m:
raise Exception("Unable to parse OCSFolderInfoURL: %s" % OCSFolderInfoURL)
proto = m.group(1)
dbuser = m.group(2)
dbpass = m.group(3)
dbhost = m.group(4)
dbport = m.group(5)
dbname = m.group(6)
# 7 is folderinfo table
encodedUserName = asCSSIdentifier(username)
if (proto == "postgresql"):
postgresqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
elif (proto == "mysql"):
mysqlCleanup(dbhost, dbport, dbuser, dbpass, dbname, encodedUserName)
else:
raise Exception("Unknown sql proto: %s" % (proto))
if __name__ == "__main__":
main()
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+59 -18
View File
@@ -6,20 +6,52 @@ import re
DEBUG=False
dir_mappings = {"../UI/Templates":"../UI/Common",
"../UI/Templates/AdministrationUI":"../UI/AdministrationUI",
"../UI/Templates/Appointments":"../SoObjects/Appointments",
"../UI/Templates/ContactsUI":"../UI/Contacts",
"../UI/Templates/MailerUI":"../UI/MailerUI",
"../UI/Templates/MailPartViewers":"../UI/MailPartViewers",
"../UI/Templates/MainUI":"../UI/MainUI",
"../UI/Templates/PreferencesUI":"../UI/PreferencesUI",
"../UI/Templates/SchedulerUI":"../UI/Scheduler"
}
dir_mappings = {
# .wox
"../UI/Templates":"../UI/Common",
"../UI/Templates/AdministrationUI":"../UI/AdministrationUI",
"../UI/Templates/Appointments":"../SoObjects/Appointments",
"../UI/Templates/ContactsUI":"../UI/Contacts",
"../UI/Templates/MailerUI":"../UI/MailerUI",
"../UI/Templates/MailPartViewers":"../UI/MailPartViewers",
"../UI/Templates/MainUI":"../UI/MainUI",
"../UI/Templates/PreferencesUI":"../UI/PreferencesUI",
"../UI/Templates/SchedulerUI":"../UI/Scheduler",
# .toolbars
"../UI/AdministrationUI/Toolbars":"../UI/AdministrationUI",
"../UI/Contacts/Toolbars":"../UI/Contacts",
"../UI/MailerUI/Toolbars":"../UI/MailerUI",
"../UI/PreferencesUI/Toolbars":"../UI/PreferencesUI",
"../UI/Scheduler/Toolbars":"../UI/Scheduler",
# .js
"../UI/WebServerResources/generic.js":"../UI/Common",
"../UI/WebServerResources/UIxAclEditor.js":"../UI/Common",
"../UI/WebServerResources/SOGoRootPage.js":"../UI/MainUI",
"../UI/WebServerResources/SOGoRootPage.js":"../UI/MainUI",
"../UI/WebServerResources/PasswordPolicy.js":"../UI/AdministrationUI",
"../UI/WebServerResources/ContactsUI.js":"../UI/Contacts",
"../UI/WebServerResources/UIxContactEditor.js":"../UI/Contacts",
"../UI/WebServerResources/UIxContactsUserFolders.js":"../UI/Contacts",
"../UI/WebServerResources/UIxContactsUserRightsEditor.js":"../UI/Contacts",
"../UI/WebServerResources/MailerUI.js":"../UI/MailerUI",
"../UI/WebServerResources/MailerUIdTree.js":"../UI/MailerUI",
"../UI/WebServerResources/UIxMailEditor.js":"../UI/MailerUI",
"../UI/WebServerResources/UIxMailSearch.js":"../UI/MailerUI",
"../UI/WebServerResources/UIxMailUserRightsEditor.js":"../UI/MailerUI",
"../UI/WebServerResources/UIxPreferences.js":"../UI/PreferencesUI",
"../UI/WebServerResources/UIxFilterEditor.js":"../UI/PreferencesUI",
"../UI/WebServerResources/SchedulerUI.js":"../UI/Scheduler",
"../UI/WebServerResources/UIxCalUserRightsEditor.js":"../UI/Scheduler",
"../UI/WebServerResources/UIxCalViewPrint.js":"../UI/Scheduler",
}
def get_translations(path):
def get_translations(path, filename):
try:
transpath = dir_mappings.get(path, path)
transpath = dir_mappings.get(path)
if not transpath:
transpath = dir_mappings.get(path + '/' + filename)
if not transpath:
transpath = path
transname = transpath + '/English.lproj/Localizable.strings'
transall = open(transname).read()
except:
@@ -29,6 +61,8 @@ def get_translations(path):
def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=()):
for path, dirs, files in os.walk(rootdir):
if (os.path.basename(path) in greylist):
continue
filelist = [f for f in files if f[(-1 * len(extention)):] == extention]
if filelist:
for filename in filelist:
@@ -40,7 +74,7 @@ def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=(
values = [r.groups()[0] for r in [recomp.search(l) for l in lines] if r]
if values:
#- Get the current english translations for the path
transgood = get_translations(path)
transgood = get_translations(path, filename)
if not transgood:
print "No translation file found for %s, skipping %s" % (path, pathname)
continue
@@ -53,7 +87,8 @@ def find_missing_translations(rootdir='.', extention='', recomp=None, greylist=(
if found:
if DEBUG: print "\t", '[%s] FOUND -- "%s"' % found.groups()
else:
notfound.append("-->\t[%s] ==== Not Found ====" % value)
#notfound.append("-->\t[%s] ==== Not Found ====" % value)
notfound.append("""-->\t"%s" = "%s";""" % (value, value))
if notfound:
if not DEBUG:print pathname
print "\n".join(notfound)
@@ -69,14 +104,20 @@ def main():
print 'Usage:', sys.argv[0], '[-g]\n\t\t-g: debug will show matching also'
sys.exit(1)
greylist = ('UIxFilterEditor.wox')
#- Get only the label:value from all lines
recomp = re.compile(' label:[^=]*="(.*?)"')
find_missing_translations('../UI', 'wox', recomp, greylist)
recomp = re.compile('.label:[^=]*="([^$].*?)"')
find_missing_translations('../UI', 'wox', recomp, ())
#- [self labelForKey: @"Issuer"]
recomp = re.compile('\[self labelForKey: @"(.*?)"\]')
find_missing_translations('../UI', 'm', recomp, ())
#- tooltip = "Switch to day view"
recomp = re.compile(' tooltip = "(.*?)";')
find_missing_translations('../UI', 'toolbar', recomp, ('Resources'))
#- _("Reminder")
recomp = re.compile(' _\("(.*?)"\)')
find_missing_translations('../UI/WebServerResources', 'js', recomp, ())
main()
@@ -6,7 +6,6 @@ vevent_class2 = "(حدث سري)";
vtodo_class0 = "(مهمة عامة)";
vtodo_class1 = "(مهمة خاصة)";
vtodo_class2 = "(مهمة سرية)";
/* Receipts */
"The event \"%{Summary}\" was created" = "أُنشِئ الحدث \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "حُذِفَ الحدث \"%{Summary}\"";
@@ -14,7 +13,6 @@ vtodo_class2 = "(مهمة سرية)";
"The following attendees(s) were notified" = "تم ابلاغ المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were added" = "تم أضافة المدعو (المدعويين) الأتي أسماءهم";
"The following attendees(s) were removed" = "تم مسح المدعو (المدعويين) الأتي أسماءهم";
/* IMIP messages */
"calendar_label" = "التقويم";
"startDate_label" = "البداية";
@@ -23,20 +21,17 @@ vtodo_class2 = "(مهمة سرية)";
"location_label" = "المكان";
"summary_label" = "الملخص:";
"comment_label" = "التعليق:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "دعوة الحدث: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(أرسلت بواسطة٪ {SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "وقد دعت%{Organizer} %{SentByText} الى %{Summary} . ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate}⏎\nالوصف:%{Description}\n";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}قام بدعوتك %{Summary}. ⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}\n";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "الحدث الغي : \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "ألغى%{Organizer} %{SentByText} هذا الحدث:.%{Summary} ⏎\n⏎\nبداية:٪ {StartDate} ⏎\nنهاية:%{EndDate} ⏎\nالوصف:%{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}قام بإلغاء هذا الحدث: %{Summary}.⏎\n⏎\nبداية:٪ {StartDate} في {٪ StartTime} ⏎\nالغاية: {٪ EndDate} في {٪ EndTime} ⏎\nالوصف:%{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "موعد \"%{Summary}\" بتاريخ %{OldStartDate} قد تغير";
@@ -46,7 +41,6 @@ vtodo_class2 = "(مهمة سرية)";
= " لقد تغيرت المحددات التالية في الاجتماع \"%{Summary}\":";
"Please accept or decline those changes."
= "يرجى قبول أو رفض تلك التغييرات.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "دعوةٌ مقبولة: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "دعوةٌ مرفوضة: \"%{Summary}\"";
@@ -60,7 +54,6 @@ vtodo_class2 = "(مهمة سرية)";
= "وقد فوض%{Attendee} %{SentByText} الدعوة إلى %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} لم يقرر حتى الآن حضورهذا الحدث الخاص بك.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "لا يمكن الوصول إلى الموارد: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "الحد الأقصى لعدد الحجوزات في وقت واحد (%{NumberOfSimultaneousBookings}) وصلت للموارد \"%{Cn} %{SystemEmail}\" . الحدث المتضارب في الموعد هو \"٪ {EventTitle}\"، ويبدأ فى٪ {StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Isilpeko ekitaldia)";
vtodo_class0 = "(Zeregin publikoa)";
vtodo_class1 = "(Zeregin pribatua)";
vtodo_class2 = "(Isilpeko egitekoa)";
/* Receipts */
"The event \"%{Summary}\" was created" = "\"%{Summary}\" ekitaldia sortu da";
"The event \"%{Summary}\" was deleted" = "\"%{Summary}\" ekitaldia ezabatu da";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
"The following attendees(s) were notified" = "Ondorengo partaidea(k) jakinarazi d(ir)a";
"The following attendees(s) were added" = "Ondorengo partaidea(k) gehitu d(ir)a";
"The following attendees(s) were removed" = "Ondorengo partaidea(k) ezabatu d(ir)a";
/* IMIP messages */
"calendar_label" = "Egutegia";
"startDate_label" = "Hasi";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Isilpeko egitekoa)";
"location_label" = "Kokapena";
"summary_label" = "Laburpena:";
"comment_label" = "Iruzkina:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Ekitaldirako gonbidapena: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy}-k bidalia) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}.-ra\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDeskribapena: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}-k gonbidatu zaitu %{Summary}-ra \n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nEnd: %{EndDate}-an, %{EndTime}-tan\nDeskribapena: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Ekitaldia bertan behera utzi da: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} ekitaldi hau bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}\nAmaiera: %{EndDate}\nDEskribapena: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}-k honako ekitaldia bertan behera utzi du: %{Summary}.\n\nHasiera: %{StartDate}-an, %{StartTime}-tan\nAmaiera: %{EndDate}-an %{EndTime}-tan\nDeskribapena: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "%{OldStartDate} eguneko \"%{Summary}\" hitzordua aldatu da";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= " \"%{Summary}\" bilerako ondorengo parametroak aldatu dira:";
"Please accept or decline those changes."
= "Mesedez, onartu edo ezetsi ondorengo aldaketak.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Gonbidapena onartua: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Ezetzitako gonbidapena: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Isilpeko egitekoa)";
= "%{Attendee} %{SentByText}-k zure gonbidapena %{Delegate}-ri delegatu dio.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}-k ez du oraindik zure gonbidapenari buruz erabakirik hartu.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ezin da honako baliabidea atzitu: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" baliabidearentzako gehienezko erreserba kopurura (%{NumberOfSimultaneousBookings}) iritsi gara. Gatazka sortu duen ekitaldia \"%{EventTitle}\" da, eta %{StartDate}-n hasten da.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Evento Confidencial)";
vtodo_class0 = "(Tarefa Pública)";
vtodo_class1 = "(Tarefa Privada)";
vtodo_class2 = "(Tarefa Confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "O evento \"%{Summary}\" foi criado";
"The event \"%{Summary}\" was deleted" = "O evento \"%{Summary}\" foi removido";
@@ -15,29 +14,25 @@ vtodo_class2 = "(Tarefa Confidencial)";
"The following attendees(s) were notified" = "Estes participantes foram notificados";
"The following attendees(s) were added" = "Estes participantes foram adicionados";
"The following attendees(s) were removed" = "Estes participantes foram removidos";
/* IMIP messages */
"calendar_label" = "Calendário";
"startDate_label" = "Início";
"endDate_label" = "Fim";
"due_label" = "Data de Vencimento:";
"location_label" = "Local";
"location_label" = "Localização";
"summary_label" = "Resumo:";
"comment_label" = "Comentário:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Convite do Evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviado por %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidou você para %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} convidou você para %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento Cancelado: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFim: %{EndDate}\nDescrição: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInício: %{StartDate} as %{StartTime}\nFim: %{EndDate} as %{EndTime}\nDescrição: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "O compromisso \"%{Summary}\" de %{OldStartDate} mudou";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "Os seguintes parâmetros mudaram na reunião \"%{Summary}\" :\n\n";
"Please accept or decline those changes."
= "Por favor, aceitar ou recusar as alterações.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Convite aceito: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Convite recusado: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tarefa Confidencial)";
= "%{Attendee} %{SentByText} delegou o convite para %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}foi ainda não decidiu seu convite ao evento.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Não foi possível acessar o recurso: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "O número máximo de reservas simultaneas (%{NumberOfSimultaneousBookings}) acabou para o recurso \"%{Cn} %{SystemEmail}\". O evento conflitante é \"%{EventTitle}\", e inicia em %{StartDate}.";
@@ -7,37 +7,32 @@ vevent_class2 = "(Esdeveniment confidencial)";
vtodo_class0 = "(Tasca pública)";
vtodo_class1 = "(Tasca privada)";
vtodo_class2 = "(Tasca confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Es va crear l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was deleted" = "Es va esborrar l'esdeveniment \"%{Summary}\" ";
"The event \"%{Summary}\" was updated" = "Es va modificar l'esdeveniment \"%{Summary}\" ";
"The following attendees(s) were notified" = "I va notificar als següents participants ";
"The following attendees(s) were added" = "Es van agregar els següents participants ";
"The following attendees(s) were added" = "S'han afegit els següents participants";
"The following attendees(s) were removed" = "Heu suprimit els assistents següents";
/* IMIP messages */
"calendar_label" = "Calendari";
"startDate_label" = "Inici";
"endDate_label" = "Final";
"due_label" = "Data límit:";
"due_label" = "Data de venciment";
"location_label" = "Lloc";
"summary_label" = "Resum:";
"comment_label" = "Comentari:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitació a l'esdeveniment: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviada per %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} us ha invitat a %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Esdeveniment suspès: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}ha suspès aquest esdeveniment: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "La cita \"%{Summary}\" per al dia %{OldStartDate} ha canviat";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "Els paràmetres següents han canviat en la reunió \"%{Summary}\":";
"Please accept or decline those changes."
= "Si us plau, accepteu o rebutgeu aquests canvis.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitació acceptada: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Invitació rebutjada; \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tasca confidencial)";
= "%{Attendee} %{SentByText}ha delegat la invitació en %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}encara no s'ha decidit sobre la invitació a l'esdeveniment.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "No es pot accedir al recurs: \"%{Cn} %{SystemEmail}\" ";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Nombre màxim de reserves simultànies (%{NumberOfSimultaneousBookings}) assolit per al recurs \"%{Cn} %{SystemEmail}\". L'esdeveniment que entra en conflicte és \"%{EventTitle}\", i comença el %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(機密事件)";
vtodo_class0 = "(公開任務)";
vtodo_class1 = "(私人任務)";
vtodo_class2 = "(機密任務)";
/* Receipts */
"The event \"%{Summary}\" was created" = "事件 \"%{Summary}\" 已建立";
"The event \"%{Summary}\" was deleted" = "事件\"%{Summary}\" 已刪除";
@@ -15,7 +14,6 @@ vtodo_class2 = "(機密任務)";
"The following attendees(s) were notified" = "已通知下列出席者";
"The following attendees(s) were added" = "已增加下列出席者";
"The following attendees(s) were removed" = "已移除下列出席者";
/* IMIP messages */
"calendar_label" = "行事曆";
"startDate_label" = "開始";
@@ -24,20 +22,17 @@ vtodo_class2 = "(機密任務)";
"location_label" = "地點";
"summary_label" = "事件主題:";
"comment_label" = "備註:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "邀請事件: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(來自 %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} 邀請您參加 %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "取消事件 : \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} 己取消事件: %{Summary}。\n\n起始日期: %{StartDate}\n結束日期: %{EndDate}\n備註說明: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} 己取消了事件: %{Summary}。\n\n起始時間: %{StartDate} %{StartTime}\n結束時間: %{EndDate} %{EndTime}\n備註說明: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= " 安排於%{OldStartDate} 的事件 \"%{Summary}\" 已變更";
@@ -47,7 +42,6 @@ vtodo_class2 = "(機密任務)";
= "已變更會議 \"%{Summary}\" 下列參數:";
"Please accept or decline those changes."
= "請接受或拒絶這些變更。";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "接受邀請: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "拒絶邀請: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(機密任務)";
= "\"%{Attendee} %{SentByText}已將您的邀請委任給%{Delegate}。";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}還沒有決定是否接受您的邀請。";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "無法讀取資源: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" 的衝突事件己達上限 ( %{NumberOfSimultaneousBookings} )。衝突事件 \"%{EventTitle}\" 的起始日期為 %{StartDate}。";
@@ -0,0 +1,60 @@
"Inviting the following persons is prohibited:" = "Pozivanje slijedeće osobe je zabranjeno:";
"Personal Calendar" = "Osobni kalendar";
vevent_class0 = "(Javni događaj)";
vevent_class1 = "(Osobni događaj)";
vevent_class2 = "(Povjerljivi događaj)";
vtodo_class0 = "(Javni zadatak)";
vtodo_class1 = "(Osobni zadatak)";
vtodo_class2 = "(Povjerljivi zadatak)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Događaj \"% {Summary}\" je stvoren";
"The event \"%{Summary}\" was deleted" = "Događaj \"% {Summary}\" je izbrisan";
"The event \"%{Summary}\" was updated" = "Događaj \"% {Summary}\" je ažuriran";
"The following attendees(s) were notified" = "Sljedeći sudionici su obaviješteni";
"The following attendees(s) were added" = "Sljedeći sudionici su dodani";
"The following attendees(s) were removed" = "Sljedeći sudionici su uklonjeni";
/* IMIP messages */
"calendar_label" = "Kalendar";
"startDate_label" = "Start";
"endDate_label" = "Kraj";
"due_label" = "Datum dospijeća";
"location_label" = "Mjesto";
"summary_label" = "Sažetak";
"comment_label" = "Komentar";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Pozivnica: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(poslao %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vas je pozvao na %{Summary}.\n\nPočetak: %{StartDate}\nKraj: %{EndDate}\nOpis: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vas je pozvao na %{Summary}.\n\nPočetak: %{StartDate} at %{StartTime}\nKraj: %{EndDate} at %{EndTime}\nOpis: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Događaj otkazan: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}je otkazao ovaj događaj: %{Summary}.\n\nPočetak: %{StartDate}\nKraj: %{EndDate}\nOpis: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}je otkazao ovaj događaj: %{Summary}.\nPočetak: %{StartDate} at %{StartTime}\nKraj: %{EndDate} at %{EndTime}\nOpis: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Sastanak \"%{Summary}\" za %{OldStartDate} je promjenjen";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "Sastanak \"%{Summary}\" iz %{OldStartDate} u %{OldStartTime} je promjenjen";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "Sljedeći parametri su se promijenili u \"%{Summary}\" sastanak:";
"Please accept or decline those changes."
= "Molim prihvatite ili odbacite promjene.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Prihvaćen poziv:: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Odbijen poziv:: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Proslijediti poziv: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Još nije odlučeno o pozivu: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}je prihvatio vaš poziv događaj.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText}je odbio pozivnicu za događaj.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText}je proslijedio poziv za %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}još nije odlučio o vašem pozivu za događaj.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ne mogu pristupiti: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimalni broj istovremenih rezervacija (%{NumberOfSimultaneousBookings}) dosegnut je za \"%{Cn} %{SystemEmail}\". Problematični događaj je \"%{EventTitle}\" i počinje %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Skrytá událost)";
vtodo_class0 = "(Veřejný úkol)";
vtodo_class1 = "(Soukromý úkol)";
vtodo_class2 = "(Skrytý úkol)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Událost \"%{Summary}\" byla vytvořena";
"The event \"%{Summary}\" was deleted" = "Událost \"%{Summary}\" byla smazána";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Skrytý úkol)";
"The following attendees(s) were notified" = "Následující účastníci byli upozorněni";
"The following attendees(s) were added" = "Následující účastníci byli přidáni";
"The following attendees(s) were removed" = "Následující účastníci byli odebráni";
/* IMIP messages */
"calendar_label" = "Kalendář";
"startDate_label" = "Začátek";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Skrytý úkol)";
"location_label" = "Místo";
"summary_label" = "Název:";
"comment_label" = "Komentář:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Pozvání na událost: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(poslal/a %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Zrušení události: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Schůzka \"%{Summary}\" na %{OldStartDate} byla změněna";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Skrytý úkol)";
= "Následující parametry byly ve schůzce \"%{Summary}\" změněny:";
"Please accept or decline those changes."
= "Prosím přijměte nebo odmítněte tyto změny.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Přijaté pozvání: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Odmítnuté pozvání: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Skrytý úkol)";
= "%{Attendee} %{SentByText}delegoval/a Vaše pozvání na %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}dosud o Vašem pozvání k události nerozhodl/a.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nedostupný zdroj: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Byl dosažen maximální počet současných rezervací\n(%{NumberOfSimultaneousBookings}) pro zdroj \"%{Cn} %{SystemEmail}\". Konfliktní událost je \"%{EventTitle}\" a začíná %{StartDate}.";
@@ -6,7 +6,6 @@ vevent_class2 = "(Hemmelig begivenhed)";
vtodo_class0 = "(Offentlig opgave)";
vtodo_class1 = "(Privat opgave)";
vtodo_class2 = "(Hemmelig opgave)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Begivenheden \"%{Summary}\" blev oprettet";
"The event \"%{Summary}\" was deleted" = "Begivenheden \"%{Summary}\" blev slettet";
@@ -14,7 +13,6 @@ vtodo_class2 = "(Hemmelig opgave)";
"The following attendees(s) were notified" = "Følgende deltager(e) blev underrettet";
"The following attendees(s) were added" = "Følgende deltager(e) blev tilføjet";
"The following attendees(s) were removed" = "Følgende deltager(e) blev fjernet";
/* IMIP messages */
"calendar_label" = "Kalender";
"startDate_label" = "Start";
@@ -23,20 +21,17 @@ vtodo_class2 = "(Hemmelig opgave)";
"location_label" = "Sted";
"summary_label" = "Resumé:";
"comment_label" = "Kommentér:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Begivenhed Invitation: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(Sendt af%{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}har inviteret dig til %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer}%{SentByText} har inviteret dig til%{Summary}.\n\nStart:%{StartDate}ved%{StartTime}\nEnd:%{EndDate}ved%{endTime}\nDescription:%{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Begivenhed Annulleret: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}har aflyst denne begivenhed: %{Summary}.\n\nStart: %{StartDate}\nSlut: %{EndDate}\nBeskrivelse: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}har annulleret denne begivenhed: %{Summary}.\n\nStart: %{StartDate} %{StartTime}\nSlut: %{EndDate} %{EndTime}\nBeskrivelse: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Aftalen \"%{Summary}\" for %{OldStartDate} er ændret";
@@ -46,7 +41,6 @@ vtodo_class2 = "(Hemmelig opgave)";
= "Følgende parametre har ændret sig i \"%{Summary}\" møde:";
"Please accept or decline those changes."
= "Venligst acceptér eller afvis disse ændringer.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Accepterede invitationen: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Afslåede invitationen: \"%{Summary}\"";
@@ -60,7 +54,6 @@ vtodo_class2 = "(Hemmelig opgave)";
= "%{Attendee} %{SentByText} har delegeret en invitation til %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} har endnu ikke taget stilling til din begivenhedsinvitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan ikke tilgå ressourcen: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimalt antal tilladte indkaldelser (%{NumberOfSimultaneousBookings}) er nået for ressource \"%{Cn} %{SystemEmail}\". Konflikter med begivenheden \"%{EventTitle}\", som starter %{StartDate}.";
@@ -1,13 +1,12 @@
"Inviting the following persons is prohibited:" = "De volgende personen uitnodigen is niet toegestaan:";
"Inviting the following persons is prohibited:" = "Uitnodigen van de volgende personen is niet toegestaan:";
"Personal Calendar" = "Persoonlijke agenda";
vevent_class0 = "(Publieke afspraak)";
vevent_class1 = "(Privé-afspraak)";
vevent_class2 = "(Vertrouwelijke afspraak)";
vevent_class1 = "(Privé-gebeurtenis)";
vevent_class2 = "(Vertrouwelijke gebeurtenis)";
vtodo_class0 = "(Publieke taak)";
vtodo_class1 = "(Privétaak)";
vtodo_class2 = "(Vertrouwelijke taak)";
/* Receipts */
"The event \"%{Summary}\" was created" = "De gebeurtenis \"%{Summary}\" is aangemaakt";
"The event \"%{Summary}\" was deleted" = "De gebeurtenis \"%{Summary}\" is verwijderd";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
"The following attendees(s) were notified" = "De volgende deelnemers zijn in kennis gesteld";
"The following attendees(s) were added" = "De volgende deelnemers zijn toegevoegd";
"The following attendees(s) were removed" = "De volgende deelnemers zijn verwijderd";
/* IMIP messages */
"calendar_label" = "Agenda";
"startDate_label" = "Start";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Vertrouwelijke taak)";
"location_label" = "Locatie";
"summary_label" = "Samenvatting:";
"comment_label" = "Commentaar:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Uitnodiging voor gebeurtenis: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(Verzonden door %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}heeft u uitgenodigd voor %{Summary}.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Gebeurtenis geannuleerd: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}heeft deze gebeurtenis afgezegd: %{Summary}.\n\nBegin: %{StartDate}\nEinde: %{EndDate}\nBeschrijving: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}heeft de gebeurtenis: %{Summary} geannuleerd.\n\nStart: %{StartDate} om %{StartTime}\nEinde: %{EndDate} om %{EndTime}\nOmschrijving: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "De gebeurtenis \"%{Summary}\" op %{OldStartDate} is veranderd.";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
= "De volgende parameters zijn veranderd in de \"%{Summary}\" vergadering:";
"Please accept or decline those changes."
= "Accepteer deze wijzigingen of wijs ze af.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Uitnodiging aanvaard: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Uitnodiging afgewezen: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrouwelijke taak)";
= "%{Attendee} %{SentByText}heeft de uitnodiging gedelegeerd aan %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}heeft nog niet gereageerd op uw uitnodiging.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kan bron \"%{Cn} %{SystemEmail}\" niet benaderen";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Het maximale aantal gelijktijdige reserveringen (%{NumberOfSimultaneousBookings}) voor \"%{Cn} %{SystemEmail}\" is bereikt. De conflicterende gebeurtenis is \"%{EventTitle}\" en begint op %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Confidential event)";
vtodo_class0 = "(Public task)";
vtodo_class1 = "(Private task)";
vtodo_class2 = "(Confidential task)";
/* Receipts */
"The event \"%{Summary}\" was created" = "The event \"%{Summary}\" was created";
"The event \"%{Summary}\" was deleted" = "The event \"%{Summary}\" was deleted";
@@ -15,29 +14,25 @@ vtodo_class2 = "(Confidential task)";
"The following attendees(s) were notified" = "The following attendee(s) were notified";
"The following attendees(s) were added" = "The following attendee(s) were added";
"The following attendees(s) were removed" = "The following attendee(s) were removed";
/* IMIP messages */
"calendar_label" = "Calendar";
"startDate_label" = "Start";
"endDate_label" = "End";
"due_label" = "Due Date:";
"due_label" = "Due Date";
"location_label" = "Location";
"summary_label" = "Summary:";
"comment_label" = "Comment:";
"summary_label" = "Summary";
"comment_label" = "Comment";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Event Invitation: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(sent by %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Event Cancelled: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "The appointment \"%{Summary}\" for the %{OldStartDate} has changed";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Confidential task)";
= "The following parameters have changed in the \"%{Summary}\" meeting:";
"Please accept or decline those changes."
= "Please accept or decline those changes.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Accepted invitation: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Declined invitation: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Confidential task)";
= "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}has not yet decided upon your event invitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Cannot access resource: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Luottamuksellinen tapahtuma)";
vtodo_class0 = "(Julkinen tehtävä)";
vtodo_class1 = "(Yksityinen tehtävä)";
vtodo_class2 = "(Luottamuksellinen tehtävä)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Tapahtuma \"%{Summary}\" luotiin";
"The event \"%{Summary}\" was deleted" = "Tapahtuma \"%{Summary}\" poistettiin";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
"The following attendees(s) were notified" = "Seuraava(t) osallistuja(t) on informoitu";
"The following attendees(s) were added" = "Seuraava(t) osalllistuja(t) on lisätty";
"The following attendees(s) were removed" = "Seuraava(t) osalllistuja(t) poistettiin";
/* IMIP messages */
"calendar_label" = "Kalenteri";
"startDate_label" = "Alkaa";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
"location_label" = "Sijainti";
"summary_label" = "Yhteenveto:";
"comment_label" = "Kommentti:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Tapahtumakutsu: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(lähettäjä %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}on kutsunut sinut %{Summary}n.⏎ ⏎ Alkaa: %{StartDate} klo %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Tapahtuma peruttu: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}on perunut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}⏎ Loppuu: %{EndDate}⏎ Kuvaus: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}on peruuttanut tapahtuman: %{Summary}.⏎ ⏎ Alkaa: %{StartDate}klot %{StartTime}⏎ Loppuu: %{EndDate} klo %{EndTime}⏎ Kuvaus: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Tapaaminen \"%{Summary}\" %{OldStartDate} on muuttunut";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
= "Seuraavat tiedot ovat muuttuneet tapaamisessa \"%{Summary}\":";
"Please accept or decline those changes."
= "Ole hyvä ja hyväksy tai hylkää muutokset.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Hyväksytty kutsu: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Hylätty kutsu: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Luottamuksellinen tehtävä)";
= "%{Attendee} %{SentByText} on lähettänyt kutsusi edelleen osoitteeseen %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} ei vielä ole tehnyt päätöstä tapahtumakutsustasi.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Ei pääsyä resurssiin: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maksimimäärä yhtäaikaisia varauksia\n(%{NumberOfSimultaneousBookings}) saavutettu resurssille \"%{Cn} %{SystemEmail}\". Ristiriitainen tapahtuma: \"%{EventTitle}\" alkaa %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Événement confidentiel)";
vtodo_class0 = "(Tâche publique)";
vtodo_class1 = "(Tâche privée)";
vtodo_class2 = "(Tâche confidentielle)";
/* Receipts */
"The event \"%{Summary}\" was created" = "L'événement «%{Summary}» a été créé";
"The event \"%{Summary}\" was deleted" = "L'événement «%{Summary}» a été effacé";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Tâche confidentielle)";
"The following attendees(s) were notified" = "Les invités suivants ont été avisés ";
"The following attendees(s) were added" = "Les invités suivants ont été ajoutés ";
"The following attendees(s) were removed" = "Les invités suivants ont été supprimés ";
/* IMIP messages */
"calendar_label" = "Agenda ";
"startDate_label" = "Début ";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Tâche confidentielle)";
"location_label" = "Lieu ";
"summary_label" = "Titre :";
"comment_label" = "Description :";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitation à la réunion : «%{Summary}»";
"(sent by %{SentBy}) " = "(envoyé par %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}vous a invité à la réunion « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Réunion annulée : « %{Summary} »";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate}\nFin: %{EndDate}\nDescription: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}a annulé cette réunion : « %{Summary} ».\n\nDébut: %{StartDate} à %{StartTime}\nFin: %{EndDate} à %{EndTime}\nDescription: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "La réunion « %{Summary} » de %{OldStartDate} a été modifiée";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Tâche confidentielle)";
= "Les paramètres suivants ont été modifiés pour la réunion « %{Summary} » :";
"Please accept or decline those changes."
= "Veuillez reconfirmer ou annuler votre présence.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitation acceptée : «%{Summary}»";
"Declined invitation: \"%{Summary}\"" = "Invitation refusée : «%{Summary}»";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Tâche confidentielle)";
= "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre invitation.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Impossible d'accéder à la ressource suivante: %{Cn} %{SystemEmail}";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Le nombre maximal de réservations simultanées (%{NumberOfSimultaneousBookings}) est atteint pour la ressource «%{Cn} %{SystemEmail}». L'événement en conflit est «%{EventTitle}» et débute le %{StartDate}.";
+1 -1
View File
@@ -54,7 +54,7 @@ Appointments_RESOURCE_FILES += \
MSExchangeFreeBusySOAPResponseMap.plist \
MSExchangeFreeBusySOAPRequest.wo
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan ChineseTaiwan Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian NorwegianBokmal NorwegianNynorsk Polish Russian Slovak Slovenian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
Appointments_LANGUAGES = Arabic Basque BrazilianPortuguese Catalan ChineseTaiwan Croatian Czech Danish Dutch English Finnish French German Hungarian Icelandic Italian Lithuanian Macedonian NorwegianBokmal NorwegianNynorsk Polish Portuguese Russian Slovak Slovenian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh
Appointments_LOCALIZED_RESOURCE_FILES = Localizable.strings
@@ -7,7 +7,6 @@ vevent_class2 = "(Vertraulicher Termin)";
vtodo_class0 = "(Öffentliche Aufgabe)";
vtodo_class1 = "(Private Aufgabe)";
vtodo_class2 = "(Vertrauliche Aufgabe)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Der Termin \"%{Summary}\" wurde angelegt";
"The event \"%{Summary}\" was deleted" = "Der Termin \"%{Summary}\" wurde gelöscht";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
"The following attendees(s) were notified" = "Die folgenden Teilnehmer wurden benachrichtigt";
"The following attendees(s) were added" = "Die folgenden Teilnehmer wurden hinzugefügt";
"The following attendees(s) were removed" = "Die folgenden Teilnehmer wurden entfernt";
/* IMIP messages */
"calendar_label" = "Kalender";
"startDate_label" = "Beginn";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
"location_label" = "Ort";
"summary_label" = "Zusammenfassung:";
"comment_label" = "Kommentar:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Termineinladung: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(gesendet von %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} Hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Termin abgesagt: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} hat den folgenden Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} hat diesen Termin abgesagt: \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Die Verabredung \"%{Summary}\" am %{OldStartDate} wurde geändert";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
= "Folgendes wurde am Termin \"%{Summary}\" geändert:";
"Please accept or decline those changes."
= "Bitte akzeptieren Sie diese Änderung oder lehnen Sie diese ab.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Einladung zugestimmt: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Einladung abgelehnt: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
= "%{Attendee} %{SentByText} hat die Einladung delegiert an %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} hat noch nicht über Ihre Termineinladung entschieden.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kann auf folgende Ressource nicht zugreifen: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Die maximale Anzahl von gleichzeitigen Buchungen (%{NumberOfSimultaneousBookings}) für die Ressource \"%{Cn} %{SystemEmail}\" ist erreicht. Der kollidierende Termin ist \"%{EventTitle}\", und beginnt am %{StartDate}.";
@@ -7,7 +7,6 @@ vevent_class2 = "(Bizalmas esemény)";
vtodo_class0 = "(Nyilvános feladat)";
vtodo_class1 = "(Magán feladat)";
vtodo_class2 = "(Bizalmas feladat)";
/* Receipts */
"The event \"%{Summary}\" was created" = "A \"%{Summary}\" esemény létre lett hozva";
"The event \"%{Summary}\" was deleted" = "A \"%{Summary}\" esemény törölve lett";
@@ -15,7 +14,6 @@ vtodo_class2 = "(Bizalmas feladat)";
"The following attendees(s) were notified" = "Az alábbi résztvevők lettek értesítve";
"The following attendees(s) were added" = "Az alábbi résztvevők lettek hozzáadva";
"The following attendees(s) were removed" = "Az alábbi résztvevők lettek eltávolítva";
/* IMIP messages */
"calendar_label" = "Naptár";
"startDate_label" = "Kezdete";
@@ -24,20 +22,17 @@ vtodo_class2 = "(Bizalmas feladat)";
"location_label" = "Hely";
"summary_label" = "Összegzés:";
"comment_label" = "Megjegyzés:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Meghívás eseményre: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy} által elküldve) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt a(z) %{Summary} tárgyú eseményre.\n\nKezdete: %{StartDate}: \nVége: %{EndDate}\nLeírása: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} meghívta Önt erre az eseményre: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Az esemény törölve lett: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} törlte az alábbi eseményt: %{Summary}.\n\nKzdete: %{StartDate}\nVége: %{EndDate}\nLeírás: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} törölte az alábbi eseményt: %{Summary}.\n\nKezdete: %{StartDate} at %{StartTime}\nVége: %{EndDate} at %{EndTime}\nLeírás: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "A(z) \"%{Summary}\" tárgyú, %{OldStartDate} időpontra tervezett esemény módosult.";
@@ -47,7 +42,6 @@ vtodo_class2 = "(Bizalmas feladat)";
= "A \"%{Summary}\" tárgyú találkozó következő adatai módosultak:";
"Please accept or decline those changes."
= "Kérem fogadja el vagy utasítsa vissza a változásokat.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Elfogadott meghivás: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Elutasított meghívás: \"%{Summary}\"";
@@ -61,7 +55,6 @@ vtodo_class2 = "(Bizalmas feladat)";
= "%{Attendee} %{SentByText} átruházta a meghívást az alábbi személynek %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}még meggondolja a meghívását.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Az alábbi elem nem elérhető: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "A maximális egyidejű foglalás számát (%{NumberOfSimultaneousBookings}) elérte a(z) \"%{Cn} %{SystemEmail}\" elem esetében. Az ütköző esemény a(z) \"%{EventTitle}\", a kezdeti időpontja %{StartDate}.";
@@ -6,24 +6,19 @@ vevent_class2 = "(Viðburður er trúnaðarmál)";
vtodo_class0 = "(Almennt verkefni)";
vtodo_class1 = "(Einkaverkefni)";
vtodo_class2 = "(Verkefni er trúnaðarmál)";
/* Receipts */
"Title:" = "Titill:";
"Start:" = "Byrjun:";
"End:" = "Endir:";
"Receipt: users invited to a meeting" = "Móttakandi: notendur boðnir á fund";
"You have invited the following attendees(s):" = "Eftirfarandi þáttakanda/þáttakendum hefur verið boðið:";
"... to attend the following event:" = "... að vera viðstaddir eftirfarandi viðburð:";
"Receipt: invitation updated" = "Móttakandi: uppfærður boðsmiði";
"The following attendees(s):" = "Eftirfarandi þáttakandi/Þáttakendur:";
"... have been notified of the changes to the following event:" = "... hafa fengið upplýsingar um breytingarnar á eftirfarandi viðburði:";
"Receipt: attendees removed from an event" = "Móttakandi: þáttakendur fjarlægðir af viðburði";
"You have removed the following attendees(s):" = "Eftirfarandi þáttakandi/þáttakendur hafa verið fjarlægðir:";
"... from the following event:" = "... af eftirfarandi viðburði:";
/* IMIP messages */
"startDate_label" = "Byrjun";
"endDate_label" = "Endir";
@@ -31,17 +26,14 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
"location_label" = "Staðsetning";
"summary_label" = "Samantekt:";
"comment_label" = "Athugasemd:";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Boð á viðburð: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(%{SentBy} sendi þetta) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}hefur boðið þér á þennan viðburð: %{Summary}.\n\nByrjun: %{StartDate} at %{StartTime}\nEndir: %{EndDate} at %{EndTime}\nLýsing: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Viðburði aflýst: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}hefur aflýst þessum viðburði: %{Summary}.\n\nByrjun: %{StartDate} kl. %{StartTime}\nEndir: %{EndDate} kl. %{EndTime}\nLýsing: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "Tímapöntunin \"%{Summary}\" sem var þann %{OldStartDate} kl. %{OldStartTime} hefur breyst.";
@@ -49,7 +41,6 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
= "Eftirfarandi atriði hafa breyst varðandi fundinn: \"%{Summary}\"";
"Please accept or decline those changes."
= "Þessar breytingar þarf að samþykkja eða hafna.";
/* Reply */
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}hefur samþykkt boð þitt á viðburð.";
@@ -59,6 +50,5 @@ vtodo_class2 = "(Verkefni er trúnaðarmál)";
= "%{Attendee} %{SentByText}hefur skipað sem fulltrúa sinn %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}hefur ekki enn tekið ákvörðun varðandi boð þitt.";
/* Resources */
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Hámarksfjöldi samtímabókana (%{NumberOfSimultaneousBookings}) hefur verið náð varðandi aðfangið \"%{Cn} %{SystemEmail}\".";
Loaded 100 of 675 files, more files were not shown because too many files have changed in this diff. Show more