2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
23 #include "NGMimeType.h"
24 #include "NGConcreteMimeType.h"
25 #include "NGMimeUtilities.h"
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";
37 static BOOL _parseMimeType(id self, NSString *_str, NSString **type,
38 NSString **subType, NSDictionary **parameters);
40 @implementation NGMimeType
46 static NSMutableDictionary *typeToClass = nil;
49 classForType(NSString *_type, NSString *_subType, NSDictionary *_parameters)
52 if (_type == nil) return Nil;
54 if ([_type isEqualToString:@"*"] || [_subType isEqualToString:@"*"])
55 return [NGConcreteWildcardType class];
57 if ([_type isEqualToString:NGMimeTypeApplication]) {
58 if ([_subType isEqualToString:@"octet"])
59 return [NGConcreteAppOctetMimeType class];
61 if ([_type isEqualToString:NGMimeTypeText]) {
62 if ([_subType isEqualToString:@"x-vcard"])
63 return [NGConcreteTextVcardMimeType class];
66 c = [typeToClass objectForKey:_type];
67 return c ? c : [NGConcreteGenericMimeType class];
69 static Class NSStringClass = Nil;
72 static BOOL isInitialized = NO;
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];
94 + (NSStringEncoding)stringEncodingForCharset:(NSString *)_s {
96 NSStringEncoding encoding;
97 BOOL foundUnsupported;
99 foundUnsupported = NO;
100 charset = [_s lowercaseString];
102 if ([charset length] == 0)
103 encoding = [NSString defaultCStringEncoding];
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;
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;
121 /* some unsupported, but known encoding */
122 else if ([charset isEqualToString:@"ks_c_5601-1987"]) {
123 encoding = [NSString defaultCStringEncoding];
124 foundUnsupported = YES;
126 else if ([charset isEqualToString:@"euc-kr"]) {
127 encoding = [NSString defaultCStringEncoding];
128 foundUnsupported = YES;
130 else if ([charset isEqualToString:@"big5"]) {
131 encoding = [NSString defaultCStringEncoding];
132 foundUnsupported = YES;
134 else if ([charset isEqualToString:@"iso-2022-jp"]) {
135 encoding = [NSString defaultCStringEncoding];
136 foundUnsupported = YES;
138 else if ([charset isEqualToString:@"gb2312"]) {
139 encoding = [NSString defaultCStringEncoding];
140 foundUnsupported = YES;
142 else if ([charset isEqualToString:@"koi8-r"]) {
143 encoding = [NSString defaultCStringEncoding];
144 foundUnsupported = YES;
147 else if ([charset isEqualToString:@"windows-1252"]) {
148 encoding = NSWindowsCP1252StringEncoding;
150 else if ([charset isEqualToString:@"iso-8859-2"]) {
151 encoding = NSISOLatin2StringEncoding;
153 else if ([charset isEqualToString:@"x-unknown"] ||
154 [charset isEqualToString:@"unknown"]) {
155 encoding = NSASCIIStringEncoding;
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;
167 [self logWithFormat:@"%s: unknown charset '%@'",
168 __PRETTY_FUNCTION__, _s];
169 encoding = [NSString defaultCStringEncoding];
176 - (id)initWithType:(NSString *)_type subType:(NSString *)_subType
177 parameters:(NSDictionary *)_parameters
181 c = classForType(_type, _subType, _parameters);
184 return [[c alloc] initWithType:_type subType:_subType
185 parameters:_parameters];
188 + (id)mimeType:(NSString *)_type subType:(NSString *)_subType {
191 c = classForType(_type, _subType, nil);
193 NSAssert(c, @"did not find class for mimetype ..");
195 return [[[c alloc] initWithType:_type subType:_subType
196 parameters:nil] autorelease];
199 + (id)mimeType:(NSString *)_type subType:(NSString *)_subType
200 parameters:(NSDictionary *)_parameters
204 c = classForType(_type, _subType, _parameters);
205 NSAssert(c, @"did not find class for mimetype ..");
207 return [[[c alloc] initWithType:_type subType:_subType
208 parameters:_parameters] autorelease];
211 + (id)mimeType:(NSString *)_stringValue {
212 NSString *type, *subType;
213 NSDictionary *parameters;
215 if ([_stringValue length] == 0)
223 if (_parseMimeType(self, _stringValue, &type, &subType, ¶meters)) {
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 ..");
233 NSAssert(result, @"allocation of mimetype failed ..");
235 result = [result initWithType:type subType:subType parameters:parameters];
236 NSAssert(result, @"initialization of mimetype failed ..");
238 result = [result autorelease];
239 NSAssert(result, @"autorelease of mimetype failed ..");
244 [self logWithFormat:@"ERROR[%s]: parsing of mimetype '%@' failed !",
245 __PRETTY_FUNCTION__, _stringValue];
246 return nil; // parsing failed
253 [self subclassResponsibility:_cmd];
256 - (NSString *)subType {
257 [self subclassResponsibility:_cmd];
260 - (BOOL)isCompositeType {
261 [self subclassResponsibility:_cmd];
265 /* comparing types */
267 - (BOOL)isEqual:(id)_other {
268 if (_other == nil) return NO;
269 if (_other == self) return YES;
271 return ([_other isKindOfClass:[NGMimeType class]])
272 ? [self isEqualToMimeType:_other]
276 - (BOOL)isEqualToMimeType:(NGMimeType *)_type {
277 if (_type == nil) return NO;
278 if (_type == self) return YES;
280 if (![self hasSameType:_type])
283 if (![[_type parametersAsDictionary] isEqual:[self parametersAsDictionary]])
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;
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;
304 - (BOOL)doesMatchType:(NGMimeType *)_other { // interpretes wildcards
305 NSString *t, *st, *ot, *ost;
310 ost = [_other subType];
312 if ([t isEqualToString:@"*"] || [ot isEqualToString:@"*"]) {
316 if (![t isEqualToString:ot]) return NO;
318 if ([st isEqualToString:@"*"] || [ost isEqualToString:@"*"]) {
322 if (![st isEqualToString:ost]) return NO;
329 - (NSEnumerator *)parameterNames {
330 [self doesNotRecognizeSelector:_cmd]; // subclass
333 - (id)valueOfParameter:(NSString *)_parameterName {
334 [self doesNotRecognizeSelector:_cmd]; // subclass
338 /* representations */
340 - (NSDictionary *)parametersAsDictionary {
341 NSMutableDictionary *parameters;
346 if ((names = [self parameterNames]) == nil)
349 parameters = [[NSMutableDictionary alloc] init];
350 while ((name = [names nextObject]))
351 [parameters setObject:[self valueOfParameter:name] forKey:name];
353 d = [parameters copy];
354 [parameters release];
355 return [d autorelease];
358 - (NSString *)parametersAsString {
360 NSMutableString *result;
363 if ((names = [self parameterNames]) == nil)
366 result = [NSMutableString stringWithCapacity:64];
367 while ((name = [names nextObject])) {
370 value = [[self valueOfParameter:name] stringValue];
372 [result appendString:@"; "];
373 [result appendString:name];
374 [result appendString:@"="];
376 if ([self valueNeedsQuotes:value]) {
377 [result appendString:@"\""];
378 [result appendString:value];
379 [result appendString:@"\""];
382 [result appendString:value];
387 - (BOOL)valueNeedsQuotes:(NSString *)_parameterValue {
388 unsigned len = [_parameterValue cStringLength];
394 [_parameterValue getCString:cstr]; cstr[len] = '\0';
396 if (isMime_SpecialByte(*cstr))
407 - (NSString *)stringValue {
408 [self subclassResponsibility:_cmd];
414 - (Class)classForCoder {
415 return [NGMimeType class];
418 - (void)encodeWithCoder:(NSCoder *)_encoder {
419 [_encoder encodeObject:[self type]];
420 [_encoder encodeObject:[self subType]];
421 [_encoder encodeObject:[self parametersAsDictionary]];
424 - (id)initWithCoder:(NSCoder *)_decoder {
425 NSString *type, *subType;
428 type = [_decoder decodeObject];
429 subType = [_decoder decodeObject];
430 paras = [_decoder decodeObject];
432 return [self initWithType:type subType:subType parameters:paras];
437 - (id)copyWithZone:(NSZone *)_zone {
438 return [[NGMimeType allocWithZone:_zone]
439 initWithType:[self type] subType:[self subType]
440 parameters:[self parametersAsDictionary]];
445 - (NSString *)description {
446 return [NSString stringWithFormat:@"<NGMimeType: %@>", [self stringValue]];
449 @end /* NGMimeType */
457 NSString *application;
460 } NGMimeTypeConstants;
475 NSString *octetStream;
476 } NGMimeSubTypeConstants;
478 static NGMimeTypeConstants *MimeTypeConstants = NULL;
479 static NGMimeSubTypeConstants *MimeSubTypeConstants = NULL;
481 static NSString *_stringForType(char *_type, int _len) {
482 if (NSStringClass == Nil) NSStringClass = [NSString class];
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;
500 return MimeTypeConstants->star;
503 if (strncmp(_type, "text", 4) == 0)
504 return MimeTypeConstants->text;
507 if (_type[0] == 'i') {
508 if (strncmp(_type, "image", 5) == 0)
509 return MimeTypeConstants->image;
511 else if (_type[0] == 'v') {
512 if (strncmp(_type, "video", 5) == 0)
513 return MimeTypeConstants->video;
515 else if (_type[0] == 'a') {
516 if (strncmp(_type, "audio", 5) == 0)
517 return MimeTypeConstants->audio;
521 if (strncmp(_type, "message", 7) == 0)
522 return MimeTypeConstants->message;
525 if (strncmp(_type, "multipart", 9) == 0)
526 return MimeTypeConstants->multipart;
528 if (strncmp(_type, "application", 11) == 0)
529 return MimeTypeConstants->application;
532 return [NSStringClass stringWithCString:_type length:_len];
535 static NSString *_stringForSubType(char *_type, int _len) {
536 if (NSStringClass == Nil) NSStringClass = [NSString class];
538 if (MimeSubTypeConstants == NULL) {
539 MimeSubTypeConstants = malloc(sizeof(NGMimeSubTypeConstants));
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";
561 return MimeSubTypeConstants->star;
564 if (_type[0] == 'p') {
565 if (strncmp(_type, "png", 3) == 0)
566 return MimeSubTypeConstants->png;
568 else if (_type[0] == 'g') {
569 if (strncmp(_type, "gif", 3) == 0)
570 return MimeSubTypeConstants->gif;
572 else if (_type[0] == 'c') {
573 if (strncmp(_type, "css", 3) == 0)
574 return MimeSubTypeConstants->css;
576 else if (_type[0] == 'x') {
577 if (strncmp(_type, "xml", 3) == 0)
578 return MimeSubTypeConstants->xml;
582 if (_type[0] == 'h') {
583 if (strncmp(_type, "html", 4) == 0)
584 return MimeSubTypeConstants->html;
586 else if (_type[0] == 'j') {
587 if (strncmp(_type, "jpeg", 4) == 0)
588 return MimeSubTypeConstants->jpeg;
592 if (_type[0] == 'p') {
593 if (strncmp(_type, "plain", 5) == 0)
594 return MimeSubTypeConstants->plain;
596 else if (_type[0] == 'm') {
597 if (strncmp(_type, "mixed", 5) == 0)
598 return MimeSubTypeConstants->mixed;
600 else if (_type[0] == 'x') {
601 if (strncmp(_type, "x-mng", 5) == 0)
602 return MimeSubTypeConstants->xMng;
606 if (strncmp(_type, "rfc822", 6) == 0)
607 return MimeSubTypeConstants->rfc822;
610 if (strncmp(_type, "xhtml+xml", 9) == 0)
611 return MimeSubTypeConstants->xhtmlXml;
614 if (strncmp(_type, "octet-stream", 12) == 0)
615 return MimeSubTypeConstants->octetStream;
618 return [NSStringClass stringWithCString:_type length:_len];
621 static BOOL _parseMimeType(id self, NSString *_str, NSString **type,
622 NSString **subType, NSDictionary **parameters)
626 unsigned slen = [_str length];
627 unichar buf[slen + 1];
632 [_str getCharacters:buf]; buf[slen] = '\0';
634 /* skip leading spaces */
635 while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
639 tmp = cstr; // keep beginning of type name
641 while ((*cstr != '/') && (*cstr != '\0') && (*cstr != ';')) {
645 if (len == 0) return NO; // no type was read
648 unsigned char buf[len + 1];
652 for (i = 0; i < len; i++) buf[i] = tolower(tmp[i]);
653 *type = _stringForType(buf, len);
656 if (*cstr == '/') { // subtype name
659 tmp = cstr; // keep beginning of subtype name
661 while ((*cstr != ';') && (!isRfc822_LWSP(*cstr)) && (*cstr != '\0')) {
667 return YES; // no subtype was read
670 unsigned char buf[len + 1];
674 for (i = 0; i < len; i++) buf[i] = tolower(tmp[i]);
675 *subType = _stringForSubType(buf, len);
683 while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
686 if (*cstr == ';') // skip ';' (parameter separator)
690 while (isRfc822_LWSP(*cstr) && (*cstr != '\0'))
693 if (*cstr == '\0') { // string ends, no parameters defined
698 *parameters = parseParameters(self, _str, cstr);
699 if (![*parameters count])