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