]> err.no Git - sope/blob - sope-mime/NGMime/NGMimeType.m
renamed packages as discussed in the developer list
[sope] / sope-mime / NGMime / NGMimeType.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo 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   OGo 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 OGo; 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 // $Id$
22
23 #include "NGMimeType.h"
24 #include "NGConcreteMimeType.h"
25 #include "NGMimeUtilities.h"
26 #include "common.h"
27
28 NGMime_DECLARE NSString *NGMimeTypeText        = @"text";
29 NGMime_DECLARE NSString *NGMimeTypeAudio       = @"audio";
30 NGMime_DECLARE NSString *NGMimeTypeVideo       = @"video";
31 NGMime_DECLARE NSString *NGMimeTypeImage       = @"image";
32 NGMime_DECLARE NSString *NGMimeTypeApplication = @"application";
33 NGMime_DECLARE NSString *NGMimeTypeMultipart   = @"multipart";
34 NGMime_DECLARE NSString *NGMimeTypeMessage     = @"message";
35 NGMime_DECLARE NSString *NGMimeParameterTextCharset = @"charset";
36
37 static BOOL _parseMimeType(id self, NSString *_str, NSString **type,
38                            NSString **subType, NSDictionary **parameters);
39
40 @implementation NGMimeType
41
42 + (int)version {
43   return 2;
44 }
45
46 static NSMutableDictionary *typeToClass = nil;
47
48 static inline Class
49 classForType(NSString *_type, NSString *_subType, NSDictionary *_parameters)
50 {
51   Class c = Nil;
52   if (_type == nil) return Nil;
53
54   if ([_type isEqualToString:@"*"] || [_subType isEqualToString:@"*"])
55     return [NGConcreteWildcardType class];
56
57   if ([_type isEqualToString:NGMimeTypeApplication]) {
58     if ([_subType isEqualToString:@"octet"])
59       return [NGConcreteAppOctetMimeType class];
60   }
61   if ([_type isEqualToString:NGMimeTypeText]) {
62     if ([_subType isEqualToString:@"x-vcard"])
63       return [NGConcreteTextVcardMimeType class];
64   }
65   
66   c = [typeToClass objectForKey:_type];
67   return c ? c : [NGConcreteGenericMimeType class];
68 }
69 static Class NSStringClass  = Nil;
70
71 + (void)initialize {
72   static BOOL isInitialized = NO;
73   if (!isInitialized) {
74     isInitialized = YES;
75
76     typeToClass = [[NSMutableDictionary alloc] initWithCapacity:10];
77     [typeToClass setObject:[NGConcreteTextMimeType  class] 
78                  forKey:NGMimeTypeText];
79     [typeToClass setObject:[NGConcreteVideoMimeType class] 
80                  forKey:NGMimeTypeVideo];
81     [typeToClass setObject:[NGConcreteAudioMimeType class] 
82                  forKey:NGMimeTypeAudio];
83     [typeToClass setObject:[NGConcreteImageMimeType class] 
84                  forKey:NGMimeTypeImage];
85     [typeToClass setObject:[NGConcreteApplicationMimeType class]
86                  forKey:NGMimeTypeApplication];
87     [typeToClass setObject:[NGConcreteMultipartMimeType class]
88                  forKey:NGMimeTypeMultipart];
89     [typeToClass setObject:[NGConcreteMessageMimeType class]
90                  forKey:NGMimeTypeMessage];
91   }
92 }
93
94 + (NSStringEncoding)stringEncodingForCharset:(NSString *)_s {
95   NSString         *charset;
96   NSStringEncoding encoding;
97   BOOL             foundUnsupported;
98
99   foundUnsupported = NO;  
100   charset          = [_s lowercaseString];
101   
102   if ([charset length] == 0)
103     encoding = [NSString defaultCStringEncoding];
104   
105   /* UTF-, ASCII */
106   else if ([charset isEqualToString:@"us-ascii"])
107     encoding = NSASCIIStringEncoding;
108   else if ([charset isEqualToString:@"utf-8"])
109     encoding = NSUTF8StringEncoding;
110   else if ([charset isEqualToString:@"utf-16"])
111     encoding = NSUnicodeStringEncoding;
112
113   /* ISO Latin 1 */
114   else if ([charset isEqualToString:@"iso-latin-1"])
115     encoding = NSISOLatin1StringEncoding;
116   else if ([charset isEqualToString:@"iso-8859-1"])
117     encoding = NSISOLatin1StringEncoding;
118   else if ([charset isEqualToString:@"8859-1"])
119     encoding = NSISOLatin1StringEncoding;
120   
121   /* some unsupported, but known encoding */
122   else if ([charset isEqualToString:@"ks_c_5601-1987"]) {
123     encoding = [NSString defaultCStringEncoding];
124     foundUnsupported = YES;
125   }
126   else if ([charset isEqualToString:@"euc-kr"]) {
127     encoding = [NSString defaultCStringEncoding];
128     foundUnsupported = YES;
129   }
130   else if ([charset isEqualToString:@"big5"]) {
131     encoding = [NSString defaultCStringEncoding];
132     foundUnsupported = YES;
133   }
134   else if ([charset isEqualToString:@"iso-2022-jp"]) {
135     encoding = [NSString defaultCStringEncoding];
136     foundUnsupported = YES;
137   }
138   else if ([charset isEqualToString:@"gb2312"]) {
139     encoding = [NSString defaultCStringEncoding];
140     foundUnsupported = YES;
141   }
142   else if ([charset isEqualToString:@"koi8-r"]) {
143     encoding = [NSString defaultCStringEncoding];
144     foundUnsupported = YES;
145   }
146   
147   else if ([charset isEqualToString:@"windows-1252"]) {
148     encoding = NSWindowsCP1252StringEncoding;
149   }
150   else if ([charset isEqualToString:@"iso-8859-2"]) {
151     encoding = NSISOLatin2StringEncoding;
152   }
153   else if ([charset isEqualToString:@"x-unknown"] ||
154            [charset isEqualToString:@"unknown"]) {
155     encoding = NSASCIIStringEncoding;
156   }
157   /* ISO Latin 9 */
158 #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
159   else if ([charset isEqualToString:@"iso-latin-9"])
160     encoding = NSISOLatin9StringEncoding;
161   else if ([charset isEqualToString:@"iso-8859-15"])
162     encoding = NSISOLatin9StringEncoding;
163   else if ([charset isEqualToString:@"8859-15"])
164     encoding = NSISOLatin9StringEncoding;
165 #endif
166   else {
167     [self logWithFormat:@"%s: unknown charset '%@'",
168           __PRETTY_FUNCTION__, _s];
169     encoding = [NSString defaultCStringEncoding];
170   }
171   return encoding;
172 }
173
174 // init
175
176 - (id)initWithType:(NSString *)_type subType:(NSString *)_subType
177   parameters:(NSDictionary *)_parameters
178 {
179   Class c;
180
181   c = classForType(_type, _subType, _parameters);
182   [self release];
183   
184   return [[c alloc] initWithType:_type subType:_subType
185                     parameters:_parameters];
186 }
187
188 + (id)mimeType:(NSString *)_type subType:(NSString *)_subType {
189   Class c;
190
191   c = classForType(_type, _subType, nil);
192   
193   NSAssert(c, @"did not find class for mimetype ..");
194
195   return [[[c alloc] initWithType:_type subType:_subType
196                      parameters:nil] autorelease];
197 }
198
199 + (id)mimeType:(NSString *)_type subType:(NSString *)_subType
200   parameters:(NSDictionary *)_parameters
201 {
202   Class c;
203
204   c = classForType(_type, _subType, _parameters);
205   NSAssert(c, @"did not find class for mimetype ..");
206   
207   return [[[c alloc] initWithType:_type subType:_subType
208                      parameters:_parameters] autorelease];
209 }
210
211 + (id)mimeType:(NSString *)_stringValue {
212   NSString     *type, *subType;
213   NSDictionary *parameters;
214
215   if ([_stringValue length] == 0)
216     /* empty ... */
217     return nil;
218
219   parameters = nil;
220   type       = nil;
221   subType    = nil;
222
223   if (_parseMimeType(self, _stringValue, &type, &subType, &parameters)) {
224     Class c;
225     id    result;
226
227     c = classForType(type, subType, nil);
228     NSAssert(c,       @"did not find class for mimetype ..");
229     NSAssert(type,    @"didn't parse type ..");
230     NSAssert(subType, @"didn't parse subtype ..");
231
232     result = [c alloc];
233     NSAssert(result, @"allocation of mimetype failed ..");
234
235     result = [result initWithType:type subType:subType parameters:parameters];
236     NSAssert(result, @"initialization of mimetype failed ..");
237
238     result = [result autorelease];
239     NSAssert(result, @"autorelease of mimetype failed ..");
240
241     return result;
242   }
243   else {
244     [self logWithFormat:@"ERROR[%s]: parsing of mimetype '%@' failed !",
245           __PRETTY_FUNCTION__, _stringValue];
246     return nil; // parsing failed
247   }
248 }
249
250 /* types */
251
252 - (NSString *)type {
253   [self subclassResponsibility:_cmd];
254   return nil;
255 }
256 - (NSString *)subType {
257   [self subclassResponsibility:_cmd];
258   return nil;
259 }
260 - (BOOL)isCompositeType {
261   [self subclassResponsibility:_cmd];
262   return NO;
263 }
264
265 /* comparing types */
266
267 - (BOOL)isEqual:(id)_other {
268   if (_other == nil)  return NO;
269   if (_other == self) return YES;
270
271   return ([_other isKindOfClass:[NGMimeType class]])
272     ? [self isEqualToMimeType:_other]
273     : NO;
274 }
275
276 - (BOOL)isEqualToMimeType:(NGMimeType *)_type {
277   if (_type == nil)  return NO;
278   if (_type == self) return YES;
279
280   if (![self hasSameType:_type])
281     return NO;
282
283   if (![[_type parametersAsDictionary] isEqual:[self parametersAsDictionary]])
284     return NO;
285
286   return YES;
287 }
288
289 - (BOOL)hasSameGeneralType:(NGMimeType *)_other { // only the 'type' must match
290   if (_other == self) return YES;
291   if ([_other isCompositeType] != [self isCompositeType]) return NO;
292   if (![[_other type]    isEqualToString:[self type]])    return NO;
293   return YES;
294 }
295 - (BOOL)hasSameType:(NGMimeType *)_other { // parameters need not match
296   if (_other == nil)  return NO;
297   if (_other == self) return YES;
298   if ([_other isCompositeType] != [self isCompositeType]) return NO;
299   if (![[_other type]    isEqualToString:[self type]])    return NO;
300   if (![[_other subType] isEqualToString:[self subType]]) return NO;
301   return YES;
302 }
303
304 - (BOOL)doesMatchType:(NGMimeType *)_other { // interpretes wildcards
305   NSString *t, *st, *ot, *ost;
306
307   t   = [self type];
308   st  = [self subType];
309   ot  = [_other type];
310   ost = [_other subType];
311
312   if ([t isEqualToString:@"*"] || [ot isEqualToString:@"*"]) {
313     t   = @"*";
314     ot  = @"*";
315   }
316   if (![t  isEqualToString:ot]) return NO;
317
318   if ([st isEqualToString:@"*"] || [ost isEqualToString:@"*"]) {
319     ot  = @"*";
320     ost = @"*";
321   }
322   if (![st isEqualToString:ost]) return NO;
323   
324   return YES;
325 }
326
327 /* parameters */
328
329 - (NSEnumerator *)parameterNames {
330   [self doesNotRecognizeSelector:_cmd]; // subclass
331   return nil;
332 }
333 - (id)valueOfParameter:(NSString *)_parameterName {
334   [self doesNotRecognizeSelector:_cmd]; // subclass
335   return nil;
336 }
337
338 /* representations */
339
340 - (NSDictionary *)parametersAsDictionary {
341   NSMutableDictionary *parameters;
342   NSString            *name;
343   NSDictionary        *d;
344   NSEnumerator        *names;
345
346   if ((names = [self parameterNames]) == nil)
347     return nil;
348
349   parameters = [[NSMutableDictionary alloc] init];
350   while ((name = [names nextObject]))
351     [parameters setObject:[self valueOfParameter:name] forKey:name];
352
353   d = [parameters copy];
354   [parameters release];
355   return [d autorelease];
356 }
357
358 - (NSString *)parametersAsString {
359   NSEnumerator    *names;
360   NSMutableString *result;
361   NSString        *name;
362
363   if ((names = [self parameterNames]) == nil)
364     return nil;
365   
366   result = [NSMutableString stringWithCapacity:64];
367   while ((name = [names nextObject])) {
368     NSString *value;
369
370     value = [[self valueOfParameter:name] stringValue];
371     
372     [result appendString:@"; "];
373     [result appendString:name];
374     [result appendString:@"="];
375     
376     if ([self valueNeedsQuotes:value]) {
377       [result appendString:@"\""];
378       [result appendString:value];
379       [result appendString:@"\""];
380     }
381     else
382       [result appendString:value];
383   }
384   return result;
385 }
386
387 - (BOOL)valueNeedsQuotes:(NSString *)_parameterValue {
388   unsigned len = [_parameterValue cStringLength];
389   char     buf[len + 15];
390   char     *cstr;
391
392   cstr = &(buf[0]);
393
394   [_parameterValue getCString:cstr]; cstr[len] = '\0';
395   while (*cstr) {
396     if (isMime_SpecialByte(*cstr))
397       return YES;
398
399     if (*cstr == 32)
400       return YES;
401     
402     cstr++;
403   }
404   return NO;
405 }
406
407 - (NSString *)stringValue {
408   [self subclassResponsibility:_cmd];
409   return nil;
410 }
411
412 // NSCoding
413
414 - (Class)classForCoder {
415   return [NGMimeType class];
416 }
417
418 - (void)encodeWithCoder:(NSCoder *)_encoder {
419   [_encoder encodeObject:[self type]];
420   [_encoder encodeObject:[self subType]];
421   [_encoder encodeObject:[self parametersAsDictionary]];
422 }
423
424 - (id)initWithCoder:(NSCoder *)_decoder {
425   NSString     *type, *subType;
426   NSDictionary *paras;
427
428   type    = [_decoder decodeObject];
429   subType = [_decoder decodeObject];
430   paras   = [_decoder decodeObject];
431
432   return [self initWithType:type subType:subType parameters:paras];
433 }
434
435 // NSCopying
436
437 - (id)copyWithZone:(NSZone *)_zone {
438   return [[NGMimeType allocWithZone:_zone]
439                       initWithType:[self type] subType:[self subType]
440                       parameters:[self parametersAsDictionary]];
441 }
442
443 // description
444
445 - (NSString *)description {
446   return [NSString stringWithFormat:@"<NGMimeType: %@>", [self stringValue]];
447 }
448
449 @end /* NGMimeType */
450
451 typedef struct {
452   NSString *image;
453   NSString *video;
454   NSString *audio;
455   NSString *text;
456   NSString *star;
457   NSString *application;
458   NSString *multipart;
459   NSString *message;
460 } NGMimeTypeConstants;
461
462 typedef struct {
463   NSString *plain;
464   NSString *star;
465   NSString *mixed;
466   NSString *jpeg;
467   NSString *png;
468   NSString *gif;
469   NSString *xml;
470   NSString *html;
471   NSString *css;
472   NSString *xMng;
473   NSString *xhtmlXml;
474   NSString *rfc822;
475   NSString *octetStream;
476 } NGMimeSubTypeConstants;
477
478 static NGMimeTypeConstants      *MimeTypeConstants      = NULL;
479 static NGMimeSubTypeConstants   *MimeSubTypeConstants   = NULL;
480
481 static NSString *_stringForType(char *_type, int _len) {
482   if (NSStringClass == Nil) NSStringClass = [NSString class];
483
484   if (MimeTypeConstants == NULL) {
485     MimeTypeConstants = malloc(sizeof(NGMimeTypeConstants));
486     MimeTypeConstants->image       = NGMimeTypeImage;
487     MimeTypeConstants->video       = NGMimeTypeVideo;
488     MimeTypeConstants->audio       = NGMimeTypeAudio;
489     MimeTypeConstants->text        = NGMimeTypeText;
490     MimeTypeConstants->star        = @"*";
491     MimeTypeConstants->application = NGMimeTypeApplication;
492     MimeTypeConstants->multipart   = NGMimeTypeMultipart;
493     MimeTypeConstants->message     = NGMimeTypeMessage;
494   }
495   switch (_len) {
496     case 0:
497       return @"";
498     case 1:
499       if (_type[0] == '*')
500         return MimeTypeConstants->star;
501       break;
502     case 4:
503       if (strncmp(_type, "text", 4) == 0) 
504         return MimeTypeConstants->text;
505       break;
506     case 5:
507       if (_type[0] == 'i') {
508         if (strncmp(_type, "image", 5) == 0) 
509           return MimeTypeConstants->image;
510       }
511       else if (_type[0] == 'v') {
512         if (strncmp(_type, "video", 5) == 0) 
513           return MimeTypeConstants->video;
514       }
515       else if (_type[0] == 'a') {
516         if (strncmp(_type, "audio", 5) == 0) 
517           return MimeTypeConstants->audio;
518       }
519       break;
520     case 7:
521       if (strncmp(_type, "message", 7) == 0)
522         return MimeTypeConstants->message;
523       break;
524     case 9:
525       if (strncmp(_type, "multipart", 9) == 0)
526         return MimeTypeConstants->multipart;
527       break;    case 11:
528       if (strncmp(_type, "application", 11) == 0)
529         return MimeTypeConstants->application;
530       break;
531   }
532   return [NSStringClass stringWithCString:_type length:_len];
533 }
534
535 static NSString *_stringForSubType(char *_type, int _len) {
536   if (NSStringClass == Nil) NSStringClass = [NSString class];
537
538   if (MimeSubTypeConstants == NULL) {
539     MimeSubTypeConstants = malloc(sizeof(NGMimeSubTypeConstants));
540
541     MimeSubTypeConstants->plain       = @"plain";
542     MimeSubTypeConstants->star        = @"*";
543     MimeSubTypeConstants->mixed       = @"mixed";
544     MimeSubTypeConstants->jpeg        = @"jpeg";
545     MimeSubTypeConstants->png         = @"png";
546     MimeSubTypeConstants->gif         = @"gif";
547     MimeSubTypeConstants->xml         = @"xml";
548     MimeSubTypeConstants->html        = @"html";
549     MimeSubTypeConstants->css         = @"css";
550     MimeSubTypeConstants->xMng        = @"xMng";
551     MimeSubTypeConstants->xhtmlXml    = @"xhtmlXml";
552     MimeSubTypeConstants->rfc822      = @"rfc822";
553     MimeSubTypeConstants->octetStream = @"octet-stream";
554   }
555   switch (_len) {
556     case 0:
557       return @"";
558
559     case 1:
560       if (_type[0] == '*')
561         return MimeSubTypeConstants->star;
562       break;
563     case 3:
564       if (_type[0] == 'p') {
565         if (strncmp(_type, "png", 3) == 0) 
566           return MimeSubTypeConstants->png;
567       }
568       else if (_type[0] == 'g') {
569         if (strncmp(_type, "gif", 3) == 0) 
570           return MimeSubTypeConstants->gif;
571       }
572       else if (_type[0] == 'c') {
573         if (strncmp(_type, "css", 3) == 0) 
574           return MimeSubTypeConstants->css;
575       }
576       else if (_type[0] == 'x') {
577         if (strncmp(_type, "xml", 3) == 0) 
578           return MimeSubTypeConstants->xml;
579       }
580       break;
581     case 4:
582       if (_type[0] == 'h') {
583         if (strncmp(_type, "html", 4) == 0) 
584           return MimeSubTypeConstants->html;
585       }
586       else if (_type[0] == 'j') {
587         if (strncmp(_type, "jpeg", 4) == 0) 
588           return MimeSubTypeConstants->jpeg;
589       }
590       break;
591     case 5:
592       if (_type[0] == 'p') {
593         if (strncmp(_type, "plain", 5) == 0) 
594           return MimeSubTypeConstants->plain;
595       }
596       else if (_type[0] == 'm') {
597         if (strncmp(_type, "mixed", 5) == 0) 
598           return MimeSubTypeConstants->mixed;
599       }
600       else if (_type[0] == 'x') {
601         if (strncmp(_type, "x-mng", 5) == 0) 
602           return MimeSubTypeConstants->xMng;
603       }
604       break;
605     case 6:
606       if (strncmp(_type, "rfc822", 6) == 0) 
607           return MimeSubTypeConstants->rfc822;
608       break;
609     case 9:
610       if (strncmp(_type, "xhtml+xml", 9) == 0) 
611           return MimeSubTypeConstants->xhtmlXml;
612       break;
613     case 12:
614       if (strncmp(_type, "octet-stream", 12) == 0) 
615           return MimeSubTypeConstants->octetStream;
616       break;
617   }
618   return [NSStringClass stringWithCString:_type length:_len];
619 }
620
621 static BOOL _parseMimeType(id self, NSString *_str, NSString **type,
622                            NSString **subType, NSDictionary **parameters)
623 {
624   unsigned len;
625   unichar  *cstr, *tmp;
626   unsigned slen  = [_str length];
627   unichar  buf[slen + 1];
628
629   len  = 0;
630   cstr = &(buf[0]);
631
632   [_str getCharacters:buf]; buf[slen] = '\0';
633
634   /* skip leading spaces */
635   while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
636     cstr++;
637
638   /* type name */
639   tmp = cstr; // keep beginning of type name
640   len = 0;
641   while ((*cstr != '/') && (*cstr != '\0') && (*cstr != ';')) {
642     cstr++;
643     len++;
644   }
645   if (len == 0) return NO; // no type was read
646
647   {
648     unsigned char     buf[len + 1];
649     register unsigned i;
650     
651     buf[len] = '\0';
652     for (i = 0; i < len; i++) buf[i] = tolower(tmp[i]);
653     *type = _stringForType(buf, len);
654   }
655
656   if (*cstr == '/') { // subtype name
657     cstr++; // skip '/'
658
659     tmp = cstr; // keep beginning of subtype name
660     len = 0;
661     while ((*cstr != ';') && (!isRfc822_LWSP(*cstr)) && (*cstr != '\0')) {
662       cstr++;
663       len++;
664     }
665     if (len <= 0) {
666       *subType = @"*";
667       return YES; // no subtype was read      
668     }
669     else {
670       unsigned char     buf[len + 1];
671       register unsigned i;
672       
673       buf[len] = '\0';
674       for (i = 0; i < len; i++) buf[i] = tolower(tmp[i]);
675       *subType = _stringForSubType(buf, len);
676     }
677   }
678   else {
679     *subType = @"*";
680   }
681
682   // skip spaces
683   while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
684     cstr++;
685   
686   if (*cstr == ';') // skip ';' (parameter separator)
687     cstr++;
688
689   // skip spaces
690   while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
691     cstr++;
692
693   if (*cstr == '\0') { // string ends, no parameters defined
694     *parameters = nil;
695     return YES;
696   }
697   // parse parameters
698   *parameters = parseParameters(self, _str, cstr);
699   if (![*parameters count])
700     *parameters = nil;
701
702   return YES;
703 }