]> err.no Git - sope/blob - sope-mime/NGImap4/EOQualifier+IMAPAdditions.m
fixed OGo bug #1899
[sope] / sope-mime / NGImap4 / EOQualifier+IMAPAdditions.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "imCommon.h"
23
24 @interface EOQualifier(PrivateMethodes)
25
26 - (NSString *)qualifierDescription;
27
28 - (NSException *)invalidImap4SearchQualifier:(NSString *)_reason;
29
30 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search 
31   insertNot:(BOOL)_insertNot;
32 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search;
33
34 - (id)imap4SearchString;
35
36 @end
37
38 @implementation EOQualifier(IMAPAdditions)
39
40 - (BOOL)isImap4UnseenQualifier { /* a special key/value qualifier */
41   return NO;
42 }
43
44 /* building search qualifiers */
45
46 static NSArray *FlagKeyWords = nil;
47 static NSArray *OtherKeyWords = nil;
48 static BOOL    debugOn = NO;
49
50 static void _initImap4SearchCategory(void) {
51   NSUserDefaults *ud;
52   
53   if (FlagKeyWords) return;
54
55   ud = [NSUserDefaults standardUserDefaults];
56   FlagKeyWords = [[NSArray alloc] initWithObjects: @"answered", @"deleted",
57                             @"draft", @"flagged", @"new", @"old", @"recent",
58                             @"seen", @"unanswered", @"undeleted", @"undraft",
59                             @"unflagged", @"unseen", nil];
60   OtherKeyWords = [[NSArray alloc] initWithObjects:
61                              @"bcc", @"body", @"cc", @"from", @"subject",
62                              @"text", @"to", @"keyword", @"unkeyword", nil];
63   
64   debugOn = [ud boolForKey:@"ImapDebugQualifierGeneration"];
65 }
66
67 - (NSException *)invalidImap4SearchQualifier:(NSString *)_reason {
68   if (_reason == nil) _reason = @"unknown reason";
69   return [NSException exceptionWithName:@"NGImap4SearchQualifierException"
70                       reason:_reason
71                       userInfo:nil];
72 }
73
74 - (BOOL)isImap4NotQualifier {
75   return NO;
76 }
77 - (BOOL)isImap4KeyValueQualifier {
78   return NO;
79 }
80
81 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search 
82   insertNot:(BOOL)_insertNot
83 {
84   return [self invalidImap4SearchQualifier:@"expected key/value qualifier"];
85 }
86 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search { 
87   return [self appendToImap4SearchString:_search insertNot:NO];
88 }
89
90 - (id)imap4SearchString { /* returns exception on fail */
91   [self logWithFormat:@"ERROR(%s): subclass %@ must overide this method!",
92         __PRETTY_FUNCTION__, [self class]];
93   return nil;
94 }
95
96 @end /* EOQualifier(IMAPAdditions) */
97
98
99 @implementation EOAndQualifier(IMAPAdditions)
100
101 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
102   NSArray         *quals;
103   unsigned        i, lCount;
104   
105   quals  = [self qualifiers];
106   
107   if ((lCount = [quals count]) == 0) /* no subqualifiers */
108     return nil;
109   if (lCount == 1) {
110     // TODO: use appendToImap4SearchString?
111     [_search appendString:[[quals objectAtIndex:0] imap4SearchString]];
112     return nil;
113   }
114   
115   for (i = 0; i < lCount; i++) {
116     EOQualifier *qualifier;
117     NSException *error;
118     
119     qualifier = [quals objectAtIndex:i];
120     if (debugOn)
121       [self logWithFormat:@"  append subqualifier: %@", qualifier];
122     
123     [_search appendString:(i == 0) ? @"(" : @" ("];
124     if ((error = [qualifier appendToImap4SearchString:_search]))
125       return error;
126     [_search appendString:@")"];
127   }
128   
129   return nil /* no error */;
130 }
131
132 - (id)imap4SearchString { /* returns exception on fail */
133   NSMutableString *search;
134   NSException     *error;
135   unsigned        lCount;
136   
137   _initImap4SearchCategory();
138   
139   if (debugOn) {
140     [self logWithFormat:
141             @"generate IMAP4 expression for AND qualifier: %@", self];
142   }
143   
144   if ((lCount = [[self qualifiers] count]) == 0) /* no subqualifiers */
145     return nil;
146   if (lCount == 1)
147     return [[[self qualifiers] objectAtIndex:0] imap4SearchString];
148   
149   search = [NSMutableString stringWithCapacity:lCount * 3];
150   
151   if ((error = [self appendToImap4SearchString:search]) != nil) {
152     if (debugOn) [self logWithFormat:@"  error: %@", error];
153     return error;
154   }
155   
156   if (debugOn)
157     [self logWithFormat:@"  generated: '%@'", search];
158
159   return search;
160 }
161
162 @end /* EOAndQualifier(IMAPAdditions) */
163
164
165 @implementation EOOrQualifier(IMAPAdditions)
166
167 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
168   // TODO: move generation to this method
169   id s;
170   
171   s = [self imap4SearchString];
172   if ([s isKindOfClass:[NSException class]])
173     return s;
174   
175   [_search appendString:s];
176   return nil;
177 }
178
179 - (id)imap4SearchString { /* returns exception on fail */
180   NSArray         *quals;
181   NSMutableString *search;
182   unsigned        i, lCount;
183   NSException     *error;
184   
185   _initImap4SearchCategory();
186   
187   if (debugOn) {
188     [self logWithFormat:
189             @"generate IMAP4 expression for or-qualifier: %@", self];
190   }
191   
192   quals = [self qualifiers];
193
194   if ((lCount = [quals count]) == 0) /* no subqualifiers */
195     return nil;
196   if (lCount == 1)
197     return [[quals objectAtIndex:0] imap4SearchString];
198   
199   search = [NSMutableString stringWithCapacity:lCount * 32];
200   
201   /*
202     Note: or queries are specified as:
203             OR <search-key1> <search-key2>
204           so we need to wrap more ORs in multiple "OR" IMAP4 expressions
205           eg: "OR (OR (subject "abc") (subject "nbc")) from "duck""
206   */
207   
208   if ((error = [[quals objectAtIndex:0] appendToImap4SearchString:search]))
209     return error;
210   
211   for (i = 1; i < lCount; i++) {
212     EOQualifier *qualifier;
213     
214     qualifier = [quals objectAtIndex:i];
215     [search insertString:@"OR (" atIndex:0];
216     [search appendString:@") ("];
217     if ((error = [qualifier appendToImap4SearchString:search]))
218       return error;
219     [search appendString:@")"];
220   }
221   
222   if (debugOn)
223     [self logWithFormat:@"  generated: '%@'", search];
224   return search;
225 }
226
227 @end /* EOOrQualifier(IMAPAdditions) */
228
229
230 @implementation EOKeyValueQualifier(IMAPAdditions)
231
232 - (BOOL)isImap4KeyValueQualifier {
233   return YES;
234 }
235
236 - (BOOL)isImap4UnseenQualifier {
237   // TODO: this is rather weird: flags suggests an array value!
238   if (![[self key] isEqualToString:@"flags"]) 
239     return NO;
240   return [[self value] isEqualToString:@"unseen"];
241 }
242
243 - (NSException *)appendFlagsCheckToImap4SearchString:(NSMutableString *)search 
244   insertNot:(BOOL)insertNot
245 {
246   NSEnumerator *enumerator = nil;
247   id       lvalue;
248   SEL      lselector;
249   
250   lvalue    = [self value];
251   lselector = [self selector];
252
253   // TODO: add support for <> qualifier? (seen => unseen)
254       
255   if (sel_eq(lselector, EOQualifierOperatorEqual)) {
256     lvalue = [NSArray arrayWithObject:lvalue];
257   }
258   else if (!sel_eq(lselector, EOQualifierOperatorContains)) {
259     return [self invalidImap4SearchQualifier:
260                    @"unexpected EOKeyValueQualifier selector"];
261   }
262   if (![lvalue isKindOfClass:[NSArray class]]) {
263     return [self invalidImap4SearchQualifier:
264                    @"expected an array in contains-qualifier"];
265   }
266   
267   enumerator = [lvalue objectEnumerator];
268   while ((lvalue = [enumerator nextObject]) != nil) {
269     lvalue = [lvalue lowercaseString];
270         
271     if ([FlagKeyWords containsObject:lvalue]) {
272       if (insertNot) [search appendString:@"not "];
273       [search appendString:lvalue];
274     }
275     else {
276       return [self invalidImap4SearchQualifier:
277                      @"unexpected keyword for EOKeyValueQualifier"];
278     }
279   }
280   return nil;
281 }
282
283 - (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector {
284   if (sel_eq(lselector, EOQualifierOperatorEqual))
285     return @" senton ";
286   if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
287     return @" sentsince ";
288   if (sel_eq(lselector, EOQualifierOperatorLessThan))
289     return @" sentbefore ";
290   
291   return nil;
292 }
293
294 - (NSException *)appendToImap4SearchString:(NSMutableString *)search 
295   insertNot:(BOOL)insertNot
296 {
297   // TODO: this needs to get reworked
298   /* returns exception on fail */
299   NSString *lkey;
300   id       lvalue;
301   SEL      lselector;
302   
303   lkey      = [[self key] lowercaseString];
304   lvalue    = [self value];
305   lselector = [self selector];
306     
307   if ([lkey isEqualToString:@"flags"]) {
308     /* NOTE: special "not" processing! */
309     return [self appendFlagsCheckToImap4SearchString:search 
310                  insertNot:insertNot];
311   }
312   
313   /* not a flag */
314   if (insertNot) 
315     [search appendString:@"not "];
316   
317   if ([lkey isEqualToString:@"date"]) {
318     NSString *s;
319     
320     if (![lvalue isKindOfClass:[NSCalendarDate class]]) {
321       return [self invalidImap4SearchQualifier:
322                      @"expected a NSDate as value"];
323     }
324     
325     if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil)
326       return [self invalidImap4SearchQualifier:@"unexpected selector"];
327     
328     // TODO: operator created but NOT added?
329     
330     // TODO: much faster without descriptionWithCalendarFormat:?!
331     s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"];
332     [search appendString:s];
333     return nil;
334   }
335
336   if ([lkey isEqualToString:@"uid"]) {
337     if (!sel_eq(lselector, EOQualifierOperatorEqual))
338       return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"];
339     
340     [search appendString:@"uid "];
341     [search appendString:[lvalue stringValue]];
342     return nil;
343   }
344   
345   if ([lkey isEqualToString:@"size"]) {
346     if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
347       [search appendString:@"larger "];
348     else if (sel_eq(lselector, EOQualifierOperatorLessThan))
349       [search appendString:@"smaller "];
350     else
351       return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"];
352         
353     [search appendString:[lvalue stringValue]];
354     return nil;
355   }
356   
357   if ([OtherKeyWords containsObject:lkey]) {
358     // TODO: actually most keywords only allow for contains! Eg "subject abc"
359     //       is a contains query, not an equal query!
360     /*
361        RFC 3501:
362        In all search keys that use strings, a message matches the key if
363        the string is a substring of the field.  The matching is
364        case-insensitive.
365
366        Would be: "a caseInsensitiveLike: '*ABC*'"
367     */
368     if (!sel_eq(lselector, EOQualifierOperatorEqual) &&
369         !sel_eq(lselector, EOQualifierOperatorContains)) {
370       [self logWithFormat:@"IMAP4 generation: got: %@, allowed: %@", 
371             NSStringFromSelector(lselector),
372             NSStringFromSelector(EOQualifierOperatorEqual)];
373       return [self invalidImap4SearchQualifier:
374                      @"unexpected qualifier, disallowed comparison on "
375                      @"OtherKeyWords)"];
376     }
377     
378     [search appendString:lkey];
379     [search appendString:@" \""];
380     [search appendString:[lvalue stringValue]];
381     [search appendString:@"\""];
382     return nil;
383   }
384   
385   
386   if (!sel_eq(lselector, EOQualifierOperatorEqual))
387     return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"];
388   
389   [search appendString:@"header "];
390   [search appendString:lkey];
391   [search appendString:@" \""];
392   [search appendString:[lvalue stringValue]];
393   [search appendString:@"\""];
394   return nil;
395 }
396
397 - (id)imap4SearchString { /* returns exception on fail */
398   NSMutableString *search;
399   NSException     *error;
400   
401   _initImap4SearchCategory();
402
403   if ([self isImap4UnseenQualifier]) {
404     if (debugOn)
405       [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]];
406     return @"unseen";
407   }
408   
409   search = [NSMutableString stringWithCapacity:256];
410   
411   if ((error = [self appendToImap4SearchString:search]))
412     return error;
413   
414   return search;
415 }
416
417 @end /* EOKeyValueQualifier(IMAPAdditions) */
418
419
420 @implementation EONotQualifier(IMAPAdditions)
421
422 - (BOOL)isImap4NotQualifier {
423   return YES;
424 }
425
426 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search { 
427   /*
428     TODO: we do this because the key/value qualifier can generate multiple
429           queries
430   */
431   return [[self qualifier] appendToImap4SearchString:_search insertNot:YES];
432 }
433
434 - (id)imap4SearchString { /* returns exception on fail */
435   NSMutableString *search;
436   NSException     *error;
437   
438   _initImap4SearchCategory();
439   
440   search = [NSMutableString stringWithCapacity:256];
441   
442   if ((error = [self appendToImap4SearchString:search]))
443     return error;
444   
445   return search;
446 }
447
448 @end /* EONotQualifier(IMAPAdditions) */