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