448
792
intoData: (NSMutableData*)dData
449
793
withContext: (GSMimeCodingContext*)con
451
unsigned size = [dData length];
452
795
unsigned len = [sData length];
459
798
if (dData == nil || [con isKindOfClass: [GSMimeCodingContext class]] == NO)
461
800
[NSException raise: NSInvalidArgumentException
462
format: @"Bad destination data for decode"];
801
format: @"[%@ -%@] bad destination data for decode",
802
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
464
804
GS_RANGE_CHECK(aRange, len);
467
* Get pointers into source data buffer.
807
* Chunked decoding is relatively complex ... it makes sense to do it
808
* here, in order to make use of parser facilities, rather than having
809
* the decoding context do the work. In this case the context is used
810
* solely to store state information.
469
src = (const char *)[sData bytes];
470
src += aRange.location;
471
end = src + aRange.length;
474
if (ccls == [GSMimeBase64DecoderContext class])
476
GSMimeBase64DecoderContext *ctxt;
478
ctxt = (GSMimeBase64DecoderContext*)con;
481
* Expand destination data buffer to have capacity to handle info.
483
[dData setLength: size + (3 * (end + 8 - src))/4];
484
dst = (unsigned char*)[dData mutableBytes];
488
* Now decode data into buffer, keeping count and temporary
499
else if (islower(cc))
503
else if (isdigit(cc))
517
[ctxt setAtEnd: YES];
522
[ctxt setAtEnd: YES];
527
cc = -1; /* ignore */
532
ctxt->buf[ctxt->pos++] = cc;
536
decodebase64(dst, ctxt->buf);
543
* Odd characters at end of decoded data need to be added separately.
545
if ([ctxt atEnd] == YES && ctxt->pos > 0)
547
unsigned len = ctxt->pos - 1;;
549
while (ctxt->pos < 4)
551
ctxt->buf[ctxt->pos++] = '\0';
554
decodebase64(dst, ctxt->buf);
557
[dData setLength: size + dst - beg];
559
else if (ccls == [GSMimeQuotedDecoderContext class])
561
GSMimeQuotedDecoderContext *ctxt;
563
ctxt = (GSMimeQuotedDecoderContext*)con;
566
* Expand destination data buffer to have capacity to handle info.
568
[dData setLength: size + (end - src)];
569
dst = (unsigned char*)[dData mutableBytes];
576
if ((*src == '\n') || (*src == '\r'))
582
ctxt->buf[ctxt->pos++] = *src;
590
val = isdigit(c) ? (c - '0') : (c - 55);
593
val += isdigit(c) ? (c - '0') : (c - 55);
598
else if (*src == '=')
600
ctxt->buf[ctxt->pos++] = '=';
608
[dData setLength: size + dst - beg];
610
else if (ccls == [GSMimeChunkedDecoderContext class])
812
if ([con class] == [GSMimeChunkedDecoderContext class])
612
814
GSMimeChunkedDecoderContext *ctxt;
613
const char *footers = src;
815
unsigned size = [dData length];
615
822
ctxt = (GSMimeChunkedDecoderContext*)con;
825
* Get pointers into source data buffer.
827
src = (const char *)[sData bytes];
829
src += aRange.location;
830
end = src + aRange.length;
619
833
* Make sure buffer is big enough, and set up output pointers.
2081
2513
while (input < dataEnd && unwrappingComplete == NO)
2083
unsigned pos = input;
2085
if ((c = bytes[pos]) != '\r' && c != '\n')
2087
while (pos < dataEnd && (c = bytes[pos]) != '\r' && c != '\n')
2093
break; /* need more data */
2096
if (c == '\r' && pos < dataEnd && bytes[pos] == '\n')
2102
break; /* need more data */
2105
* Copy data up to end of line, and skip past end.
2107
while (input < dataEnd && (c = bytes[input]) != '\r' && c != '\n')
2109
bytes[lineEnd++] = bytes[input++];
2114
* Eat a newline that is part of a cr-lf sequence.
2117
if (c == '\r' && input < dataEnd && bytes[input] == '\n')
2515
if ((c = bytes[input]) != '\r' && c != '\n')
2123
* See if we have a wrapped line.
2125
if ((c = bytes[input]) == '\r' || c == '\n' || isspace(c) == 0)
2522
if (input < dataEnd && c == '\r' && bytes[input] == '\n')
2526
if (input < dataEnd || (c == '\n' && lineEnd == lineStart))
2528
unsigned length = lineEnd - lineStart;
2532
/* An empty line cannot be folded. */
2533
unwrappingComplete = YES;
2535
else if ((c = bytes[input]) != '\r' && c != '\n' && isspace(c))
2537
unsigned diff = input - lineEnd;
2539
memmove(&bytes[lineStart + diff], &bytes[lineStart], length);
2545
/* No folding ... done. */
2546
unwrappingComplete = YES;
2552
if (unwrappingComplete == YES)
2554
if (lineEnd == lineStart)
2127
unwrappingComplete = YES;
2128
bytes[lineEnd] = '\0';
2556
unsigned lengthRemaining;
2130
* If this is a zero-length line, we have reached the end of
2559
* Overwrite the header data with the body, ready to start
2560
* parsing the body data.
2133
if (lineEnd == lineStart)
2562
lengthRemaining = dataEnd - input;
2563
if (lengthRemaining > 0)
2135
unsigned lengthRemaining;
2138
* Overwrite the header data with the body, ready to start
2139
* parsing the body data.
2141
lengthRemaining = dataEnd - input;
2142
if (lengthRemaining > 0)
2144
memcpy(bytes, &bytes[input], lengthRemaining);
2146
dataEnd = lengthRemaining;
2147
[data setLength: lengthRemaining];
2148
bytes = (unsigned char*)[data mutableBytes];
2565
memcpy(bytes, &bytes[input], lengthRemaining);
2567
dataEnd = lengthRemaining;
2568
[data setLength: lengthRemaining];
2569
bytes = (unsigned char*)[data mutableBytes];
2157
NSDebugMLLog(@"GSMime", @"exit: inBody:%d unwrappingComplete: %d "
2158
@"input:%u dataEnd:%u lineStart:%u '%*.*s'", inBody, unwrappingComplete,
2159
input, dataEnd, lineStart, dataEnd - input, dataEnd - input, &bytes[input]);
2579
input = lineStart; /* Reset to try again with more data. */
2582
NSDebugMLLog(@"GSMimeH", @"exit: inBody:%d unwrappingComplete: %d "
2583
@"input:%u dataEnd:%u lineStart:%u '%*.*s'", flags.inBody,
2585
input, dataEnd, lineStart, lineEnd - lineStart, lineEnd - lineStart,
2160
2587
return unwrappingComplete;
2590
- (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info
2592
[self scanPastSpace: scanner];
2593
while ([scanner scanString: @";" intoString: 0] == YES)
2595
NSString *paramName;
2597
paramName = [self scanName: scanner];
2598
if ([paramName length] == 0)
2600
NSLog(@"Invalid Mime %@ field (parameter name)", [info name]);
2604
[self scanPastSpace: scanner];
2605
if ([scanner scanString: @"=" intoString: 0] == YES)
2607
NSString *paramValue;
2609
paramValue = [self scanToken: scanner];
2610
[self scanPastSpace: scanner];
2611
if (paramValue == nil)
2615
[info setParameter: paramValue forKey: paramName];
2619
NSLog(@"Ignoring Mime %@ field parameter (%@)",
2620
[info name], paramName);
2630
@implementation GSMimeHeader
2632
static NSCharacterSet *nonToken = nil;
2633
static NSCharacterSet *tokenSet = nil;
2637
if (nonToken == nil)
2639
NSMutableCharacterSet *ms;
2641
ms = [NSMutableCharacterSet new];
2642
[ms addCharactersInRange: NSMakeRange(33, 126-32)];
2643
[ms removeCharactersInString: @"()<>@,;:\\\"/[]?="];
2644
tokenSet = [ms copy];
2646
nonToken = RETAIN([tokenSet invertedSet]);
2647
if (NSArrayClass == 0)
2649
NSArrayClass = [NSArray class];
2655
* Makes the value into a quoted string if necessary (ie if it contains
2656
* any special / non-token characters). If flag is YES then the value
2657
* is made into a quoted string even if it does not contain special characters.
2659
+ (NSString*) makeQuoted: (NSString*)v always: (BOOL)flag
2663
unsigned l = [v length];
2665
r = [v rangeOfCharacterFromSet: nonToken
2666
options: NSLiteralSearch
2667
range: NSMakeRange(pos, l - pos)];
2668
if (flag == YES || r.length > 0)
2670
NSMutableString *m = [NSMutableString new];
2672
[m appendString: @"\""];
2673
while (r.length > 0)
2677
if (r.location > pos)
2680
[v substringWithRange: NSMakeRange(pos, r.location - pos)]];
2682
pos = r.location + 1;
2683
c = [v characterAtIndex: r.location];
2686
if (c == '\\' || c == '"')
2688
[m appendFormat: @"\\%c", c];
2692
[m appendFormat: @"%c", c];
2697
NSLog(@"NON ASCII characters not yet implemented");
2699
r = [v rangeOfCharacterFromSet: nonToken
2700
options: NSLiteralSearch
2701
range: NSMakeRange(pos, l - pos)];
2706
[v substringWithRange: NSMakeRange(pos, l - pos)]];
2708
[m appendString: @"\""];
2715
* Convert the supplied string to a standardized token by making it
2716
* lowercase and removing all illegal characters.
2718
+ (NSString*) makeToken: (NSString*)t
2722
t = [t lowercaseString];
2723
r = [t rangeOfCharacterFromSet: nonToken];
2726
NSMutableString *m = [t mutableCopy];
2728
while (r.length > 0)
2730
[m deleteCharactersInRange: r];
2731
r = [m rangeOfCharacterFromSet: nonToken];
2738
- (id) copyWithZone: (NSZone*)z
2740
GSMimeHeader *c = [GSMimeHeader allocWithZone: z];
2744
c = [c initWithName: [self name]
2746
parameters: [self parameters]];
2747
e = [objects keyEnumerator];
2748
while ((k = [e nextObject]) != nil)
2750
[c setObject: [self objectForKey: k] forKey: k];
2764
- (NSString*) description
2766
NSMutableString *desc;
2768
desc = [NSMutableString stringWithFormat: @"GSMimeHeader <%0x> -\n", self];
2769
[desc appendFormat: @" name: %@\n", [self name]];
2770
[desc appendFormat: @" value: %@\n", [self value]];
2771
[desc appendFormat: @" params: %@\n", [self parameters]];
2777
return [self initWithName: @"unknown" value: @"none" parameters: nil];
2781
* Convenience method calling -initWithName:value:parameters: with the
2782
* supplied argument and nil parameters.
2784
- (id) initWithName: (NSString*)n
2787
return [self initWithName: n value: v parameters: nil];
2792
* Initialise a GSMimeHeader supplying a name, a value and a dictionary
2793
* of any parameters occurring after the value.
2795
- (id) initWithName: (NSString*)n
2797
parameters: (NSDictionary*)p
2799
objects = [NSMutableDictionary new];
2800
params = [NSMutableDictionary new];
2803
[self setParameters: p];
2808
* Returns the name of this header ... a lowercase string.
2816
* Return extra information specific to a particular header type.
2818
- (id) objectForKey: (NSString*)k
2820
return [objects objectForKey: k];
2824
* Returns a dictionary of all the additional objects for the header.
2826
- (NSDictionary*) objects
2828
return AUTORELEASE([objects copy]);
2832
* Return the named parameter value.
2834
- (NSString*) parameterForKey: (NSString*)k
2836
NSString *p = [params objectForKey: k];
2840
k = [GSMimeHeader makeToken: k];
2841
p = [params objectForKey: k];
2847
* Returns the parameters of this header ... a dictionary whose keys
2848
* are all lowercase strings, and whose values are strings which may
2849
* contain mixed case.
2851
- (NSDictionary*) parameters
2853
return AUTORELEASE([params copy]);
2857
* Returns the full text of the header, built from its component parts,
2858
* and including a terminating CR-LF
2860
- (NSMutableData*) rawMimeData
2862
NSMutableData *md = [NSMutableData dataWithCapacity: 128];
2863
NSEnumerator *e = [params keyEnumerator];
2865
NSData *d = [[self name] dataUsingEncoding: NSASCIIStringEncoding];
2866
unsigned l = [d length];
2873
* Capitalise the header name. However, the version header is a special
2874
* case - it is defined as being literally 'MIME-Version'
2876
memcpy(buf, [d bytes], l);
2877
if (l == 12 && memcmp(buf, "mime-version", 12) == 0)
2879
memcpy(buf, "MIME-Version", 12);
2887
if (islower(buf[i]))
2889
buf[i] = toupper(buf[i]);
2892
if (buf[i++] == '-')
2902
[md appendBytes: buf length: l];
2903
d = wordData(value);
2904
if ([md length] + [d length] + 2 > LIM)
2906
[md appendBytes: ":\r\n\t" length: 4];
2908
l = [md length] + 8;
2912
[md appendBytes: ": " length: 2];
2917
while ((k = [e nextObject]) != nil)
2925
v = [GSMimeHeader makeQuoted: [params objectForKey: k] always: NO];
2931
if ((l + kl + vl + 3) > LIM)
2933
[md appendBytes: ";\r\n\t" length: 4];
2934
[md appendData: kd];
2935
[md appendBytes: "=" length: 1];
2936
[md appendData: vd];
2941
[md appendBytes: "; " length: 2];
2942
[md appendData: kd];
2943
[md appendBytes: "=" length: 1];
2944
[md appendData: vd];
2948
[md appendBytes: "\r\n" length: 2];
2954
* Sets the name of this header ... converts to lowercase and removes
2955
* illegal characters. If given a nil or empty string argument,
2956
* sets the name to 'unknown'.
2958
- (void) setName: (NSString*)s
2960
s = [GSMimeHeader makeToken: s];
2961
if ([s length] == 0)
2969
* Method to store specific information for particular types of
2970
* header. This is used for non-standard parts of headers.<br />
2971
* Setting a nil value for o will remove any existing value set
2972
* using the k as its key.
2974
- (void) setObject: (id)o forKey: (NSString*)k
2978
[objects removeObjectForKey: k];
2982
[objects setObject: o forKey: k];
2987
* Sets a parameter of this header ... converts name to lowercase and
2988
* removes illegal characters.<br />
2989
* If a nil parameter name is supplied, removes any parameter with the
2992
- (void) setParameter: (NSString*)v forKey: (NSString*)k
2994
k = [GSMimeHeader makeToken: k];
2997
[params removeObjectForKey: k];
3001
[params setObject: v forKey: k];
3006
* Sets all parameters of this header ... converts names to lowercase
3007
* and removes illegal characters from them.
3009
- (void) setParameters: (NSDictionary*)d
3011
NSMutableDictionary *m = [NSMutableDictionary new];
3012
NSEnumerator *e = [d keyEnumerator];
3015
while ((k = [e nextObject]) != nil)
3017
[m setObject: [d objectForKey: k] forKey: [GSMimeHeader makeToken: k]];
3024
* Sets the value of this header (without changing parameters)<br />
3025
* If given a nil argument, set an empty string value.
3027
- (void) setValue: (NSString*)s
3037
* Returns the full text of the header, built from its component parts,
3038
* and including a terminating CR-LF
3042
NSString *s = [NSString alloc];
3044
s = [s initWithData: [self rawMimeData] encoding: NSASCIIStringEncoding];
3045
return AUTORELEASE(s);
3049
* Returns the value of this header (excluding any parameters)
3059
@interface GSMimeDocument (Private)
3060
- (unsigned) _indexOfHeaderNamed: (NSString*)name;
2174
3071
* The class keeps track of all the document headers, and provides
2175
* methods for modifying the headers that apply to a document and
2176
* for looking at the header structures, by providing an info
2177
* dictionary containing the various parts of a header.
2180
* The common dictionary keys used for elements provided for
2181
* <em>all</em> headers are -
2184
* <term>RawHeader</term>
2185
* <desc>This is the unmodified text of the header
2187
* <term>BaseName</term>
2188
* <desc>This is the header name.
2190
* <term>BaseValue</term>
2191
* <desc>This is the text after the header name and colon.
2194
* <desc>This is a lowercase representation of the header name.
2196
* <term>Value</term>
2197
* <desc>This is the value of the header (normally lower case).
2198
* It may only be a small subset of the information in the header
2199
* with other information being split into separate fields
2200
* depending on the type of header.
3072
* methods for modifying and examining the headers that apply to a
2204
3076
@implementation GSMimeDocument
3079
* Return the MIME characterset name corresponding to the
3080
* specified string encoding.
3082
+ (NSString*) charsetFromEncoding: (NSStringEncoding)enc
3084
if (enc == NSASCIIStringEncoding)
3085
return @"us-ascii"; // Default character set.
3086
if (enc == NSISOLatin1StringEncoding)
3087
return @"iso-8859-1";
3088
if (enc == NSISOLatin2StringEncoding)
3089
return @"iso-8859-2";
3090
if (enc == NSISOLatin3StringEncoding)
3091
return @"iso-8859-3";
3092
if (enc == NSISOLatin4StringEncoding)
3093
return @"iso-8859-4";
3094
if (enc == NSISOCyrillicStringEncoding)
3095
return @"iso-8859-5";
3096
if (enc == NSISOArabicStringEncoding)
3097
return @"iso-8859-6";
3098
if (enc == NSISOGreekStringEncoding)
3099
return @"iso-8859-7";
3100
if (enc == NSISOHebrewStringEncoding)
3101
return @"iso-8859-8";
3102
if (enc == NSISOLatin5StringEncoding)
3103
return @"iso-8859-9";
3104
if (enc == NSISOLatin6StringEncoding)
3105
return @"iso-8859-10";
3106
if (enc == NSISOLatin7StringEncoding)
3107
return @"iso-8859-13";
3108
if (enc == NSISOLatin8StringEncoding)
3109
return @"iso-8859-14";
3110
if (enc == NSISOLatin9StringEncoding)
3111
return @"iso-8859-15";
3112
if (enc == NSWindowsCP1250StringEncoding)
3113
return @"windows-1250";
3114
if (enc == NSWindowsCP1251StringEncoding)
3115
return @"windows-1251";
3116
if (enc == NSWindowsCP1252StringEncoding)
3117
return @"windows-1252";
3118
if (enc == NSWindowsCP1253StringEncoding)
3119
return @"windows-1253";
3120
if (enc == NSWindowsCP1254StringEncoding)
3121
return @"windows-1254";
3122
if (enc == NSBIG5StringEncoding)
3124
if (enc == NSShiftJISStringEncoding)
3125
return @"shift_JIS";
3130
* Decode the source data from base64 encoding and return the result.
3132
+ (NSData*) decodeBase64: (NSData*)source
3136
const signed char *src;
3137
const signed char *end;
3138
unsigned char *result;
3140
unsigned char buf[4];
3147
length = [source length];
3150
return [NSData data];
3152
declen = ((length + 3) * 3)/4;
3153
src = (const char*)[source bytes];
3156
result = (unsigned char*)NSZoneMalloc(NSDefaultMallocZone(), declen);
3159
while ((src != end) && *src != '\0')
3167
else if (islower(c))
3171
else if (isdigit(c))
3193
c = -1; /* ignore */
3202
decodebase64(dst, buf);
3212
for (i = pos; i < 4; i++)
3219
unsigned char tail[3];
3220
decodebase64(tail, buf);
3221
memcpy(dst, tail, pos);
3225
return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
3226
initWithBytesNoCopy: result length: dst - result]);
3230
* Converts the base64 encoded data in source to a decoded ASCII string
3231
* using the +decodeBase64: method. If the encoded data does not represent
3232
* an ASCII string, you should use the +decodeBase64: method directly.
3234
+ (NSString*) decodeBase64String: (NSString*)source
3236
NSData *d = [source dataUsingEncoding: NSASCIIStringEncoding];
3239
d = [self decodeBase64: d];
3242
r = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding];
3249
* Convenience method to return an autoreleased document using the
3250
* specified content, type, and name value. This calls the
3251
* -setContent:type:name: method to set up the document.
3253
+ (GSMimeDocument*) documentWithContent: (id)newContent
3254
type: (NSString*)type
3255
name: (NSString*)name
3257
GSMimeDocument *doc = AUTORELEASE([self new]);
3259
[doc setContent: newContent type: type name: name];
3264
* Encode the source data to base64 encoding and return the result.
3266
+ (NSData*) encodeBase64: (NSData*)source
3270
unsigned char *sBuf;
3271
unsigned char *dBuf;
3277
length = [source length];
3280
return [NSData data];
3282
destlen = 4 * ((length + 2) / 3);
3283
sBuf = (unsigned char*)[source bytes];
3284
dBuf = NSZoneMalloc(NSDefaultMallocZone(), destlen);
3286
destlen = encodebase64(dBuf, sBuf, length);
3288
return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
3289
initWithBytesNoCopy: dBuf length: destlen]);
3293
* Converts the ASCII string source into base64 encoded data using the
3294
* +encodeBase64: method. If the original data is not an ASCII string,
3295
* you should use the +encodeBase64: method directly.
3297
+ (NSString*) encodeBase64String: (NSString*)source
3299
NSData *d = [source dataUsingEncoding: NSASCIIStringEncoding];
3302
d = [self encodeBase64: d];
3305
r = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding];
3312
* Return the string encoding corresponding to the specified MIME
3313
* characterset name.
3315
+ (NSStringEncoding) encodingFromCharset: (NSString*)charset
3319
return NSASCIIStringEncoding; // Default character set.
3322
charset = [charset lowercaseString];
3325
* Try the three most popular charactersets first - for efficiency.
3327
if ([charset isEqualToString: @"us-ascii"] == YES)
3328
return NSASCIIStringEncoding;
3329
if ([charset isEqualToString: @"iso-8859-1"] == YES)
3330
return NSISOLatin1StringEncoding;
3331
if ([charset isEqualToString: @"utf-8"] == YES)
3332
return NSUTF8StringEncoding;
3335
* Now try all remaining character sets in alphabetical order.
3337
if ([charset isEqualToString: @"ascii"] == YES)
3338
return NSASCIIStringEncoding;
3339
if ([charset isEqualToString: @"iso-8859-2"] == YES)
3340
return NSISOLatin2StringEncoding;
3341
if ([charset isEqualToString: @"iso-8859-3"] == YES)
3342
return NSISOLatin3StringEncoding;
3343
if ([charset isEqualToString: @"iso-8859-4"] == YES)
3344
return NSISOLatin4StringEncoding;
3345
if ([charset isEqualToString: @"iso-8859-5"] == YES)
3346
return NSISOCyrillicStringEncoding;
3347
if ([charset isEqualToString: @"iso-8859-6"] == YES)
3348
return NSISOArabicStringEncoding;
3349
if ([charset isEqualToString: @"iso-8859-7"] == YES)
3350
return NSISOGreekStringEncoding;
3351
if ([charset isEqualToString: @"iso-8859-8"] == YES)
3352
return NSISOHebrewStringEncoding;
3353
if ([charset isEqualToString: @"iso-8859-9"] == YES)
3354
return NSISOLatin5StringEncoding;
3355
if ([charset isEqualToString: @"iso-8859-10"] == YES)
3356
return NSISOLatin6StringEncoding;
3357
if ([charset isEqualToString: @"iso-8859-13"] == YES)
3358
return NSISOLatin7StringEncoding;
3359
if ([charset isEqualToString: @"iso-8859-14"] == YES)
3360
return NSISOLatin8StringEncoding;
3361
if ([charset isEqualToString: @"iso-8859-15"] == YES)
3362
return NSISOLatin9StringEncoding;
3363
if ([charset isEqualToString: @"windows-1250"] == YES)
3364
return NSWindowsCP1250StringEncoding;
3365
if ([charset isEqualToString: @"windows-1251"] == YES)
3366
return NSWindowsCP1251StringEncoding;
3367
if ([charset isEqualToString: @"windows-1252"] == YES)
3368
return NSWindowsCP1252StringEncoding;
3369
if ([charset isEqualToString: @"windows-1253"] == YES)
3370
return NSWindowsCP1253StringEncoding;
3371
if ([charset isEqualToString: @"windows-1254"] == YES)
3372
return NSWindowsCP1254StringEncoding;
3373
if ([charset isEqualToString: @"iso-10646-ucs-2"] == YES)
3374
return NSUnicodeStringEncoding;
3375
if ([charset isEqualToString: @"iso-10646"] == YES)
3376
return NSUnicodeStringEncoding;
3377
if ([charset isEqualToString: @"big5"] == YES)
3378
return NSBIG5StringEncoding;
3379
if ([charset isEqualToString: @"shift_JIS"] == YES)
3380
return NSShiftJISStringEncoding;
3382
return NSASCIIStringEncoding; // Default character set.
2206
3385
+ (void) initialize
2208
3387
if (self == [GSMimeDocument class])
2279
3552
* <term>multipart</term>
2280
3553
* <desc>an NSArray object containing GSMimeDocument objects</desc>
3555
* If you want to be sure that you get a particular type of data, use the
3556
* -convertToData or -convertToText method.
2285
3560
return content;
3564
* Search the content of this document to locate a part whose content ID
3565
* matches the specified key. Recursively descend into other documents.<br />
3566
* Wraps the supplied key in angle brackets if they are not present.<br />
3567
* Return nil if no match is found, the matching GSMimeDocument otherwise.
3569
- (id) contentByID: (NSString*)key
3571
if ([key hasPrefix: @"<"] == NO)
3573
key = [NSString stringWithFormat: @"<%@>", key];
3575
if ([content isKindOfClass: NSArrayClass] == YES)
3577
NSEnumerator *e = [content objectEnumerator];
3580
while ((d = [e nextObject]) != nil)
3582
if ([[d contentID] isEqualToString: key] == YES)
3586
d = [d contentByID: key];
3597
* Search the content of this document to locate a part whose content ID
3598
* matches the specified key. Recursively descend into other documents.<br />
3599
* Wraps the supplied key in angle brackets if they are not present.<br />
3600
* Return nil if no match is found, the matching GSMimeDocument otherwise.
3602
- (id) contentByLocation: (NSString*)key
3604
if ([content isKindOfClass: NSArrayClass] == YES)
3606
NSEnumerator *e = [content objectEnumerator];
3609
while ((d = [e nextObject]) != nil)
3611
if ([[d contentLocation] isEqualToString: key] == YES)
3615
d = [d contentByLocation: key];
3626
* Search the content of this document to locate a part whose content-type
3627
* name or content-disposition name matches the specified key.
3628
* Recursively descend into other documents.<br />
3629
* Return nil if no match is found, the matching GSMimeDocument otherwise.
3631
- (id) contentByName: (NSString*)key
3634
if ([content isKindOfClass: NSArrayClass] == YES)
3636
NSEnumerator *e = [content objectEnumerator];
3639
while ((d = [e nextObject]) != nil)
3643
hdr = [d headerNamed: @"content-type"];
3644
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
3648
hdr = [d headerNamed: @"content-disposition"];
3649
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
3653
d = [d contentByName: key];
3664
* Convenience method to fetch the content file name from the header.
3666
- (NSString*) contentFile
3668
GSMimeHeader *hdr = [self headerNamed: @"content-disposition"];
3670
return [hdr parameterForKey: @"filename"];
3674
* Convenience method to fetch the content ID from the header.
3676
- (NSString*) contentID
3678
GSMimeHeader *hdr = [self headerNamed: @"content-id"];
3684
* Convenience method to fetch the content location from the header.
3686
- (NSString*) contentLocation
3688
GSMimeHeader *hdr = [self headerNamed: @"content-location"];
3694
* Convenience method to fetch the content name from the header.
3696
- (NSString*) contentName
3698
GSMimeHeader *hdr = [self headerNamed: @"content-type"];
3700
return [hdr parameterForKey: @"name"];
3704
* Convenience method to fetch the content sub-type from the header.
3706
- (NSString*) contentSubtype
3708
GSMimeHeader *hdr = [self headerNamed: @"content-type"];
3709
NSString *val = nil;
3713
val = [hdr objectForKey: @"Subtype"];
3721
r = [val rangeOfString: @"/"];
3724
val = [val substringFromIndex: r.location + 1];
3725
r = [val rangeOfString: @"/"];
3728
val = [val substringToIndex: r.location];
3730
val = [val stringByTrimmingSpaces];
3731
[hdr setObject: val forKey: @"Subtype"];
3745
* Convenience method to fetch the content type from the header.
3747
- (NSString*) contentType
3749
GSMimeHeader *hdr = [self headerNamed: @"content-type"];
3750
NSString *val = nil;
3754
val = [hdr objectForKey: @"Type"];
3762
r = [val rangeOfString: @"/"];
3765
val = [val substringToIndex: r.location];
3766
val = [val stringByTrimmingSpaces];
3768
[hdr setObject: val forKey: @"Type"];
3777
* Search the content of this document to locate all parts whose content-type
3778
* name or content-disposition name matches the specified key.
3779
* Do <em>NOT</em> recurse into other documents.<br />
3780
* Return nil if no match is found, an array of matching GSMimeDocument
3781
* instances otherwise.
3783
- (NSArray*) contentsByName: (NSString*)key
3785
NSMutableArray *a = nil;
3787
if ([content isKindOfClass: NSArrayClass] == YES)
3789
NSEnumerator *e = [content objectEnumerator];
3792
while ((d = [e nextObject]) != nil)
3797
hdr = [d headerNamed: @"content-type"];
3798
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
3800
hdr = [d headerNamed: @"content-disposition"];
3801
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
3810
a = [NSMutableArray arrayWithCapacity: 4];
3820
* Return the content as an NSData object (unless it is multipart)<br />
3821
* Perform conversion from text to data using the charset specified in
3822
* the content-type header, or infer the charset, and update the header
3823
* accordingly.<br />
3824
* If the content can not be represented as a plain NSData object, this
3825
* method returns nil.
3827
- (NSData*) convertToData
3831
if ([content isKindOfClass: [NSString class]] == YES)
3833
GSMimeHeader *hdr = [self headerNamed: @"content-type"];
3834
NSString *charset = [hdr parameterForKey: @"charset"];
3835
NSStringEncoding enc;
3837
enc = [GSMimeDocument encodingFromCharset: charset];
3838
d = [content dataUsingEncoding: enc];
3841
charset = selectCharacterSet(content, &d);
3842
[hdr setParameter: charset forKey: @"charset"];
3845
else if ([content isKindOfClass: [NSData class]] == YES)
3853
* Return the content as an NSString object (unless it is multipart)
3854
* If the content cannot be represented as text, this returns nil.
3856
- (NSString*) convertToText
3860
if ([content isKindOfClass: [NSString class]] == YES)
3864
else if ([content isKindOfClass: [NSData class]] == YES)
3866
GSMimeHeader *hdr = [self headerNamed: @"content-type"];
3867
NSString *charset = [hdr parameterForKey: @"charset"];
3868
NSStringEncoding enc;
3870
enc = [GSMimeDocument encodingFromCharset: charset];
3871
s = [[NSString alloc] initWithData: content encoding: enc];
3878
* Returns a copy of the receiver.
2288
3880
- (id) copyWithZone: (NSZone*)z
2290
return RETAIN(self);
3882
GSMimeDocument *c = [GSMimeDocument allocWithZone: z];
3884
c->headers = [[NSMutableArray allocWithZone: z] initWithArray: headers
3887
if ([content isKindOfClass: NSArrayClass] == YES)
3889
c->content = [[NSMutableArray allocWithZone: z] initWithArray: content
3894
c->content = [content copyWithZone: z];
2293
3899
- (void) dealloc
4032
* <p>Make a probably unique string suitable for use as the
4033
* boundary parameter in the content of a multipart document.
4035
* <p>This implementation provides base64 encoded data
4036
* consisting of an MD5 digest of some pseudo random stuff,
4037
* plus an incrementing counter. The inclusion of the counter
4038
* guarantees that we won't produce two identical strings in
4039
* the same run of the program.
4042
- (NSString*) makeBoundary
4044
static int count = 0;
4045
unsigned char output[20];
4050
int sequence = ++count;
4052
source = [[[NSProcessInfo processInfo] globallyUniqueString]
4053
dataUsingEncoding: NSUTF8StringEncoding];
4054
digest = [source md5Digest];
4055
memcpy(output, [digest bytes], 16);
4056
output[16] = (sequence >> 24) & 0xff;
4057
output[17] = (sequence >> 16) & 0xff;
4058
output[18] = (sequence >> 8) & 0xff;
4059
output[19] = sequence & 0xff;
4061
md = [[NSMutableData alloc] initWithLength: 40];
4062
[md setLength: encodebase64([md mutableBytes], output, 20)];
4063
result = [[NSString alloc] initWithData: md encoding: NSASCIIStringEncoding];
4065
return AUTORELEASE(result);
4069
* Create new content ID header, set it as the content ID of the document
4070
* and return it.<br />
4071
* This is a convenience method which simply places angle brackets around
4072
* an [NSProcessInfo-globallyUniqueString] to form the header value.
4074
- (GSMimeHeader*) makeContentID
4077
NSString *str = [[NSProcessInfo processInfo] globallyUniqueString];
4079
str = [NSString stringWithFormat: @"<%@>", str];
4080
hdr = [[GSMimeHeader alloc] initWithName: @"content-id"
4083
[self setHeader: hdr];
4089
* Deprecated ... use -setHeader:value:parameters:
4091
- (GSMimeHeader*) makeHeader: (NSString*)name
4092
value: (NSString*)value
4093
parameters: (NSDictionary*)parameters
4097
hdr = [[GSMimeHeader alloc] initWithName: name
4099
parameters: parameters];
4100
[self setHeader: hdr];
4106
* Create new message ID header, set it as the message ID of the document
4107
* and return it.<br />
4108
* This is a convenience method which simply places angle brackets around
4109
* an [NSProcessInfo-globallyUniqueString] to form the header value.
4111
- (GSMimeHeader*) makeMessageID
4114
NSString *str = [[NSProcessInfo processInfo] globallyUniqueString];
4116
str = [NSString stringWithFormat: @"<%@>", str];
4117
hdr = [[GSMimeHeader alloc] initWithName: @"message-id"
4120
[self setHeader: hdr];
4126
* Return an NSData object representing the MIME document as raw data
4127
* ready to be sent via an email system.<br />
4128
* Calls -rawMimeData: with the isOuter flag set to YES.
4130
- (NSMutableData*) rawMimeData
4132
return [self rawMimeData: YES];
4136
* <p>Return an NSData object representing the MIME document as raw data
4137
* ready to be sent via an email system.
4139
* <p>The isOuter flag denotes whether this document is the outermost
4140
* part of a MIME message, or is a part of a multipart message.
4142
* <p>During generation of the document this method will perform some
4143
* consistency checks and try to automatically generate missing header
4144
* information needed to build the mime data (eg. filling in the boundary
4145
* parameter in the content-type header for multipart documents).<br />
4146
* However, you should not depend on automatic behaviors but should
4147
* fill in as much detail as possible before generating data.
4150
- (NSMutableData*) rawMimeData: (BOOL)isOuter
4152
NSMutableArray *partData = nil;
4153
NSMutableData *md = [NSMutableData dataWithCapacity: 1024];
4155
NSEnumerator *enumerator;
4159
NSData *boundary = 0;
4160
BOOL contentIsBinary = NO;
4161
BOOL contentIs7bit = YES;
4164
CREATE_AUTORELEASE_POOL(arp);
4169
* Ensure there is a mime version header.
4171
hdr = [self headerNamed: @"mime-version"];
4174
hdr = [GSMimeHeader alloc];
4175
hdr = [hdr initWithName: @"mime-version"
4178
[self addHeader: hdr];
4185
* Inner documents should not contain the mime version header.
4187
hdr = [self headerNamed: @"mime-version"];
4190
[self deleteHeader: hdr];
4194
if ([content isKindOfClass: NSArrayClass] == YES)
4196
count = [content count];
4197
partData = [NSMutableArray arrayWithCapacity: count];
4198
for (i = 0; i < count; i++)
4200
GSMimeDocument *part = [content objectAtIndex: i];
4202
[partData addObject: [part rawMimeData: NO]];
4205
* If any part of a multipart document is not 7bit then
4206
* the document as a whole must not be 7bit either.
4207
* It is important to check this *after* the part has been
4208
* processed by -rawMimeData:, so we know that the encoding
4209
* set for the part is valid.
4211
if (contentIs7bit == YES)
4215
enc = [part headerNamed: @"content-transfer-encoding"];
4217
if ([v isEqualToString: @"8bit"] == YES
4218
|| [v isEqualToString: @"binary"] == YES)
4221
if ([v isEqualToString: @"binary"] == YES)
4223
contentIsBinary = YES;
4230
type = [self headerNamed: @"content-type"];
4234
* Attempt to infer the content type from the content.
4236
if (partData != nil)
4238
[self setContent: content type: @"multipart/mixed" name: nil];
4240
else if ([content isKindOfClass: [NSString class]] == YES)
4242
[self setContent: content type: @"text/plain" name: nil];
4244
else if ([content isKindOfClass: [NSData class]] == YES)
4246
[self setContent: content
4247
type: @"application/octet-stream"
4252
[NSException raise: NSInternalInconsistencyException
4253
format: @"[%@ -%@] with bad content",
4254
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
4256
type = [self headerNamed: @"content-type"];
4259
if (partData != nil)
4264
enc = [self headerNamed: @"content-transfer-encoding"];
4266
if ([v isEqualToString: @"binary"])
4269
* For binary encoding, we can just accept the setting.
4273
else if ([v isEqualToString: @"8bit"])
4275
if (contentIsBinary == YES)
4277
shouldSet = YES; // Need to promote from 8bit to binary
4284
else if (v == nil || [v isEqualToString: @"7bit"] == YES)
4287
* For 7bit encoding, we can accept the setting if the content
4288
* is all 7bit data, otherwise we must change it to 8bit so
4289
* that the content can be handled properly.
4291
if (contentIs7bit == YES)
4303
* A multipart document can't have any other encoding, so we need
4309
if (shouldSet == YES)
4314
* Force a change to the current transfer encoding setting.
4316
if (contentIs7bit == YES)
4320
else if (contentIsBinary == YES)
4322
encoding = @"binary";
4330
enc = [GSMimeHeader alloc];
4331
enc = [enc initWithName: @"content-transfer-encoding"
4334
[self setHeader: enc];
4339
[enc setValue: encoding];
4343
v = [type parameterForKey: @"boundary"];
4346
v = [self makeBoundary];
4347
[type setParameter: v forKey: @"boundary"];
4349
boundary = [v dataUsingEncoding: NSASCIIStringEncoding];
4351
v = [type objectForKey: @"Subtype"];
4352
if ([v isEqualToString: @"related"] == YES)
4354
GSMimeDocument *start;
4356
v = [type parameterForKey: @"start"];
4359
start = [content objectAtIndex: 0];
4362
* The 'start' parameter is not compulsory ... should we
4363
* force it to be set anyway in case some dumb software
4364
* doesn't default to the first part of the message?
4366
v = [start contentID];
4369
hdr = [start makeContentID];
4372
[type setParameter: v forKey: @"start"];
4377
start = [self contentByID: v];
4379
hdr = [start headerNamed: @"content-type"];
4382
* If there is no 'type' parameter, we can fill it in automatically.
4384
if ([type parameterForKey: @"type"] == nil)
4386
[type setParameter: v forKey: @"type"];
4388
if ([v isEqual: [type parameterForKey: @"type"]] == NO)
4390
[NSException raise: NSInvalidArgumentException
4391
format: @"multipart/related 'type' (%@) does not match "
4392
@"that of the 'start' part (%@)",
4393
[type parameterForKey: @"type"], v];
4401
d = [self convertToData];
4402
enc = [self headerNamed: @"content-transfer-encoding"];
4403
encoding = [enc value];
4404
if (encoding == nil)
4406
if ([[type objectForKey: @"Type"] isEqualToString: @"text"] == YES)
4408
NSString *charset = [type parameterForKey: @"charset"];
4411
&& [charset isEqualToString: @"ascii"] == NO
4412
&& [charset isEqualToString: @"us-ascii"] == NO)
4415
enc = [GSMimeHeader alloc];
4416
enc = [enc initWithName: @"content-transfer-encoding"
4419
[self addHeader: enc];
4425
enc = [GSMimeHeader alloc];
4426
enc = [enc initWithName: @"content-transfer-encoding"
4429
[self addHeader: enc];
4435
|| [encoding isEqualToString: @"7bit"] == YES
4436
|| [encoding isEqualToString: @"8bit"] == YES)
4438
unsigned char *bytes = (unsigned char*)[d bytes];
4439
unsigned length = [d length];
4440
BOOL hadCarriageReturn = NO;
4441
unsigned lineLength = 0;
4444
for (i = 0; i < length; i++)
4446
unsigned char c = bytes[i];
4448
if (hadCarriageReturn == YES)
4452
encoding = @"binary"; // CR not part of CRLF
4455
hadCarriageReturn = NO;
4460
encoding = @"binary"; // LF not part of CRLF
4465
hadCarriageReturn = YES;
4467
else if (++lineLength > 998)
4469
encoding = @"binary"; // Line of more than 998
4475
encoding = @"binary";
4480
encoding = @"8bit"; // Not 7bit data
4484
if (encoding != nil)
4488
enc = [GSMimeHeader alloc];
4489
enc = [enc initWithName: @"content-transfer-encoding"
4492
[self addHeader: enc];
4497
[enc setValue: encoding];
4504
* Add all the headers.
4506
enumerator = [headers objectEnumerator];
4507
while ((hdr = [enumerator nextObject]) != nil)
4509
[md appendData: [hdr rawMimeData]];
4512
if (partData != nil)
4514
count = [content count];
4515
for (i = 0; i < count; i++)
4517
GSMimeDocument *part = [content objectAtIndex: i];
4518
NSMutableData *rawPart = [partData objectAtIndex: i];
4520
if (contentIs7bit == YES)
4524
enc = [part headerNamed: @"content-transport-encoding"];
4526
if (v != nil && ([v isEqualToString: @"8bit"]
4527
|| [v isEqualToString: @"binary"]))
4529
[NSException raise: NSInternalInconsistencyException
4530
format: @"[%@ -%@] bad part encoding for 7bit container",
4531
NSStringFromClass([self class]),
4532
NSStringFromSelector(_cmd)];
4536
* For a multipart document, insert the boundary before each part.
4538
[md appendBytes: "\r\n--" length: 4];
4539
[md appendData: boundary];
4540
[md appendBytes: "\r\n" length: 2];
4541
[md appendData: rawPart];
4543
[md appendBytes: "\r\n--" length: 4];
4544
[md appendData: boundary];
4545
[md appendBytes: "--\r\n" length: 4];
4550
* Separate headers from body.
4552
[md appendBytes: "\r\n" length: 2];
4554
if ([[enc value] isEqualToString: @"base64"] == YES)
4560
d = [GSMimeDocument encodeBase64: d];
4564
while (len - pos > 76)
4566
[md appendBytes: &ptr[pos] length: 76];
4567
[md appendBytes: "\r\n" length: 2];
4570
[md appendBytes: &ptr[pos] length: len-pos];
4572
else if ([[enc value] isEqualToString: @"x-uuencode"] == YES)
4576
name = [[self headerNamed: @"content-type"] parameterForKey: @"name"];
4581
[d uuencodeInto: md name: @"untitled" mode: 0644];
2409
4593
* Sets a new value for the content of the document.
2411
- (BOOL) setContent: (id)newContent
2413
ASSIGN(content, newContent);
4595
- (void) setContent: (id)newContent
4597
if ([newContent isKindOfClass: [NSString class]] == YES)
4599
if (newContent != content)
4601
ASSIGNCOPY(content, newContent);
4604
else if ([newContent isKindOfClass: [NSData class]] == YES)
4606
if (newContent != content)
4608
ASSIGNCOPY(content, newContent);
4611
else if ([newContent isKindOfClass: NSArrayClass] == YES)
4613
if (newContent != content)
4615
unsigned c = [newContent count];
4619
id o = [newContent objectAtIndex: c];
4621
if ([o isKindOfClass: [GSMimeDocument class]] == NO)
4623
[NSException raise: NSInvalidArgumentException
4624
format: @"Content contains non-GSMimeDocument"];
4627
newContent = [newContent mutableCopy];
4628
ASSIGN(content, newContent);
4629
RELEASE(newContent);
4634
[NSException raise: NSInvalidArgumentException
4635
format: @"[%@ -%@] passed bad content",
4636
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
4641
* Convenience method calling -setContent:type:name: to set document
4642
* content and type with a nil value for name ... useful for top-level
4643
* documents rather than parts within a document (parts should really
4646
- (void) setContent: (id)newContent
4647
type: (NSString*)type
4649
[self setContent: newContent type: type name: nil];
4653
* <p>Convenience method to set the content of the document along with
4654
* creating a content-type header for it.
4656
* <p>The type parameter may be a simple common content type (text,
4657
* multipart, or application), in which case the default subtype for
4658
* that type is used. Alternatively it may be full detail of a
4659
* content type header value, which will be parsed into 'type', 'subtype'
4660
* and 'parameters'.<br />
4661
* NB. In this case, if the parsed data contains a 'name' parameter
4662
* and the name argument is non-nil, the argument value will
4663
* override the parsed value.
4665
* <p>You can get the same effect by calling -setContent: to set the document
4666
* content, then creating a [GSMimeHeader] instance, initialising it with
4667
* the content type information you want using
4668
* [GSMimeHeader-initWithName:value:parameters:], and calling the
4669
* -setHeader: method to attach it to the document.
4671
* <p>Using this method imposes a few extra checks and restrictions on the
4672
* combination of content and type/subtype you may use ... so you may want
4673
* to use the more primitive methods in order to bypass these checks if
4674
* you are using unusual type/subtype information or if you need to provide
4675
* additional parameters in the header.
4678
- (void) setContent: (id)newContent
4679
type: (NSString*)type
4680
name: (NSString*)name
4682
CREATE_AUTORELEASE_POOL(arp);
4683
NSString *subtype = nil;
4684
GSMimeHeader *hdr = nil;
4691
if ([type isEqualToString: @"text"] == YES)
4695
else if ([type isEqualToString: @"multipart"] == YES)
4699
else if ([type isEqualToString: @"application"] == YES)
4701
subtype = @"octet-stream";
4705
GSMimeParser *p = AUTORELEASE([GSMimeParser new]);
4706
NSScanner *scanner = [NSScanner scannerWithString: type];
4708
hdr = AUTORELEASE([GSMimeHeader new]);
4709
[hdr setName: @"content-type"];
4710
if ([p scanHeaderBody: scanner into: hdr] == NO)
4712
[NSException raise: NSInvalidArgumentException
4713
format: @"Unable to parse type information"];
4721
val = [NSString stringWithFormat: @"%@/%@", type, subtype];
4722
hdr = [GSMimeHeader alloc];
4723
hdr = [hdr initWithName: @"content-type" value: val parameters: nil];
4724
[hdr setObject: type forKey: @"Type"];
4725
[hdr setObject: subtype forKey: @"Subtype"];
4730
type = [hdr objectForKey: @"Type"];
4731
subtype = [hdr objectForKey: @"Subtype"];
4736
[hdr setParameter: name forKey: @"name"];
4739
if ([type isEqualToString: @"multipart"] == NO
4740
&& [type isEqualToString: @"application"] == NO
4741
&& [content isKindOfClass: NSArrayClass] == YES)
4743
[NSException raise: NSInvalidArgumentException
4744
format: @"[%@ -%@] content doesn't match content-type",
4745
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
4748
[self setContent: newContent];
4749
[self setHeader: hdr];
4754
* <p>Convenience method to set the content type of the document without
4755
* altering any content.
4756
* The supplied newType may be full type information including subtype
4757
* and parameters as found after the colon in a mime Content-Type header.
4760
- (void) setContentType: (NSString *)newType
4762
CREATE_AUTORELEASE_POOL(arp);
4763
GSMimeHeader *hdr = nil;
4764
GSMimeParser *p = AUTORELEASE([GSMimeParser new]);
4765
NSScanner *scanner = [NSScanner scannerWithString: newType];
4767
hdr = AUTORELEASE([GSMimeHeader new]);
4768
[hdr setName: @"content-type"];
4769
if ([p scanHeaderBody: scanner into: hdr] == NO)
4771
[NSException raise: NSInvalidArgumentException
4772
format: @"Unable to parse type information"];
4774
[self setHeader: hdr];