~ubuntu-branches/ubuntu/karmic/gnustep-base/karmic

« back to all changes in this revision

Viewing changes to Source/Additions/GSMime.m

  • Committer: Bazaar Package Importer
  • Author(s): Eric Heintzmann
  • Date: 2005-04-17 00:14:38 UTC
  • mfrom: (1.2.1 upstream) (2.1.2 hoary)
  • Revision ID: james.westby@ubuntu.com-20050417001438-enf0y07c9tku85z1
Tags: 1.10.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
 
5
5
   Written by: Richard frith-Macdonald <rfm@gnu.org>
6
6
   Date: October 2000
7
 
   
 
7
 
8
8
   This file is part of the GNUstep Base Library.
9
9
 
10
10
   This library is free software; you can redistribute it and/or
11
11
   modify it under the terms of the GNU Library General Public
12
12
   License as published by the Free Software Foundation; either
13
13
   version 2 of the License, or (at your option) any later version.
14
 
   
 
14
 
15
15
   This library is distributed in the hope that it will be useful,
16
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
17
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
30
30
        to and from convenient internal formats.
31
31
      </p>
32
32
      <p>
33
 
        Eventually the goal is to center round three classes -
 
33
        The idea is to center round two classes -
34
34
      </p>
35
35
      <deflist>
36
36
        <term>document</term>
37
37
        <desc>
38
 
          A container for the actual data (and headers) of a mime/http document.        </desc>
 
38
          A container for the actual data (and headers) of a mime/http
 
39
          document, this is also used to create raw MIME data for sending.
 
40
        </desc>
39
41
        <term>parser</term>
40
42
        <desc>
41
43
          An object that can be fed data and will parse it into a document.
43
45
          that permits overriding in order to extend the functionality to
44
46
          cope with new document types.
45
47
        </desc>
46
 
        <term>unparser</term>
47
 
        <desc>
48
 
          An object to take a mime/http document and produce a data object
49
 
          suitable for transmission.
50
 
        </desc>
51
48
      </deflist>
52
49
   </chapter>
53
 
   $Date: 2002/03/06 15:50:14 $ $Revision: 1.1 $
 
50
   $Date: 2005/02/22 11:22:44 $ $Revision: 1.105 $
54
51
*/
55
52
 
 
53
#include "config.h"
56
54
#include        <Foundation/Foundation.h>
57
 
#include        <gnustep/base/GSMime.h>
 
55
#include        "GNUstepBase/GSMime.h"
 
56
#include        "GNUstepBase/GSCategories.h"
58
57
#include        <string.h>
59
58
#include        <ctype.h>
60
59
 
61
 
static  NSCharacterSet  *specials = nil;
 
60
static  NSCharacterSet  *whitespace = nil;
 
61
static  NSCharacterSet  *rfc822Specials = nil;
 
62
static  NSCharacterSet  *rfc2045Specials = nil;
 
63
static  Class           NSArrayClass = 0;
62
64
 
63
65
/*
64
66
 *      Name -          decodebase64()
72
74
  dst[2] = ((src[2] & 0x03) << 6) |  (src[3] & 0x3F);
73
75
}
74
76
 
 
77
static char b64[]
 
78
  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
79
 
 
80
static int
 
81
encodebase64(char *dst, const unsigned char *src, int length)
 
82
{
 
83
  int   dIndex = 0;
 
84
  int   sIndex;
 
85
 
 
86
  for (sIndex = 0; sIndex < length; sIndex += 3)
 
87
    {
 
88
      int       c0 = src[sIndex];
 
89
      int       c1 = (sIndex+1 < length) ? src[sIndex+1] : 0;
 
90
      int       c2 = (sIndex+2 < length) ? src[sIndex+2] : 0;
 
91
 
 
92
      dst[dIndex++] = b64[(c0 >> 2) & 077];
 
93
      dst[dIndex++] = b64[((c0 << 4) & 060) | ((c1 >> 4) & 017)];
 
94
      dst[dIndex++] = b64[((c1 << 2) & 074) | ((c2 >> 6) & 03)];
 
95
      dst[dIndex++] = b64[c2 & 077];
 
96
    }
 
97
 
 
98
   /* If len was not a multiple of 3, then we have encoded too
 
99
    * many characters.  Adjust appropriately.
 
100
    */
 
101
   if (sIndex == length + 1)
 
102
     {
 
103
       /* There were only 2 bytes in that last group */
 
104
       dst[dIndex - 1] = '=';
 
105
     }
 
106
   else if (sIndex == length + 2)
 
107
     {
 
108
       /* There was only 1 byte in that last group */
 
109
       dst[dIndex - 1] = '=';
 
110
       dst[dIndex - 2] = '=';
 
111
     }
 
112
  return dIndex;
 
113
}
 
114
 
75
115
typedef enum {
76
116
  WE_QUOTED,
77
117
  WE_BASE64
202
242
    }
203
243
}
204
244
 
205
 
static NSStringEncoding
206
 
parseCharacterSet(NSString *token)
207
 
{
208
 
  if ([token compare: @"us-ascii"] == NSOrderedSame)
209
 
    return NSASCIIStringEncoding;
210
 
  if ([token compare: @"iso-8859-1"] == NSOrderedSame)
211
 
    return NSISOLatin1StringEncoding;
212
 
 
213
 
  return NSASCIIStringEncoding;
214
 
}
215
 
 
216
 
/**
 
245
static NSString *
 
246
selectCharacterSet(NSString *str, NSData **d)
 
247
{
 
248
  if ([str length] == 0)
 
249
    {
 
250
      *d = [NSData data];
 
251
      return @"us-ascii";       // Default character set.
 
252
    }
 
253
  if ((*d = [str dataUsingEncoding: NSASCIIStringEncoding]) != nil)
 
254
    return @"us-ascii"; // Default character set.
 
255
  if ((*d = [str dataUsingEncoding: NSISOLatin1StringEncoding]) != nil)
 
256
    return @"iso-8859-1";
 
257
  if ((*d = [str dataUsingEncoding: NSISOLatin2StringEncoding]) != nil)
 
258
    return @"iso-8859-2";
 
259
  if ((*d = [str dataUsingEncoding: NSISOLatin3StringEncoding]) != nil)
 
260
    return @"iso-8859-3";
 
261
  if ((*d = [str dataUsingEncoding: NSISOLatin4StringEncoding]) != nil)
 
262
    return @"iso-8859-4";
 
263
  if ((*d = [str dataUsingEncoding: NSISOCyrillicStringEncoding]) != nil)
 
264
    return @"iso-8859-5";
 
265
  if ((*d = [str dataUsingEncoding: NSISOArabicStringEncoding]) != nil)
 
266
    return @"iso-8859-6";
 
267
  if ((*d = [str dataUsingEncoding: NSISOGreekStringEncoding]) != nil)
 
268
    return @"iso-8859-7";
 
269
  if ((*d = [str dataUsingEncoding: NSISOHebrewStringEncoding]) != nil)
 
270
    return @"iso-8859-8";
 
271
  if ((*d = [str dataUsingEncoding: NSISOLatin5StringEncoding]) != nil)
 
272
    return @"iso-8859-9";
 
273
  if ((*d = [str dataUsingEncoding: NSISOLatin6StringEncoding]) != nil)
 
274
    return @"iso-8859-10";
 
275
  if ((*d = [str dataUsingEncoding: NSISOLatin7StringEncoding]) != nil)
 
276
    return @"iso-8859-13";
 
277
  if ((*d = [str dataUsingEncoding: NSISOLatin8StringEncoding]) != nil)
 
278
    return @"iso-8859-14";
 
279
  if ((*d = [str dataUsingEncoding: NSISOLatin9StringEncoding]) != nil)
 
280
    return @"iso-8859-15";
 
281
  if ((*d = [str dataUsingEncoding: NSWindowsCP1250StringEncoding]) != nil)
 
282
    return @"windows-1250";
 
283
  if ((*d = [str dataUsingEncoding: NSWindowsCP1251StringEncoding]) != nil)
 
284
    return @"windows-1251";
 
285
  if ((*d = [str dataUsingEncoding: NSWindowsCP1252StringEncoding]) != nil)
 
286
    return @"windows-1252";
 
287
  if ((*d = [str dataUsingEncoding: NSWindowsCP1253StringEncoding]) != nil)
 
288
    return @"windows-1253";
 
289
  if ((*d = [str dataUsingEncoding: NSWindowsCP1254StringEncoding]) != nil)
 
290
    return @"windows-1254";
 
291
 
 
292
  *d = [str dataUsingEncoding: NSUTF8StringEncoding];
 
293
  return @"utf-8";              // Catch-all character set.
 
294
}
 
295
 
 
296
/**
 
297
 * Encode a word in a header according to RFC2047 if necessary.
 
298
 * For an ascii word, we just return the data.
 
299
 */
 
300
static NSData*
 
301
wordData(NSString *word)
 
302
{
 
303
  NSData        *d = nil;
 
304
  NSString      *charset;
 
305
 
 
306
  charset = selectCharacterSet(word, &d);
 
307
  if ([charset isEqualToString: @"us-ascii"] == YES)
 
308
    {
 
309
      return d;
 
310
    }
 
311
  else
 
312
    {
 
313
      int               len = [charset cStringLength];
 
314
      char              buf[len+1];
 
315
      NSMutableData     *md;
 
316
 
 
317
      [charset getCString: buf];
 
318
      md = [NSMutableData dataWithCapacity: [d length]*4/3 + len + 8];
 
319
      d = [GSMimeDocument encodeBase64: d];
 
320
      [md appendBytes: "=?" length: 2];
 
321
      [md appendBytes: buf length: len];
 
322
      [md appendBytes: "?b?" length: 3];
 
323
      [md appendData: d];
 
324
      [md appendBytes: "?=" length: 2];
 
325
      return md;
 
326
    }
 
327
}
 
328
 
 
329
/**
 
330
 * Coding contexts are objects used by the parser to store the state of
 
331
 * decoding incoming data while it is being incrementally parsed.<br />
217
332
 * The most rudimentary context ... this is used for decoding plain
218
 
 * text and binary dat (ie data which is not really decoded at all)
 
333
 * text and binary data (ie data which is not really decoded at all)
219
334
 * and all other decoding work is done by a subclass.
220
335
 */
221
336
@implementation GSMimeCodingContext
236
351
}
237
352
 
238
353
/**
 
354
 * Decode length bytes of data from sData and append the results to dData.<br />
 
355
 * Return YES on succes, NO if there is an error.
 
356
 */
 
357
- (BOOL) decodeData: (const void*)sData
 
358
             length: (unsigned)length
 
359
           intoData: (NSMutableData*)dData
 
360
{
 
361
  unsigned      size = [dData length];
 
362
 
 
363
  [dData setLength: size + length];
 
364
  memcpy([dData mutableBytes] + size, sData, length);
 
365
  return YES;
 
366
}
 
367
 
 
368
/**
239
369
 * Sets the current value of the 'atEnd' flag.
240
370
 */
241
371
- (void) setAtEnd: (BOOL)flag
252
382
}
253
383
@end
254
384
@implementation GSMimeBase64DecoderContext
 
385
- (BOOL) decodeData: (const void*)sData
 
386
             length: (unsigned)length
 
387
           intoData: (NSMutableData*)dData
 
388
{
 
389
  unsigned      size = [dData length];
 
390
  unsigned char *src = (unsigned char*)sData;
 
391
  unsigned char *end = src + length;
 
392
  unsigned char *beg;
 
393
  unsigned char *dst;
 
394
 
 
395
  /*
 
396
   * Expand destination data buffer to have capacity to handle info.
 
397
   */
 
398
  [dData setLength: size + (3 * (end + 8 - src))/4];
 
399
  dst = (unsigned char*)[dData mutableBytes];
 
400
  beg = dst;
 
401
 
 
402
  /*
 
403
   * Now decode data into buffer, keeping count and temporary
 
404
   * data in context.
 
405
   */
 
406
  while (src < end)
 
407
    {
 
408
      int       cc = *src++;
 
409
 
 
410
      if (isupper(cc))
 
411
        {
 
412
          cc -= 'A';
 
413
        }
 
414
      else if (islower(cc))
 
415
        {
 
416
          cc = cc - 'a' + 26;
 
417
        }
 
418
      else if (isdigit(cc))
 
419
        {
 
420
          cc = cc - '0' + 52;
 
421
        }
 
422
      else if (cc == '+')
 
423
        {
 
424
          cc = 62;
 
425
        }
 
426
      else if (cc == '/')
 
427
        {
 
428
          cc = 63;
 
429
        }
 
430
      else if  (cc == '=')
 
431
        {
 
432
          [self setAtEnd: YES];
 
433
          cc = -1;
 
434
        }
 
435
      else if (cc == '-')
 
436
        {
 
437
          [self setAtEnd: YES];
 
438
          break;
 
439
        }
 
440
      else
 
441
        {
 
442
          cc = -1;              /* ignore */
 
443
        }
 
444
 
 
445
      if (cc >= 0)
 
446
        {
 
447
          buf[pos++] = cc;
 
448
          if (pos == 4)
 
449
            {
 
450
              pos = 0;
 
451
              decodebase64(dst, buf);
 
452
              dst += 3;
 
453
            }
 
454
        }
 
455
    }
 
456
 
 
457
  /*
 
458
   * Odd characters at end of decoded data need to be added separately.
 
459
   */
 
460
  if ([self atEnd] == YES && pos > 0)
 
461
    {
 
462
      unsigned  len = pos - 1;;
 
463
 
 
464
      while (pos < 4)
 
465
        {
 
466
          buf[pos++] = '\0';
 
467
        }
 
468
      pos = 0;
 
469
      decodebase64(dst, buf);
 
470
      size += len;
 
471
    }
 
472
  [dData setLength: size + dst - beg];
 
473
  return YES;
 
474
}
255
475
@end
256
476
 
257
477
@interface      GSMimeQuotedDecoderContext : GSMimeCodingContext
262
482
}
263
483
@end
264
484
@implementation GSMimeQuotedDecoderContext
 
485
- (BOOL) decodeData: (const void*)sData
 
486
             length: (unsigned)length
 
487
           intoData: (NSMutableData*)dData
 
488
{
 
489
  unsigned      size = [dData length];
 
490
  unsigned char *src = (unsigned char*)sData;
 
491
  unsigned char *end = src + length;
 
492
  unsigned char *beg;
 
493
  unsigned char *dst;
 
494
 
 
495
  /*
 
496
   * Expand destination data buffer to have capacity to handle info.
 
497
   */
 
498
  [dData setLength: size + (end - src)];
 
499
  dst = (unsigned char*)[dData mutableBytes];
 
500
  beg = dst;
 
501
 
 
502
  while (src < end)
 
503
    {
 
504
      if (pos > 0)
 
505
        {
 
506
          if ((*src == '\n') || (*src == '\r'))
 
507
            {
 
508
              pos = 0;
 
509
            }
 
510
          else
 
511
            {
 
512
              buf[pos++] = *src;
 
513
              if (pos == 3)
 
514
                {
 
515
                  int   c;
 
516
                  int   val;
 
517
 
 
518
                  pos = 0;
 
519
                  c = buf[1];
 
520
                  val = isdigit(c) ? (c - '0') : (c - 55);
 
521
                  val *= 0x10;
 
522
                  c = buf[2];
 
523
                  val += isdigit(c) ? (c - '0') : (c - 55);
 
524
                  *dst++ = val;
 
525
                }
 
526
            }
 
527
        }
 
528
      else if (*src == '=')
 
529
        {
 
530
          buf[pos++] = '=';
 
531
        }
 
532
      else
 
533
        {
 
534
          *dst++ = *src;
 
535
        }
 
536
      src++;
 
537
    }
 
538
  [dData setLength: size + dst - beg];
 
539
  return YES;
 
540
}
265
541
@end
266
542
 
267
543
@interface      GSMimeChunkedDecoderContext : GSMimeCodingContext
299
575
}
300
576
@end
301
577
 
 
578
/**
 
579
 * Inefficient ... copies data into output object and only performs
 
580
 * the actual decoding at the end.
 
581
 */
 
582
@interface      GSMimeUUCodingContext : GSMimeCodingContext
 
583
@end
 
584
 
 
585
@implementation GSMimeUUCodingContext
 
586
- (BOOL) decodeData: (const void*)sData
 
587
             length: (unsigned)length
 
588
           intoData: (NSMutableData*)dData
 
589
{
 
590
  [super decodeData: sData length: length intoData: dData];
 
591
 
 
592
  if ([self atEnd] == YES)
 
593
    {
 
594
      NSMutableData             *dec;
 
595
 
 
596
      dec = [[NSMutableData alloc] initWithCapacity: [dData length]];
 
597
      [dData uudecodeInto: dec name: 0 mode: 0];
 
598
      [dData setData: dec];
 
599
      RELEASE(dec);
 
600
    }
 
601
  return YES;
 
602
}
 
603
@end
302
604
 
303
605
 
304
606
@interface GSMimeParser (Private)
305
607
- (BOOL) _decodeBody: (NSData*)data;
306
608
- (NSString*) _decodeHeader;
307
609
- (BOOL) _unfoldHeader;
 
610
- (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info;
308
611
@end
309
612
 
310
613
/**
315
618
 * </p>
316
619
 * <p>
317
620
 *   You supply the document to be parsed as one or more data
318
 
 *   items passed to the <code>Parse:</code> method, and (if
319
 
 *   the method always returns <code>YES</code>, you give it
320
 
 *   a final <code>nil</code> argument to mark the end of the
 
621
 *   items passed to the -parse: method, and (if
 
622
 *   the method always returns YES, you give it
 
623
 *   a final nil argument to mark the end of the
321
624
 *   document.
322
625
 * </p>
323
626
 * <p>
324
627
 *   On completion of parsing a valid document, the
325
 
 *   <code>document</code> method returns the resulting parsed document.
 
628
 *   [GSMimeParser-mimeDocument] method returns the
 
629
 *   resulting parsed document.
326
630
 * </p>
327
631
 */
328
632
@implementation GSMimeParser
329
633
 
330
634
/**
 
635
 * Convenience method to parse a single data item as a MIME message
 
636
 * and return the resulting document.
 
637
 */
 
638
+ (GSMimeDocument*) documentFromData: (NSData*)mimeData
 
639
{
 
640
  GSMimeDocument        *newDocument = nil;
 
641
  GSMimeParser          *parser = [GSMimeParser new];
 
642
 
 
643
  if ([parser parse: mimeData] == YES)
 
644
    {
 
645
      [parser parse: nil];
 
646
    }
 
647
  if ([parser isComplete] == YES)
 
648
    {
 
649
      newDocument = [parser mimeDocument];
 
650
      RETAIN(newDocument);
 
651
    }
 
652
  RELEASE(parser);
 
653
  return AUTORELEASE(newDocument);
 
654
}
 
655
 
 
656
+ (void) initialize
 
657
{
 
658
  if (NSArrayClass == 0)
 
659
    {
 
660
      NSArrayClass = [NSArray class];
 
661
    }
 
662
}
 
663
 
 
664
/**
331
665
 * Create and return a parser.
332
666
 */
333
667
+ (GSMimeParser*) mimeParser
350
684
 *   <item>7bit (no coding actually performed)</item>
351
685
 *   <item>8bit (no coding actually performed)</item>
352
686
 *   <item>chunked (for HTTP/1.1)</item>
 
687
 *   <item>x-uuencode</item>
353
688
 * </list>
 
689
 * To add new coding schemes to the parser, you need to ovrride
 
690
 * this method to return a new coding context for your scheme
 
691
 * when the info argument indicates that this is appropriate.
354
692
 */
355
 
- (GSMimeCodingContext*) contextFor: (NSDictionary*)info
 
693
- (GSMimeCodingContext*) contextFor: (GSMimeHeader*)info
356
694
{
357
695
  NSString      *name;
358
696
  NSString      *value;
362
700
      return AUTORELEASE([GSMimeCodingContext new]);
363
701
    }
364
702
 
365
 
  name = [info objectForKey: @"Name"];
 
703
  name = [info name];
366
704
  if ([name isEqualToString: @"content-transfer-encoding"] == YES
367
705
   || [name isEqualToString: @"transfer-encoding"] == YES)
368
706
    {
369
 
      value = [info objectForKey: @"Value"];
 
707
      value = [[info value] lowercaseString];
370
708
      if ([value length] == 0)
371
709
        {
372
710
          NSLog(@"Bad value for %@ header - assume binary encoding", name);
396
734
        {
397
735
          return AUTORELEASE([GSMimeChunkedDecoderContext new]);
398
736
        }
 
737
      else if ([value isEqualToString: @"x-uuencode"] == YES)
 
738
        {
 
739
          return AUTORELEASE([GSMimeUUCodingContext new]);
 
740
        }
399
741
    }
400
742
 
401
743
  NSLog(@"contextFor: - unknown header (%@) ... assumed binary encoding", name);
439
781
 *   object.
440
782
 * </p>
441
783
 * <p>
442
 
 *   You may override this method in order to implement
443
 
 *   additional coding schemes.
 
784
 *   You may override this method in order to implement additional
 
785
 *   coding schemes, but usually it should be enough for you to
 
786
 *   implement a custom GSMimeCodingContext subclass fotr this method
 
787
 *   to use.
444
788
 * </p>
445
789
 */
446
790
- (BOOL) decodeData: (NSData*)sData
448
792
           intoData: (NSMutableData*)dData
449
793
        withContext: (GSMimeCodingContext*)con
450
794
{
451
 
  unsigned              size = [dData length];
452
795
  unsigned              len = [sData length];
453
 
  unsigned char         *beg;
454
 
  unsigned char         *dst;
455
 
  const char            *src;
456
 
  const char            *end;
457
 
  Class                 ccls;
 
796
  BOOL                  result = YES;
458
797
 
459
798
  if (dData == nil || [con isKindOfClass: [GSMimeCodingContext class]] == NO)
460
799
    {
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)];
463
803
    }
464
804
  GS_RANGE_CHECK(aRange, len);
465
805
 
466
806
  /*
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.
468
811
   */
469
 
  src = (const char *)[sData bytes];
470
 
  src += aRange.location;
471
 
  end = src + aRange.length;
472
 
  
473
 
  ccls = [con class];
474
 
  if (ccls == [GSMimeBase64DecoderContext class])
475
 
    {
476
 
      GSMimeBase64DecoderContext        *ctxt;
477
 
 
478
 
      ctxt = (GSMimeBase64DecoderContext*)con;
479
 
 
480
 
      /*
481
 
       * Expand destination data buffer to have capacity to handle info.
482
 
       */
483
 
      [dData setLength: size + (3 * (end + 8 - src))/4];
484
 
      dst = (unsigned char*)[dData mutableBytes];
485
 
      beg = dst;
486
 
 
487
 
      /*
488
 
       * Now decode data into buffer, keeping count and temporary
489
 
       * data in context.
490
 
       */
491
 
      while (src < end)
492
 
        {
493
 
          int   cc = *src++;
494
 
 
495
 
          if (isupper(cc))
496
 
            {
497
 
              cc -= 'A';
498
 
            }
499
 
          else if (islower(cc))
500
 
            {
501
 
              cc = cc - 'a' + 26;
502
 
            }
503
 
          else if (isdigit(cc))
504
 
            {
505
 
              cc = cc - '0' + 52;
506
 
            }
507
 
          else if (cc == '+')
508
 
            {
509
 
              cc = 62;
510
 
            }
511
 
          else if (cc == '/')
512
 
            {
513
 
              cc = 63;
514
 
            }
515
 
          else if  (cc == '=')
516
 
            {
517
 
              [ctxt setAtEnd: YES];
518
 
              cc = -1;
519
 
            }
520
 
          else if (cc == '-')
521
 
            {
522
 
              [ctxt setAtEnd: YES];
523
 
              break;
524
 
            }
525
 
          else
526
 
            {
527
 
              cc = -1;          /* ignore */
528
 
            }
529
 
 
530
 
          if (cc >= 0)
531
 
            {
532
 
              ctxt->buf[ctxt->pos++] = cc;
533
 
              if (ctxt->pos == 4)
534
 
                {
535
 
                  ctxt->pos = 0;
536
 
                  decodebase64(dst, ctxt->buf);
537
 
                  dst += 3;
538
 
                }
539
 
            }
540
 
        }
541
 
 
542
 
      /*
543
 
       * Odd characters at end of decoded data need to be added separately.
544
 
       */
545
 
      if ([ctxt atEnd] == YES && ctxt->pos > 0)
546
 
        {
547
 
          unsigned      len = ctxt->pos - 1;;
548
 
 
549
 
          while (ctxt->pos < 4)
550
 
            {
551
 
              ctxt->buf[ctxt->pos++] = '\0';
552
 
            }
553
 
          ctxt->pos = 0;
554
 
          decodebase64(dst, ctxt->buf);
555
 
          size += len;
556
 
        }
557
 
      [dData setLength: size + dst - beg];
558
 
    }
559
 
  else if (ccls == [GSMimeQuotedDecoderContext class])
560
 
    {
561
 
      GSMimeQuotedDecoderContext        *ctxt;
562
 
 
563
 
      ctxt = (GSMimeQuotedDecoderContext*)con;
564
 
 
565
 
      /*
566
 
       * Expand destination data buffer to have capacity to handle info.
567
 
       */
568
 
      [dData setLength: size + (end - src)];
569
 
      dst = (unsigned char*)[dData mutableBytes];
570
 
      beg = dst;
571
 
 
572
 
      while (src < end)
573
 
        {
574
 
          if (ctxt->pos > 0)
575
 
            {
576
 
              if ((*src == '\n') || (*src == '\r'))
577
 
                {
578
 
                  ctxt->pos = 0;
579
 
                }
580
 
              else
581
 
                {
582
 
                  ctxt->buf[ctxt->pos++] = *src;
583
 
                  if (ctxt->pos == 3)
584
 
                    {
585
 
                      int       c;
586
 
                      int       val;
587
 
 
588
 
                      ctxt->pos = 0;
589
 
                      c = ctxt->buf[1];
590
 
                      val = isdigit(c) ? (c - '0') : (c - 55);
591
 
                      val *= 0x10;
592
 
                      c = ctxt->buf[2];
593
 
                      val += isdigit(c) ? (c - '0') : (c - 55);
594
 
                      *dst++ = val;
595
 
                    }
596
 
                }
597
 
            }
598
 
          else if (*src == '=')
599
 
            {
600
 
              ctxt->buf[ctxt->pos++] = '=';
601
 
            }
602
 
          else
603
 
            {
604
 
              *dst++ = *src;
605
 
            }
606
 
          src++;
607
 
        }
608
 
      [dData setLength: size + dst - beg];
609
 
    }
610
 
  else if (ccls == [GSMimeChunkedDecoderContext class])
 
812
  if ([con class] == [GSMimeChunkedDecoderContext class])
611
813
    {
612
814
      GSMimeChunkedDecoderContext       *ctxt;
613
 
      const char                        *footers = src;
 
815
      unsigned                  size = [dData length];
 
816
      unsigned char             *beg;
 
817
      unsigned char             *dst;
 
818
      const char                *src;
 
819
      const char                *end;
 
820
      const char                *footers;
614
821
 
615
822
      ctxt = (GSMimeChunkedDecoderContext*)con;
616
823
 
 
824
      /*
 
825
       * Get pointers into source data buffer.
 
826
       */
 
827
      src = (const char *)[sData bytes];
 
828
      footers = src;
 
829
      src += aRange.location;
 
830
      end = src + aRange.length;
617
831
      beg = 0;
618
832
      /*
619
833
       * Make sure buffer is big enough, and set up output pointers.
647
861
                src++;
648
862
                if (ctxt->state != ChunkSize)
649
863
                  {
650
 
                    int val = 0;
651
 
                    int index;
 
864
                    unsigned int        val = 0;
 
865
                    unsigned int        index;
652
866
 
653
867
                    for (index = 0; index < ctxt->pos; index++)
654
868
                      {
704
918
            case ChunkData:
705
919
              /*
706
920
               * If the pos is non-zero, we have a data chunk to read.
707
 
               * otherwise, what we actually want it to read footers.
 
921
               * otherwise, what we actually want is to read footers.
708
922
               */
709
923
              if (ctxt->pos > 0)
710
924
                {
767
981
              data = ctxt->data;
768
982
              bytes = (unsigned char*)[data mutableBytes];
769
983
              dataEnd = [data length];
770
 
              inBody = NO;
 
984
              flags.inBody = 0;
771
985
 
772
986
              /*
773
987
               * Duplicate the normal header parsing process for our footers.
774
988
               */
775
 
              while (inBody == NO)
 
989
              while (flags.inBody == 0)
776
990
                {
777
991
                  if ([self _unfoldHeader] == NO)
778
992
                    {
779
993
                      break;
780
994
                    }
781
 
                  if (inBody == NO)
 
995
                  if (flags.inBody == 0)
782
996
                    {
783
997
                      NSString          *header;
784
998
 
789
1003
                        }
790
1004
                      if ([self parseHeader: header] == NO)
791
1005
                        {
 
1006
                          flags.hadErrors = 1;
792
1007
                          break;
793
1008
                        }
794
1009
                    }
801
1016
              data = old;
802
1017
              bytes = (unsigned char*)[data mutableBytes];
803
1018
              dataEnd = [data length];
804
 
              inBody = YES;
 
1019
              flags.inBody = 1;
805
1020
            }
806
1021
        }
807
1022
      /*
811
1026
    }
812
1027
  else
813
1028
    {
814
 
      /*
815
 
       * Assume binary (no) decoding required.
816
 
       */
817
 
      [dData setLength: size + (end - src)];
818
 
      dst = (unsigned char*)[dData mutableBytes];
819
 
      memcpy(&dst[size], src, (end - src));
 
1029
      result = [con decodeData: [sData bytes] + aRange.location
 
1030
                        length: aRange.length
 
1031
                      intoData: dData];
820
1032
    }
821
1033
 
822
1034
  /*
827
1039
      [con setAtEnd: YES];
828
1040
    }
829
1041
 
830
 
  return YES;
 
1042
  return result;
831
1043
}
832
1044
 
833
1045
- (NSString*) description
840
1052
}
841
1053
 
842
1054
/**
 
1055
 * <deprecated />
843
1056
 * Returns the object into which raw mime data is being parsed.
844
1057
 */
845
 
- (GSMimeDocument*) document
 
1058
- (id) document
846
1059
{
847
1060
  return document;
848
1061
}
849
1062
 
850
1063
/**
851
 
 * Returns YES if the document parsing is known to be completed.
 
1064
 * This method may be called to tell the parser that it should not expect
 
1065
 * to parse any headers, and that the data it will receive is body data.<br />
 
1066
 * If the parse is already in the body, or is complete, this method has
 
1067
 * no effect.<br />
 
1068
 * This is for use when some other utility has been used to parse headers,
 
1069
 * and you have set the headers of the document owned by the parser
 
1070
 * accordingly.  You can then use the GSMimeParser to read the body data
 
1071
 * into the document.
 
1072
 */
 
1073
- (void) expectNoHeaders
 
1074
{
 
1075
  if (flags.complete == 0)
 
1076
    {
 
1077
      flags.inBody = 1;
 
1078
    }
 
1079
}
 
1080
 
 
1081
/**
 
1082
 * Returns YES if the document parsing is known to be completed successfully.
 
1083
 * Returns NO if either more data is needed, or if the parser encountered an
 
1084
 * error.
852
1085
 */
853
1086
- (BOOL) isComplete
854
1087
{
855
 
  return complete;
 
1088
  if (flags.hadErrors == 1)
 
1089
    {
 
1090
      return NO;
 
1091
    }
 
1092
  return (flags.complete == 1) ? YES : NO;
 
1093
}
 
1094
 
 
1095
/**
 
1096
 * Returns YES if the parser is parsing an HTTP document rather than
 
1097
 * a true MIME document.
 
1098
 */
 
1099
- (BOOL) isHttp
 
1100
{
 
1101
  return (flags.isHttp == 1) ? YES : NO;
856
1102
}
857
1103
 
858
1104
/**
861
1107
 */
862
1108
- (BOOL) isInBody
863
1109
{
864
 
  return inBody;
 
1110
  return (flags.inBody == 1) ? YES : NO;
865
1111
}
866
1112
 
867
1113
/**
870
1116
 */
871
1117
- (BOOL) isInHeaders
872
1118
{
873
 
  if (inBody == YES)
 
1119
  if (flags.inBody == 1)
874
1120
    return NO;
875
 
  if (complete == YES)
 
1121
  if (flags.complete == 1)
876
1122
    return NO;
877
1123
  return YES;
878
1124
}
884
1130
    {
885
1131
      data = [[NSMutableData alloc] init];
886
1132
      document = [[GSMimeDocument alloc] init];
 
1133
      _defaultEncoding = NSASCIIStringEncoding;
887
1134
    }
888
1135
  return self;
889
1136
}
890
1137
 
891
1138
/**
 
1139
 * Returns the GSMimeDocument instance into which data is being parsed
 
1140
 * or has been parsed.
 
1141
 */
 
1142
- (GSMimeDocument*) mimeDocument
 
1143
{
 
1144
  return document;
 
1145
}
 
1146
 
 
1147
/**
892
1148
 * <p>
893
1149
 *   This method is called repeatedly to pass raw mime data into
894
1150
 *   the parser.  It returns <code>YES</code> as long as it wants
903
1159
 *   passed all the data to it ... this tells it that the data
904
1160
 *   is complete.
905
1161
 * </p>
 
1162
 * <p>
 
1163
 *   The parser attempts to be as flexible as possible and to continue
 
1164
 *   parsing wherever it can.  If an error occurs in parsing, the
 
1165
 *   -isComplete method will always return NO, even after the -parse:
 
1166
 *   method has been called with a nil argument.
 
1167
 * </p>
 
1168
 * <p>
 
1169
 *   A multipart document will be parsed to content consisting of an
 
1170
 *   NSArray of GSMimeDocument instances representing each part.<br />
 
1171
 *   Otherwise, a document will become content of type NSData, unless
 
1172
 *   it is of content type <em>text</em>, in which case it will be an
 
1173
 *   NSString.<br />
 
1174
 *   If a document has no content type specified, it will be treated as
 
1175
 *   <em>text</em>, unless it is identifiable as a <em>file</em>
 
1176
 *   (eg. t has a content-disposition header containing a filename parameter).
 
1177
 * </p>
906
1178
 */
907
1179
- (BOOL) parse: (NSData*)d
908
1180
{
909
1181
  unsigned      l = [d length];
910
1182
 
911
 
  if (complete == YES)
 
1183
  if (flags.complete == 1)
912
1184
    {
913
1185
      return NO;        /* Already completely parsed! */
914
1186
    }
915
1187
  if (l > 0)
916
1188
    {
917
1189
      NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'", l, l, l, [d bytes]);
918
 
      if (inBody == NO)
 
1190
      if (flags.inBody == 0)
919
1191
        {
920
1192
          [data appendBytes: [d bytes] length: [d length]];
921
1193
          bytes = (unsigned char*)[data mutableBytes];
922
1194
          dataEnd = [data length];
923
1195
 
924
 
          while (inBody == NO)
 
1196
          while (flags.inBody == 0)
925
1197
            {
926
1198
              if ([self _unfoldHeader] == NO)
927
1199
                {
928
1200
                  return YES;   /* Needs more data to fill line.        */
929
1201
                }
930
 
              if (inBody == NO)
 
1202
              if (flags.inBody == 0)
931
1203
                {
932
1204
                  NSString              *header;
933
1205
 
938
1210
                    }
939
1211
                  if ([self parseHeader: header] == NO)
940
1212
                    {
 
1213
                      flags.hadErrors = 1;
941
1214
                      return NO;        /* Header not parsed properly.  */
942
1215
                    }
943
 
                  NSDebugMLLog(@"GSMime", @"Parsed header '%@'", header);
944
1216
                }
945
1217
              else
946
1218
                {
947
 
                  NSDebugMLLog(@"GSMime", @"Parsed end of headers");
 
1219
                  NSDebugMLLog(@"GSMime", @"Parsed end of headers", "");
948
1220
                }
949
1221
            }
950
1222
          /*
960
1232
           * continuation header(s), in which case, we must start parsing
961
1233
           * headers again.
962
1234
           */
963
 
          if (inBody == YES)
 
1235
          if (flags.inBody == 1)
964
1236
            {
965
1237
              NSDictionary      *info;
966
1238
 
980
1252
                           * This is an intermediary response ... so we have
981
1253
                           * to restart the parsing operation!
982
1254
                           */
983
 
                          NSDebugMLLog(@"GSMime", @"Parsed http continuation");
984
 
                          inBody = NO;
 
1255
                          NSDebugMLLog(@"GSMime",
 
1256
                            @"Parsed http continuation", "");
 
1257
                          flags.inBody = 0;
985
1258
                        }
986
1259
                    }
987
1260
                }
990
1263
 
991
1264
      if ([d length] > 0)
992
1265
        {
993
 
          if (inBody == YES)
 
1266
          if (flags.inBody == 1)
994
1267
            {
995
1268
              /*
996
1269
               * We can't just re-call -parse: ...
1010
1283
    {
1011
1284
      BOOL      result;
1012
1285
 
1013
 
      if (inBody == YES)
 
1286
      if (flags.wantEndOfLine == 1)
 
1287
        {
 
1288
          result = [self parse: [NSData dataWithBytes: "\r\n" length: 2]];
 
1289
        }
 
1290
      else if (flags.inBody == 1)
1014
1291
        {
1015
1292
          result = [self _decodeBody: d];
1016
1293
        }
1020
1297
           * If still parsing headers, add CR-LF sequences to terminate
1021
1298
           * the headers.
1022
1299
           */
1023
 
          result = [self parse: [NSData dataWithBytes: @"\r\n\r\n" length: 4]];
 
1300
          result = [self parse: [NSData dataWithBytes: "\r\n\r\n" length: 4]];
1024
1301
        }
1025
 
      inBody = NO;
1026
 
      complete = YES;   /* Finished parsing     */
 
1302
      flags.wantEndOfLine = 0;
 
1303
      flags.inBody = 0;
 
1304
      flags.complete = 1;       /* Finished parsing     */
1027
1305
      return result;
1028
1306
    }
1029
1307
}
1031
1309
/**
1032
1310
 * <p>
1033
1311
 *   This method is called to parse a header line <em>for the
1034
 
 *   current document</em>, split its contents into an info
1035
 
 *   dictionary, and add that information to the document.
 
1312
 *   current document</em>, split its contents into a GSMimeHeader
 
1313
 *   object, and add that information to the document.<br />
 
1314
 *   The method is normally used internally by the -parse: method,
 
1315
 *   but you may also call it to parse an entire header line and
 
1316
 *   add it to the document (this may be useful in conjunction
 
1317
 *   with the -expectNoHeaders method, to parse a document body data
 
1318
 *   into a document where the headers are available from a
 
1319
 *   separate source).
1036
1320
 * </p>
 
1321
 * <example>
 
1322
 *   GSMimeParser *parser = [GSMimeParser mimeParser];
 
1323
 *
 
1324
 *   [parser parseHeader: @"content-type: text/plain"];
 
1325
 *   [parser expectNoHeaders];
 
1326
 *   [parser parse: bodyData];
 
1327
 *   [parser parse: nil];
 
1328
 * </example>
1037
1329
 * <p>
1038
 
 *   The standard implementation of this method scans basic
1039
 
 *   information and then calls -scanHeader:named:into:
1040
 
 *   to complete the parsing of the header.
 
1330
 *   The standard implementation of this method scans the header
 
1331
 *   name and then calls -scanHeaderBody:into: to complete the
 
1332
 *   parsing of the header.
1041
1333
 * </p>
1042
1334
 * <p>
1043
1335
 *   This method also performs consistency checks on headers scanned
1044
1336
 *   so it is recommended that it is not overridden, but that
1045
 
 *   subclasses override -scanHeader:named:into: to
 
1337
 *   subclasses override -scanHeaderBody:into: to
1046
1338
 *   implement custom scanning.
1047
1339
 * </p>
1048
1340
 * <p>
1049
1341
 *   As a special case, for HTTP support, this method also parses
1050
1342
 *   lines in the format of HTTP responses as if they were headers
1051
 
 *   named <code>http</code>.  The resulting header info dictionary
1052
 
 *   contains -
 
1343
 *   named <code>http</code>.  The resulting header object contains
 
1344
 *   additional object values -
1053
1345
 * </p>
1054
1346
 * <deflist>
1055
1347
 *   <term>HttpMajorVersion</term>
1069
1361
  NSScanner             *scanner = [NSScanner scannerWithString: aHeader];
1070
1362
  NSString              *name;
1071
1363
  NSString              *value;
1072
 
  NSMutableDictionary   *info;
1073
 
  NSCharacterSet        *skip;
1074
 
  unsigned              count;
1075
 
 
1076
 
  info = [NSMutableDictionary dictionary];
1077
 
 
1078
 
  /*
1079
 
   * Store the raw header string in the info dictionary.
1080
 
   */
1081
 
  [info setObject: [scanner string] forKey: @"RawHeader"];
 
1364
  GSMimeHeader          *info;
 
1365
 
 
1366
  NSDebugMLLog(@"GSMime", @"Parse header - '%@'", aHeader);
 
1367
  info = AUTORELEASE([GSMimeHeader new]);
1082
1368
 
1083
1369
  /*
1084
1370
   * Special case - permit web response status line to act like a header.
1102
1388
    }
1103
1389
 
1104
1390
  /*
1105
 
   * Store the Raw header name and a lowercase version too.
1106
 
   */
1107
 
  name = [name stringByTrimmingTailSpaces];
1108
 
  [info setObject: name forKey: @"BaseName"];
1109
 
  name = [name lowercaseString];
1110
 
  [info setObject: name forKey: @"Name"];
1111
 
 
1112
 
  skip = RETAIN([scanner charactersToBeSkipped]);
1113
 
  [scanner setCharactersToBeSkipped: nil];
1114
 
  [scanner scanCharactersFromSet: skip intoString: 0];
1115
 
  [scanner setCharactersToBeSkipped: skip];
1116
 
  RELEASE(skip);
1117
 
 
1118
 
  /*
1119
 
   * Set remainder of header as a base value.
1120
 
   */
1121
 
  [info setObject: [[scanner string] substringFromIndex: [scanner scanLocation]]
1122
 
           forKey: @"BaseValue"];
 
1391
   * Set the header name.
 
1392
   */
 
1393
  [info setName: name];
 
1394
  name = [info name];
1123
1395
 
1124
1396
  /*
1125
1397
   * Break header fields out into info dictionary.
1126
1398
   */
1127
 
  if ([self scanHeader: scanner named: name into: info] == NO)
 
1399
  if ([self scanHeaderBody: scanner into: info] == NO)
1128
1400
    {
1129
1401
      return NO;
1130
1402
    }
1137
1409
      int       majv = 0;
1138
1410
      int       minv = 0;
1139
1411
 
1140
 
      value = [info objectForKey: @"BaseValue"];
 
1412
      value = [info value];
1141
1413
      if ([value length] == 0)
1142
1414
        {
1143
1415
          NSLog(@"Missing value for mime-version header");
1145
1417
        }
1146
1418
      if (sscanf([value lossyCString], "%d.%d", &majv, &minv) != 2)
1147
1419
        {
1148
 
          NSLog(@"Bad value for mime-version header");
 
1420
          NSLog(@"Bad value for mime-version header (%@)", value);
1149
1421
          return NO;
1150
1422
        }
1151
1423
      [document deleteHeaderNamed: name];       // Should be unique
1152
1424
    }
1153
1425
  else if ([name isEqualToString: @"content-type"] == YES)
1154
1426
    {
 
1427
      NSString  *tmp = [info parameterForKey: @"boundary"];
1155
1428
      NSString  *type;
1156
1429
      NSString  *subtype;
1157
1430
      BOOL      supported = NO;
1158
1431
 
1159
1432
      DESTROY(boundary);
 
1433
      if (tmp != nil)
 
1434
        {
 
1435
          unsigned int  l = [tmp cStringLength] + 2;
 
1436
          unsigned char *b = NSZoneMalloc(NSDefaultMallocZone(), l + 1);
 
1437
 
 
1438
          b[0] = '-';
 
1439
          b[1] = '-';
 
1440
          [tmp getCString: &b[2]];
 
1441
          boundary = [[NSData alloc] initWithBytesNoCopy: b length: l];
 
1442
        }
 
1443
 
1160
1444
      type = [info objectForKey: @"Type"];
1161
1445
      if ([type length] == 0)
1162
1446
        {
1163
1447
          NSLog(@"Missing Mime content-type");
1164
1448
          return NO;
1165
1449
        }
1166
 
      subtype = [info objectForKey: @"SubType"];
 
1450
      subtype = [info objectForKey: @"Subtype"];
1167
1451
        
1168
1452
      if ([type isEqualToString: @"text"] == YES)
1169
1453
        {
1170
1454
          if (subtype == nil)
1171
 
            subtype = @"plain";
1172
 
        }
1173
 
      else if ([type isEqualToString: @"application"] == YES)
1174
 
        {
1175
 
          if (subtype == nil)
1176
 
            subtype = @"octet-stream";
 
1455
            {
 
1456
              subtype = @"plain";
 
1457
            }
1177
1458
        }
1178
1459
      else if ([type isEqualToString: @"multipart"] == YES)
1179
1460
        {
1180
 
          NSDictionary  *par = [info objectForKey: @"Parameters"];
1181
 
          NSString      *tmp = [par objectForKey: @"boundary"];
1182
 
 
 
1461
          if (subtype == nil)
 
1462
            {
 
1463
              subtype = @"mixed";
 
1464
            }
1183
1465
          supported = YES;
1184
 
          if (tmp != nil)
1185
 
            {
1186
 
              unsigned int      l = [tmp cStringLength] + 2;
1187
 
              unsigned char     *b = NSZoneMalloc(NSDefaultMallocZone(), l + 1);
1188
 
 
1189
 
              b[0] = '-';
1190
 
              b[1] = '-';
1191
 
              [tmp getCString: &b[2]];
1192
 
              ASSIGN(boundary, [NSData dataWithBytesNoCopy: b length: l]);
1193
 
            }
1194
 
          else
 
1466
          if (boundary == nil)
1195
1467
            {
1196
1468
              NSLog(@"multipart message without boundary");
1197
1469
              return NO;
1198
1470
            }
1199
1471
        }
 
1472
      else
 
1473
        {
 
1474
          if (subtype == nil)
 
1475
            {
 
1476
              subtype = @"octet-stream";
 
1477
            }
 
1478
        }
1200
1479
 
1201
1480
      [document deleteHeaderNamed: name];       // Should be unique
1202
1481
    }
1203
1482
 
1204
 
  /*
1205
 
   * Ensure that info dictionary is immutable by making a copy
1206
 
   * of all keys and objects and placing them in a new dictionary.
1207
 
   */
1208
 
  count = [info count];
1209
 
  {
1210
 
    id          keys[count];
1211
 
    id          objects[count];
1212
 
    unsigned    index;
1213
 
 
1214
 
    [[info allKeys] getObjects: keys];
1215
 
    for (index = 0; index < count; index++)
1216
 
      {
1217
 
        keys[index] = [keys[index] copy];
1218
 
        objects[index] = [[info objectForKey: keys[index]] copy];
1219
 
      }
1220
 
    info = [NSDictionary dictionaryWithObjects: objects
1221
 
                                       forKeys: keys
1222
 
                                         count: count];
1223
 
    for (index = 0; index < count; index++)
1224
 
      {
1225
 
        RELEASE(objects[index]);
1226
 
        RELEASE(keys[index]);
1227
 
      }
1228
 
  }
1229
 
 
1230
 
  return [document addHeader: info];
1231
 
}
1232
 
 
1233
 
/**
1234
 
 * Returns YES if the parser is expecting to read mime headers,
1235
 
 * Returns NO is the parser has already been passed all the
1236
 
 * data containing headers, and is now waiting for the body of
1237
 
 * the mime message (or has been passed all data).
1238
 
 */
1239
 
- (BOOL) parsedHeaders
1240
 
{
1241
 
  return inBody;
 
1483
  NS_DURING
 
1484
    [document addHeader: info];
 
1485
  NS_HANDLER
 
1486
    return NO;
 
1487
  NS_ENDHANDLER
 
1488
NSDebugMLLog(@"GSMime", @"Header parsed - %@", info);
 
1489
 
 
1490
  return YES;
1242
1491
}
1243
1492
 
1244
1493
/**
1270
1519
 *   <term>content-disposition</term>
1271
1520
 *   <desc>
1272
1521
 *     <deflist>
1273
 
 *     <term>Parameters</term>
1274
 
 *     <desc>
1275
 
 *       A dictionary containing parameters as key-value pairs
1276
 
 *       in lowercase
1277
 
 *     </desc>
1278
1522
 *     <term>Value</term>
1279
1523
 *     <desc>
1280
1524
 *       The content disposition (excluding parameters) as a
1285
1529
 *   <term>content-type</term>
1286
1530
 *   <desc>
1287
1531
 *     <deflist>
1288
 
 *       <term>Parameters</term>
1289
 
 *       <desc>
1290
 
 *         A dictionary containing parameters as key-value pairs
1291
 
 *         in lowercase.
1292
 
 *       </desc>
1293
 
 *       <term>SubType</term>
 
1532
 *       <term>Subtype</term>
1294
1533
 *       <desc>The MIME subtype lowercase</desc>
1295
1534
 *       <term>Type</term>
1296
1535
 *       <desc>The MIME type lowercase</desc>
1329
1568
 *   </desc>
1330
1569
 * </deflist>
1331
1570
 */
1332
 
- (BOOL) scanHeader: (NSScanner*)scanner
1333
 
              named: (NSString*)name
1334
 
               into: (NSMutableDictionary*)info
 
1571
- (BOOL) scanHeaderBody: (NSScanner*)scanner
 
1572
                   into: (GSMimeHeader*)info
1335
1573
{
 
1574
  NSString              *name = [info name];
1336
1575
  NSString              *value = nil;
1337
 
  NSMutableDictionary   *parameters = nil;
 
1576
 
 
1577
  [self scanPastSpace: scanner];
1338
1578
 
1339
1579
  /*
1340
1580
   *    Now see if we are interested in any of it.
1341
1581
   */
1342
1582
  if ([name isEqualToString: @"http"] == YES)
1343
1583
    {
 
1584
      int       loc = [scanner scanLocation];
1344
1585
      int       major;
1345
1586
      int       minor;
1346
1587
      int       status;
1379
1620
      value = [[scanner string] substringFromIndex: [scanner scanLocation]];
1380
1621
      [info setObject: value
1381
1622
               forKey: NSHTTPPropertyStatusReasonKey];
1382
 
      value = nil;
 
1623
      value = [[scanner string] substringFromIndex: loc];
1383
1624
      /*
1384
1625
       * Get rid of preceeding headers in case this is a continuation.
1385
1626
       */
1386
1627
      hdrs = [document allHeaders];
1387
1628
      for (count = 0; count < [hdrs count]; count++)
1388
1629
        {
1389
 
          NSDictionary  *h = [hdrs objectAtIndex: count];
 
1630
          GSMimeHeader  *h = [hdrs objectAtIndex: count];
1390
1631
 
1391
 
          [document deleteHeader: [h objectForKey: @"RawHeader"]];
 
1632
          [document deleteHeader: h];
1392
1633
        }
 
1634
      /*
 
1635
       * Mark to say we are parsing HTTP content
 
1636
       */
 
1637
      [self setIsHttp];
1393
1638
    }
1394
1639
  else if ([name isEqualToString: @"content-transfer-encoding"] == YES
1395
1640
    || [name isEqualToString: @"transfer-encoding"] == YES)
1407
1652
      NSString  *type;
1408
1653
      NSString  *subtype = nil;
1409
1654
 
1410
 
      type = [self scanToken: scanner];
 
1655
      type = [self scanName: scanner];
1411
1656
      if ([type length] == 0)
1412
1657
        {
1413
1658
          NSLog(@"Invalid Mime content-type");
1417
1662
      [info setObject: type forKey: @"Type"];
1418
1663
      if ([scanner scanString: @"/" intoString: 0] == YES)
1419
1664
        {
1420
 
          subtype = [self scanToken: scanner];
 
1665
          subtype = [self scanName: scanner];
1421
1666
          if ([subtype length] == 0)
1422
1667
            {
1423
1668
              NSLog(@"Invalid Mime content-type (subtype)");
1424
1669
              return NO;
1425
1670
            }
1426
1671
          subtype = [subtype lowercaseString];
1427
 
          [info setObject: subtype forKey: @"SubType"];
 
1672
          [info setObject: subtype forKey: @"Subtype"];
1428
1673
          value = [NSString stringWithFormat: @"%@/%@", type, subtype];
1429
1674
        }
1430
1675
      else
1432
1677
          value = type;
1433
1678
        }
1434
1679
 
1435
 
      while ([scanner scanString: @";" intoString: 0] == YES)
1436
 
        {
1437
 
          NSString      *paramName;
1438
 
 
1439
 
          paramName = [self scanToken: scanner];
1440
 
          if ([paramName length] == 0)
1441
 
            {
1442
 
              NSLog(@"Invalid Mime content-type (parameter name)");
1443
 
              return NO;
1444
 
            }
1445
 
          if ([scanner scanString: @"=" intoString: 0] == YES)
1446
 
            {
1447
 
              NSString  *paramValue;
1448
 
 
1449
 
              paramValue = [self scanToken: scanner];
1450
 
              if (paramValue == nil)
1451
 
                {
1452
 
                  paramValue = @"";
1453
 
                }
1454
 
              if (parameters == nil)
1455
 
                {
1456
 
                  parameters = [NSMutableDictionary dictionary];
1457
 
                }
1458
 
              paramName = [paramName lowercaseString];
1459
 
              [parameters setObject: paramValue forKey: paramName];
1460
 
            }
1461
 
          else
1462
 
            {
1463
 
              NSLog(@"Ignoring Mime content-type parameter (%@)", paramName);
1464
 
            }
1465
 
        }
 
1680
      [self _scanHeaderParameters: scanner into: info];
1466
1681
    }
1467
1682
  else if ([name isEqualToString: @"content-disposition"] == YES)
1468
1683
    {
1469
 
      value = [self scanToken: scanner];
 
1684
      value = [self scanName: scanner];
1470
1685
      value = [value lowercaseString];
1471
1686
      /*
1472
1687
       *        Concatenate slash separated parts of field.
1473
1688
       */
1474
1689
      while ([scanner scanString: @"/" intoString: 0] == YES)
1475
1690
        {
1476
 
          NSString      *sub = [self scanToken: scanner];
 
1691
          NSString      *sub = [self scanName: scanner];
1477
1692
 
1478
1693
          if ([sub length] > 0)
1479
1694
            {
1485
1700
      /*
1486
1701
       *        Expect anything else to be 'name=value' parameters.
1487
1702
       */
1488
 
      while ([scanner scanString: @";" intoString: 0] == YES)
1489
 
        {
1490
 
          NSString      *paramName;
1491
 
 
1492
 
          paramName = [self scanToken: scanner];
1493
 
          if ([paramName length] == 0)
1494
 
            {
1495
 
              NSLog(@"Invalid Mime content-type (parameter name)");
1496
 
              return NO;
1497
 
            }
1498
 
          if ([scanner scanString: @"=" intoString: 0] == YES)
1499
 
            {
1500
 
              NSString  *paramValue;
1501
 
 
1502
 
              paramValue = [self scanToken: scanner];
1503
 
              if (paramValue == nil)
1504
 
                {
1505
 
                  paramValue = @"";
1506
 
                }
1507
 
              if (parameters == nil)
1508
 
                {
1509
 
                  parameters = [NSMutableDictionary dictionary];
1510
 
                }
1511
 
              paramName = [paramName lowercaseString];
1512
 
              [parameters setObject: paramValue forKey: paramName];
1513
 
            }
1514
 
          else
1515
 
            {
1516
 
              NSLog(@"Ignoring Mime content-disposition parameter (%@)",
1517
 
                paramName);
1518
 
            }
1519
 
        }
 
1703
      [self _scanHeaderParameters: scanner into: info];
 
1704
    }
 
1705
  else
 
1706
    {
 
1707
      int       loc;
 
1708
 
 
1709
      [self scanPastSpace: scanner];
 
1710
      loc = [scanner scanLocation];
 
1711
      value = [[scanner string] substringFromIndex: loc];
1520
1712
    }
1521
1713
 
1522
1714
  if (value != nil)
1523
1715
    {
1524
 
      [info setObject: value forKey: @"Value"];
1525
 
    }
1526
 
  if (parameters != nil)
1527
 
    {
1528
 
      [info setObject: parameters forKey: @"Parameters"];
1529
 
    }
 
1716
      [info setValue: value];
 
1717
    }
 
1718
 
1530
1719
  return YES;
1531
1720
}
1532
1721
 
1533
1722
/**
 
1723
 * A convenience method to use a scanner (that is set up to scan a
 
1724
 * header line) to scan a name - a simple word.
 
1725
 * <list>
 
1726
 *   <item>Leading whitespace is ignored.</item>
 
1727
 * </list>
 
1728
 */
 
1729
- (NSString*) scanName: (NSScanner*)scanner
 
1730
{
 
1731
  NSString              *value;
 
1732
 
 
1733
  [self scanPastSpace: scanner];
 
1734
 
 
1735
  /*
 
1736
   * Scan value terminated by any MIME special character.
 
1737
   */
 
1738
  if ([scanner scanUpToCharactersFromSet: rfc2045Specials
 
1739
                              intoString: &value] == NO)
 
1740
    {
 
1741
      return nil;
 
1742
    }
 
1743
  return value;
 
1744
}
 
1745
 
 
1746
/**
1534
1747
 * A convenience method to scan past any whitespace in the scanner
1535
1748
 * in preparation for scanning something more interesting that
1536
1749
 * comes after it.  Returns YES if any space was read, NO otherwise.
1542
1755
 
1543
1756
  skip = RETAIN([scanner charactersToBeSkipped]);
1544
1757
  [scanner setCharactersToBeSkipped: nil];
1545
 
  scanned = [scanner scanCharactersFromSet: skip intoString: 0];
 
1758
  scanned = [scanner scanCharactersFromSet: whitespace intoString: 0];
1546
1759
  [scanner setCharactersToBeSkipped: skip];
1547
1760
  RELEASE(skip);
1548
1761
  return scanned;
1557
1770
 */
1558
1771
- (NSString*) scanSpecial: (NSScanner*)scanner
1559
1772
{
 
1773
  NSCharacterSet        *specials;
1560
1774
  unsigned              location;
1561
1775
  unichar               c;
1562
1776
 
1563
1777
  [self scanPastSpace: scanner];
1564
1778
 
 
1779
  if (flags.isHttp == 1)
 
1780
    {
 
1781
      specials = rfc822Specials;
 
1782
    }
 
1783
  else
 
1784
    {
 
1785
      specials = rfc2045Specials;
 
1786
    }
1565
1787
  /*
1566
1788
   * Now return token delimiter (may be whitespace)
1567
1789
   */
1590
1812
 */
1591
1813
- (NSString*) scanToken: (NSScanner*)scanner
1592
1814
{
 
1815
  [self scanPastSpace: scanner];
1593
1816
  if ([scanner scanString: @"\"" intoString: 0] == YES)         // Quoted
1594
1817
    {
1595
1818
      NSString  *string = [scanner string];
1610
1833
            }
1611
1834
          if ([string characterAtIndex: r.location - 1] == '\\')
1612
1835
            {
1613
 
              r.location++;
1614
 
              r.length = length - r.location;
 
1836
              int       p;
 
1837
 
 
1838
              /*
 
1839
               * Count number of escape ('\') characters ... if it's odd
 
1840
               * then the quote has been escaped and is not a closing
 
1841
               * quote.
 
1842
               */
 
1843
              p = r.location;
 
1844
              while (p > 0 && [string characterAtIndex: p - 1] == '\\')
 
1845
                {
 
1846
                  p--;
 
1847
                }
 
1848
              p = r.location - p;
 
1849
              if (p % 2 == 1)
 
1850
                {
 
1851
                  r.location++;
 
1852
                  r.length = length - r.location;
 
1853
                }
 
1854
              else
 
1855
                {
 
1856
                  done = YES;
 
1857
                }
1615
1858
            }
1616
1859
          else
1617
1860
            {
1618
1861
              done = YES;
1619
1862
            }
1620
1863
        }
1621
 
      [scanner setScanLocation: r.length + 1];
 
1864
      [scanner setScanLocation: r.location + 1];
1622
1865
      length = r.location - start;
1623
1866
      if (length == 0)
1624
1867
        {
1636
1879
              if (*src == '\\')
1637
1880
                {
1638
1881
                  src++;
 
1882
                  if (flags.buggyQuotes == 1 && *src != '\\' && *src != '"')
 
1883
                    {
 
1884
                      *dst++ = '\\';    // Buggy use of escape in quotes.
 
1885
                    }
1639
1886
                }
1640
1887
              *dst++ = *src++;
1641
1888
            }
1644
1891
    }
1645
1892
  else                                                  // Token
1646
1893
    {
1647
 
      NSCharacterSet            *skip;
 
1894
      NSCharacterSet            *specials;
1648
1895
      NSString                  *value;
1649
1896
 
 
1897
      if (flags.isHttp == 1)
 
1898
        {
 
1899
          specials = rfc822Specials;
 
1900
        }
 
1901
      else
 
1902
        {
 
1903
          specials = rfc2045Specials;
 
1904
        }
 
1905
 
1650
1906
      /*
1651
1907
       * Move past white space.
1652
1908
       */
1653
 
      skip = RETAIN([scanner charactersToBeSkipped]);
1654
 
      [scanner setCharactersToBeSkipped: nil];
1655
 
      [scanner scanCharactersFromSet: skip intoString: 0];
1656
 
      [scanner setCharactersToBeSkipped: skip];
1657
 
      RELEASE(skip);
 
1909
      [self scanPastSpace: scanner];
1658
1910
 
1659
1911
      /*
1660
1912
       * Scan value terminated by any special character.
1668
1920
    }
1669
1921
}
1670
1922
 
 
1923
/**
 
1924
 * Method to inform the parser that the data it is parsing is likely to
 
1925
 * contain fields with buggy use of backslash quotes ... and it should
 
1926
 * try to be tolerant of them and treat them as is they were escaped
 
1927
 * backslashes.  This is for use with things like microsoft internet
 
1928
 * explorer, which puts the backslashes used as file path separators
 
1929
 * in parameters without quoting them.
 
1930
 */
 
1931
- (void) setBuggyQuotes: (BOOL)flag
 
1932
{
 
1933
  if (flag)
 
1934
    {
 
1935
      flags.buggyQuotes = 1;
 
1936
    }
 
1937
  else
 
1938
    {
 
1939
      flags.buggyQuotes = 0;
 
1940
    }
 
1941
}
 
1942
 
 
1943
/**
 
1944
 * Method to inform the parser that body parts with no content-type
 
1945
 * header (which are treated as text/plain) should use the specified
 
1946
 * characterset rather than the default (us-ascii)
 
1947
 */
 
1948
- (void) setDefaultCharset: (NSString*)aName
 
1949
{
 
1950
  _defaultEncoding = [GSMimeDocument encodingFromCharset: aName];
 
1951
}
 
1952
 
 
1953
/**
 
1954
 * Method to inform the parser that the data it is parsing is an HTTP
 
1955
 * document rather than true MIME.  This method is called internally
 
1956
 * if the parser detects an HTTP response line at the start of the
 
1957
 * headers it is parsing.
 
1958
 */
 
1959
- (void) setIsHttp
 
1960
{
 
1961
  flags.isHttp = 1;
 
1962
}
1671
1963
@end
1672
1964
 
1673
1965
@implementation GSMimeParser (Private)
1674
1966
/*
 
1967
 * Make a new child to parse a subsidiary document
 
1968
 */
 
1969
- (void) _child
 
1970
{
 
1971
  DESTROY(child);
 
1972
  child = [GSMimeParser new];
 
1973
  if (flags.buggyQuotes == 1)
 
1974
    {
 
1975
      [child setBuggyQuotes: YES];
 
1976
    }
 
1977
  /*
 
1978
   * Tell child parser the default encoding to use.
 
1979
   */
 
1980
  child->_defaultEncoding = _defaultEncoding;
 
1981
}
 
1982
 
 
1983
/*
1675
1984
 * This method takes the raw data of an unfolded header line, and handles
1676
 
 * RFC2047 word encoding in the header by creating a string containing the
1677
 
 * decoded words.
 
1985
 * Method to inform the parser that the data it is parsing is an HTTP
 
1986
 * document rather than true MIME.  This method is called internally
 
1987
 * if the parser detects an HTTP response line at the start of the
 
1988
 * headers it is parsing.
 
1989
 * RFC2047 word encoding in the header is handled by creating a
 
1990
 * string containing the decoded words.
1678
1991
 */
1679
1992
- (NSString*) _decodeHeader
1680
1993
{
1681
 
  NSStringEncoding      charset;
 
1994
  NSStringEncoding      enc;
 
1995
  NSString              *charset;
1682
1996
  WE                    encoding;
1683
1997
  unsigned char         c;
1684
1998
  unsigned char         *src, *dst, *beg;
1731
2045
 
1732
2046
          src += 2;
1733
2047
          tmp = src;
1734
 
          src = (unsigned char*)strchr((char*)src, '?'); 
 
2048
          src = (unsigned char*)strchr((char*)src, '?');
1735
2049
          if (src == 0)
1736
2050
            {
1737
2051
              NSLog(@"Bad encoded word - character set terminator missing");
1738
2052
              break;
1739
2053
            }
1740
2054
          *src = '\0';
1741
 
          charset = parseCharacterSet([NSString stringWithCString: tmp]);
 
2055
          charset = [NSString stringWithCString: tmp];
 
2056
          enc = [GSMimeDocument encodingFromCharset: charset];
1742
2057
          src++;
1743
2058
          if (*src == 0)
1744
2059
            {
1790
2105
              NSData    *d = [NSData dataWithBytes: beg length: dst - beg];
1791
2106
              NSString  *s;
1792
2107
 
1793
 
              s = [[NSString alloc] initWithData: d
1794
 
                                        encoding: charset];
 
2108
              s = [[NSString alloc] initWithData: d encoding: enc];
1795
2109
              [hdr appendString: s];
1796
2110
              RELEASE(s);
1797
2111
              dst = beg;
1827
2141
 
1828
2142
  if (context == nil)
1829
2143
    {
1830
 
      NSDictionary      *hdr;
 
2144
      GSMimeHeader      *hdr;
1831
2145
 
1832
2146
      expect = 0;
1833
2147
      /*
1836
2150
      hdr = [document headerNamed: @"content-length"];
1837
2151
      if (hdr != nil)
1838
2152
        {
1839
 
          expect = [[hdr objectForKey: @"BaseValue"] intValue];
 
2153
          expect = [[hdr value] intValue];
1840
2154
        }
1841
2155
 
1842
2156
      /*
1847
2161
        {
1848
2162
          hdr = [document headerNamed: @"content-transfer-encoding"];
1849
2163
        }
1850
 
      else if ([[hdr objectForKey: @"Value"] isEqual: @"chunked"] == YES)
 
2164
      else if ([[[hdr value] lowercaseString] isEqualToString: @"chunked"])
1851
2165
        {
1852
2166
          /*
1853
2167
           * Chunked transfer encoding overrides any content length spec.
1860
2174
    }
1861
2175
 
1862
2176
  NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'", l, l, l, [d bytes]);
 
2177
  // NSDebugMLLog(@"GSMime", @"Boundary - '%*.*s'", [boundary length], [boundary length], [boundary bytes]);
1863
2178
 
1864
2179
  if ([context atEnd] == YES)
1865
2180
    {
1866
 
      inBody = NO;
1867
 
      complete = YES;
 
2181
      flags.inBody = 0;
 
2182
      flags.complete = 1;
1868
2183
      if ([d length] > 0)
1869
2184
        {
1870
2185
          NSLog(@"Additional data (%*.*s) ignored after parse complete",
1874
2189
    }
1875
2190
  else if (boundary == nil)
1876
2191
    {
1877
 
      NSDictionary      *typeInfo;
 
2192
      GSMimeHeader      *typeInfo;
1878
2193
      NSString          *type;
1879
2194
 
1880
2195
      typeInfo = [document headerNamed: @"content-type"];
1882
2197
      if ([type isEqualToString: @"multipart"] == YES)
1883
2198
        {
1884
2199
          NSLog(@"multipart decode attempt without boundary");
1885
 
          inBody = NO;
1886
 
          complete = YES;
 
2200
          flags.inBody = 0;
 
2201
          flags.complete = 1;
1887
2202
          result = NO;
1888
2203
        }
1889
2204
      else
1890
2205
        {
 
2206
          unsigned      dLength = [d length];
 
2207
 
 
2208
          if (expect > 0 && rawBodyLength > expect)
 
2209
            {
 
2210
              NSData    *excess;
 
2211
 
 
2212
              dLength -= (rawBodyLength - expect);
 
2213
              rawBodyLength = expect;
 
2214
              excess = [d subdataWithRange:
 
2215
                NSMakeRange(dLength, [d length] - dLength)];
 
2216
              NSLog(@"Excess data ignored: %@", excess);
 
2217
            }
1891
2218
          [self decodeData: d
1892
 
                 fromRange: NSMakeRange(0, [d length])
 
2219
                 fromRange: NSMakeRange(0, dLength)
1893
2220
                  intoData: data
1894
2221
               withContext: context];
1895
2222
 
1896
2223
          if ([context atEnd] == YES
1897
2224
            || (expect > 0 && rawBodyLength >= expect))
1898
2225
            {
1899
 
              inBody = NO;
1900
 
              complete = YES;
 
2226
              flags.inBody = 0;
 
2227
              flags.complete = 1;
1901
2228
 
1902
 
              NSDebugMLLog(@"GSMime", @"Parse body complete");
 
2229
              NSDebugMLLog(@"GSMime", @"Parse body complete", "");
1903
2230
              /*
1904
 
               * If no content type is supplied, we assume text.
 
2231
               * If no content type is supplied, we assume text ... unless
 
2232
               * we have something that's known to be a file.
1905
2233
               */
1906
 
              if (type == nil || [type isEqualToString: @"text"] == YES)
1907
 
                {
1908
 
                  NSDictionary          *params;
1909
 
                  NSString              *charset;
 
2234
              if (type == nil)
 
2235
                {
 
2236
                  if ([document contentFile] != nil)
 
2237
                    {
 
2238
                      type = @"application";
 
2239
                    }
 
2240
                  else
 
2241
                    {
 
2242
                      type = @"text";
 
2243
                    }
 
2244
                }
 
2245
 
 
2246
              if ([type isEqualToString: @"text"] == YES)
 
2247
                {
1910
2248
                  NSStringEncoding      stringEncoding;
1911
2249
                  NSString              *string;
1912
2250
 
 
2251
                  if (typeInfo == nil)
 
2252
                    {
 
2253
                      stringEncoding = _defaultEncoding;
 
2254
                      if (stringEncoding != NSASCIIStringEncoding)
 
2255
                        {
 
2256
                          NSString      *charset;
 
2257
 
 
2258
                          if (typeInfo == nil)
 
2259
                            {
 
2260
                              typeInfo = [GSMimeHeader new];
 
2261
                              [typeInfo setName: @"content-type"];
 
2262
                              [typeInfo setValue: @"text/plain"];
 
2263
                              [typeInfo setObject: @"text"
 
2264
                                           forKey: @"Type"];
 
2265
                              [typeInfo setObject: @"plain"
 
2266
                                           forKey: @"Subtype"];
 
2267
                              [document setHeader: typeInfo];
 
2268
                              RELEASE(typeInfo);
 
2269
                            }
 
2270
                          charset = [GSMimeDocument charsetFromEncoding:
 
2271
                            stringEncoding];
 
2272
                          [typeInfo setParameter: charset
 
2273
                                          forKey: @"charset"];
 
2274
                        }
 
2275
                    }
 
2276
                  else
 
2277
                    {
 
2278
                      NSString  *charset;
 
2279
 
 
2280
                      charset = [typeInfo parameterForKey: @"charset"];
 
2281
                      stringEncoding
 
2282
                        = [GSMimeDocument encodingFromCharset: charset];
 
2283
                    }
1913
2284
                  /*
1914
2285
                   * Assume that content type is best represented as NSString.
1915
2286
                   */
1916
 
                  params = [typeInfo objectForKey: @"Parameters"];
1917
 
                  charset = [params objectForKey: @"charset"];
1918
 
                  stringEncoding = parseCharacterSet(charset);
1919
2287
                  string = [[NSString alloc] initWithData: data
1920
2288
                                                 encoding: stringEncoding];
1921
2289
                  [document setContent: string];
1927
2295
                   * Assume that any non-text content type is best
1928
2296
                   * represented as NSData.
1929
2297
                   */
1930
 
                  [document setContent: AUTORELEASE([data copy])];
 
2298
                  [document setContent: data];
1931
2299
                }
1932
2300
            }
1933
2301
          result = YES;
1939
2307
      unsigned char     *bBytes = (unsigned char*)[boundary bytes];
1940
2308
      unsigned char     bInit = bBytes[0];
1941
2309
      BOOL              done = NO;
 
2310
      BOOL              endedFinalPart = NO;
1942
2311
 
1943
2312
      [data appendBytes: [d bytes] length: [d length]];
1944
2313
      bytes = (unsigned char*)[data mutableBytes];
1946
2315
 
1947
2316
      while (done == NO)
1948
2317
        {
 
2318
          BOOL  found = NO;
 
2319
 
1949
2320
          /*
1950
2321
           * Search our data for the next boundary.
1951
2322
           */
1957
2328
                  if (lineStart == 0 || bytes[lineStart-1] == '\r'
1958
2329
                    || bytes[lineStart-1] == '\n')
1959
2330
                    {
 
2331
                      BOOL      lastPart = NO;
 
2332
                      unsigned  eol;
 
2333
 
1960
2334
                      lineEnd = lineStart + bLength;
 
2335
                      eol = lineEnd;
 
2336
                      if (lineEnd + 2 <= dataEnd && bytes[lineEnd] == '-'
 
2337
                        && bytes[lineEnd+1] == '-')
 
2338
                        {
 
2339
                          eol += 2;
 
2340
                          lastPart = YES;
 
2341
                        }
 
2342
                      if (eol < dataEnd && bytes[eol] == '\r')
 
2343
                        {
 
2344
                          eol++;
 
2345
                        }
 
2346
                      if (eol < dataEnd && bytes[eol] == '\n')
 
2347
                        {
 
2348
                          flags.wantEndOfLine = 0;
 
2349
                          found = YES;
 
2350
                          endedFinalPart = lastPart;
 
2351
                        }
 
2352
                      else
 
2353
                        {
 
2354
                          flags.wantEndOfLine = 1;
 
2355
                        }
1961
2356
                      break;
1962
2357
                    }
1963
2358
                }
1964
2359
              lineStart++;
1965
2360
            }
1966
 
          if (dataEnd - lineStart < bLength)
 
2361
          if (found == NO)
1967
2362
            {
1968
2363
              done = YES;       /* Needs more data.     */
1969
 
            } 
 
2364
            }
1970
2365
          else if (child == nil)
1971
2366
            {
 
2367
              NSString  *cset;
 
2368
        
1972
2369
              /*
1973
2370
               * Found boundary at the start of the first section.
1974
2371
               * Set sectionStart to point immediately after boundary.
1975
2372
               */
1976
2373
              lineStart += bLength;
1977
2374
              sectionStart = lineStart;
1978
 
              child = [GSMimeParser new];
 
2375
 
 
2376
              /*
 
2377
               * If we have an explicit character set for the multipart
 
2378
               * document, we set it as the default characterset inherited
 
2379
               * by any child documents.
 
2380
               */
 
2381
              cset = [[document headerNamed: @"content-type"]
 
2382
                parameterForKey: @"charset"];
 
2383
              if (cset != nil)
 
2384
                {
 
2385
                  [self setDefaultCharset: cset];
 
2386
                }
 
2387
 
 
2388
              [self _child];
1979
2389
            }
1980
2390
          else
1981
2391
            {
1982
2392
              NSData    *d;
1983
 
              BOOL      endedFinalPart = NO;
 
2393
              unsigned  pos;
1984
2394
 
1985
2395
              /*
1986
2396
               * Found boundary at the end of a section.
1991
2401
                && bytes[sectionStart+1] == '-')
1992
2402
                {
1993
2403
                  sectionStart += 2;
1994
 
                  endedFinalPart = YES;
1995
2404
                }
1996
2405
              if (bytes[sectionStart] == '\r')
1997
 
                sectionStart++;
 
2406
                {
 
2407
                  sectionStart++;
 
2408
                }
1998
2409
              if (bytes[sectionStart] == '\n')
1999
 
                sectionStart++;
 
2410
                {
 
2411
                  sectionStart++;
 
2412
                }
2000
2413
 
2001
2414
              /*
2002
2415
               * Create data object for this section and pass it to the
2003
 
               * child parser to deal with.
 
2416
               * child parser to deal with.  NB. As lineStart points to
 
2417
               * the start of the end boundary, we need to step back to
 
2418
               * before the end of line introducing it in order to have
 
2419
               * the correct length of body data for the child document.
2004
2420
               */
 
2421
              pos = lineStart;
 
2422
              if (pos > 0 && bytes[pos-1] == '\n')
 
2423
                {
 
2424
                  pos--;
 
2425
                }
 
2426
              if (pos > 0 && bytes[pos-1] == '\r')
 
2427
                {
 
2428
                  pos--;
 
2429
                }
2005
2430
              d = [NSData dataWithBytes: &bytes[sectionStart]
2006
 
                                 length: lineStart - sectionStart];
2007
 
              if ([child parse: d] == YES || [child parse: nil] == YES)
2008
 
                {
2009
 
                  NSMutableArray        *a;
 
2431
                                 length: pos - sectionStart];
 
2432
              if ([child parse: d] == YES)
 
2433
                {
 
2434
                  /*
 
2435
                   * The parser wants more data, so pass a nil data item
 
2436
                   * to tell it that it has had all there is.
 
2437
                   */
 
2438
                  [child parse: nil];
 
2439
                }
 
2440
              if ([child isComplete] == YES)
 
2441
                {
2010
2442
                  GSMimeDocument        *doc;
2011
2443
 
2012
2444
                  /*
2013
2445
                   * Store the document produced by the child, and
2014
2446
                   * create a new parser for the next section.
2015
2447
                   */
2016
 
                  a = [document content];
2017
 
                  if (a == nil)
2018
 
                    {
2019
 
                      a = [NSMutableArray new];
2020
 
                      [document setContent: a];
2021
 
                      RELEASE(a);
2022
 
                    }
2023
 
                  doc = [child document];
 
2448
                  doc = [child mimeDocument];
2024
2449
                  if (doc != nil)
2025
2450
                    {
2026
 
                      [a addObject: doc];
 
2451
                      [document addContent: doc];
2027
2452
                    }
2028
 
                  RELEASE(child);
2029
 
                  child = [GSMimeParser new];
 
2453
                  [self _child];
2030
2454
                }
2031
2455
              else
2032
2456
                {
2034
2458
                   * Section failed to decode properly!
2035
2459
                   */
2036
2460
                  NSLog(@"Failed to decode section of multipart");
2037
 
                  RELEASE(child);
2038
 
                  child = [GSMimeParser new];
 
2461
                  [self _child];
2039
2462
                }
2040
2463
 
2041
2464
              /*
2049
2472
              bytes = (unsigned char*)[data mutableBytes];
2050
2473
              lineStart -= sectionStart;
2051
2474
              sectionStart = 0;
 
2475
              if (endedFinalPart == YES)
 
2476
                {
 
2477
                  done = YES;
 
2478
                }
2052
2479
            }
2053
2480
        }
2054
2481
      /*
2055
 
       * Check to see if we have reached content length.
 
2482
       * Check to see if we have reached content length or ended multipart
 
2483
       * document.
2056
2484
       */
2057
 
      if (expect > 0 && rawBodyLength >= expect)
2058
 
        {
2059
 
          complete = YES;
2060
 
          inBody = NO;
2061
 
        }
2062
 
      result = YES;
 
2485
      if (endedFinalPart == YES || (expect > 0 && rawBodyLength >= expect))
 
2486
        {
 
2487
          flags.complete = 1;
 
2488
          flags.inBody = 0;
 
2489
          result = NO;
 
2490
        }
 
2491
      else
 
2492
        {
 
2493
          result = YES;
 
2494
        }
2063
2495
    }
2064
2496
  return result;
2065
2497
}
2069
2501
  char          c;
2070
2502
  BOOL          unwrappingComplete = NO;
2071
2503
 
2072
 
  lineStart = lineEnd;
2073
 
  NSDebugMLLog(@"GSMime", @"entry: input:%u dataEnd:%u lineStart:%u '%*.*s'",
 
2504
  lineStart = lineEnd = input;
 
2505
  NSDebugMLLog(@"GSMimeH", @"entry: input:%u dataEnd:%u lineStart:%u '%*.*s'",
2074
2506
    input, dataEnd, lineStart, dataEnd - input, dataEnd - input, &bytes[input]);
2075
2507
  /*
2076
2508
   * RFC822 lets header fields break across lines, with continuation
2080
2512
   */
2081
2513
  while (input < dataEnd && unwrappingComplete == NO)
2082
2514
    {
2083
 
      unsigned  pos = input;
2084
 
 
2085
 
      if ((c = bytes[pos]) != '\r' && c != '\n')
2086
 
        {
2087
 
          while (pos < dataEnd && (c = bytes[pos]) != '\r' && c != '\n')
2088
 
            {
2089
 
              pos++;
2090
 
            }
2091
 
          if (pos == dataEnd)
2092
 
            {
2093
 
              break;    /* need more data */
2094
 
            }
2095
 
          pos++;
2096
 
          if (c == '\r' && pos < dataEnd && bytes[pos] == '\n')
2097
 
            {
2098
 
              pos++;
2099
 
            }
2100
 
          if (pos == dataEnd)
2101
 
            {
2102
 
              break;    /* need more data */
2103
 
            }
2104
 
          /*
2105
 
           * Copy data up to end of line, and skip past end.
2106
 
           */
2107
 
          while (input < dataEnd && (c = bytes[input]) != '\r' && c != '\n')
2108
 
            {
2109
 
              bytes[lineEnd++] = bytes[input++];
2110
 
            }
2111
 
        }
2112
 
 
2113
 
      /*
2114
 
       * Eat a newline that is part of a cr-lf sequence.
2115
 
       */
2116
 
      input++;
2117
 
      if (c == '\r' && input < dataEnd && bytes[input] == '\n')
2118
 
        {
 
2515
      if ((c = bytes[input]) != '\r' && c != '\n')
 
2516
        {
2119
2517
          input++;
2120
2518
        }
2121
 
 
2122
 
      /*
2123
 
       * See if we have a wrapped line.
2124
 
       */
2125
 
      if ((c = bytes[input]) == '\r' || c == '\n' || isspace(c) == 0)
 
2519
      else
 
2520
        {
 
2521
          lineEnd = input++;
 
2522
          if (input < dataEnd && c == '\r' && bytes[input] == '\n')
 
2523
            {
 
2524
              c = bytes[input++];
 
2525
            }
 
2526
          if (input < dataEnd || (c == '\n' && lineEnd == lineStart))
 
2527
            {
 
2528
              unsigned  length = lineEnd - lineStart;
 
2529
 
 
2530
              if (length == 0)
 
2531
                {
 
2532
                  /* An empty line cannot be folded.    */
 
2533
                  unwrappingComplete = YES;
 
2534
                }
 
2535
              else if ((c = bytes[input]) != '\r' && c != '\n' && isspace(c))
 
2536
                {
 
2537
                  unsigned      diff = input - lineEnd;
 
2538
 
 
2539
                  memmove(&bytes[lineStart + diff], &bytes[lineStart], length);
 
2540
                  lineStart += diff;
 
2541
                  lineEnd += diff;
 
2542
                }
 
2543
              else
 
2544
                {
 
2545
                  /* No folding ... done.       */
 
2546
                  unwrappingComplete = YES;
 
2547
                }
 
2548
            }
 
2549
        }
 
2550
    }
 
2551
 
 
2552
  if (unwrappingComplete == YES)
 
2553
    {
 
2554
      if (lineEnd == lineStart)
2126
2555
        {
2127
 
          unwrappingComplete = YES;
2128
 
          bytes[lineEnd] = '\0';
 
2556
          unsigned              lengthRemaining;
 
2557
 
2129
2558
          /*
2130
 
           * If this is a zero-length line, we have reached the end of
2131
 
           * the headers.
 
2559
           * Overwrite the header data with the body, ready to start
 
2560
           * parsing the body data.
2132
2561
           */
2133
 
          if (lineEnd == lineStart)
 
2562
          lengthRemaining = dataEnd - input;
 
2563
          if (lengthRemaining > 0)
2134
2564
            {
2135
 
              unsigned          lengthRemaining;
2136
 
 
2137
 
              /*
2138
 
               * Overwrite the header data with the body, ready to start
2139
 
               * parsing the body data.
2140
 
               */
2141
 
              lengthRemaining = dataEnd - input;
2142
 
              if (lengthRemaining > 0)
2143
 
                {
2144
 
                  memcpy(bytes, &bytes[input], lengthRemaining);
2145
 
                }
2146
 
              dataEnd = lengthRemaining;
2147
 
              [data setLength: lengthRemaining];
2148
 
              bytes = (unsigned char*)[data mutableBytes];
2149
 
              sectionStart = 0;
2150
 
              lineStart = 0;
2151
 
              lineEnd = 0;
2152
 
              input = 0;
2153
 
              inBody = YES;
 
2565
              memcpy(bytes, &bytes[input], lengthRemaining);
2154
2566
            }
 
2567
          dataEnd = lengthRemaining;
 
2568
          [data setLength: lengthRemaining];
 
2569
          bytes = (unsigned char*)[data mutableBytes];
 
2570
          sectionStart = 0;
 
2571
          lineStart = 0;
 
2572
          lineEnd = 0;
 
2573
          input = 0;
 
2574
          flags.inBody = 1;
2155
2575
        }
2156
2576
    }
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]);
 
2577
  else
 
2578
    {
 
2579
      input = lineStart;        /* Reset to try again with more data.   */
 
2580
    }
 
2581
 
 
2582
  NSDebugMLLog(@"GSMimeH", @"exit: inBody:%d unwrappingComplete: %d "
 
2583
    @"input:%u dataEnd:%u lineStart:%u '%*.*s'", flags.inBody,
 
2584
    unwrappingComplete,
 
2585
    input, dataEnd, lineStart, lineEnd - lineStart, lineEnd - lineStart,
 
2586
    &bytes[lineStart]);
2160
2587
  return unwrappingComplete;
2161
2588
}
2162
 
@end
2163
 
 
2164
 
 
 
2589
 
 
2590
- (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info
 
2591
{
 
2592
  [self scanPastSpace: scanner];
 
2593
  while ([scanner scanString: @";" intoString: 0] == YES)
 
2594
    {
 
2595
      NSString  *paramName;
 
2596
 
 
2597
      paramName = [self scanName: scanner];
 
2598
      if ([paramName length] == 0)
 
2599
        {
 
2600
          NSLog(@"Invalid Mime %@ field (parameter name)", [info name]);
 
2601
          return NO;
 
2602
        }
 
2603
 
 
2604
      [self scanPastSpace: scanner];
 
2605
      if ([scanner scanString: @"=" intoString: 0] == YES)
 
2606
        {
 
2607
          NSString      *paramValue;
 
2608
 
 
2609
          paramValue = [self scanToken: scanner];
 
2610
          [self scanPastSpace: scanner];
 
2611
          if (paramValue == nil)
 
2612
            {
 
2613
              paramValue = @"";
 
2614
            }
 
2615
          [info setParameter: paramValue forKey: paramName];
 
2616
        }
 
2617
      else
 
2618
        {
 
2619
          NSLog(@"Ignoring Mime %@ field parameter (%@)",
 
2620
            [info name], paramName);
 
2621
        }
 
2622
    }
 
2623
  return YES;
 
2624
}
 
2625
 
 
2626
@end
 
2627
 
 
2628
 
 
2629
 
 
2630
@implementation GSMimeHeader
 
2631
 
 
2632
static NSCharacterSet   *nonToken = nil;
 
2633
static NSCharacterSet   *tokenSet = nil;
 
2634
 
 
2635
+ (void) initialize
 
2636
{
 
2637
  if (nonToken == nil)
 
2638
    {
 
2639
      NSMutableCharacterSet     *ms;
 
2640
 
 
2641
      ms = [NSMutableCharacterSet new];
 
2642
      [ms addCharactersInRange: NSMakeRange(33, 126-32)];
 
2643
      [ms removeCharactersInString: @"()<>@,;:\\\"/[]?="];
 
2644
      tokenSet = [ms copy];
 
2645
      RELEASE(ms);
 
2646
      nonToken = RETAIN([tokenSet invertedSet]);
 
2647
      if (NSArrayClass == 0)
 
2648
        {
 
2649
          NSArrayClass = [NSArray class];
 
2650
        }
 
2651
    }
 
2652
}
 
2653
 
 
2654
/**
 
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.
 
2658
 */
 
2659
+ (NSString*) makeQuoted: (NSString*)v always: (BOOL)flag
 
2660
{
 
2661
  NSRange       r;
 
2662
  unsigned      pos = 0;
 
2663
  unsigned      l = [v length];
 
2664
 
 
2665
  r = [v rangeOfCharacterFromSet: nonToken
 
2666
                         options: NSLiteralSearch
 
2667
                           range: NSMakeRange(pos, l - pos)];
 
2668
  if (flag == YES || r.length > 0)
 
2669
    {
 
2670
      NSMutableString   *m = [NSMutableString new];
 
2671
 
 
2672
      [m appendString: @"\""];
 
2673
      while (r.length > 0)
 
2674
        {
 
2675
          unichar       c;
 
2676
 
 
2677
          if (r.location > pos)
 
2678
            {
 
2679
              [m appendString:
 
2680
                [v substringWithRange: NSMakeRange(pos, r.location - pos)]];
 
2681
            }
 
2682
          pos = r.location + 1;
 
2683
          c = [v characterAtIndex: r.location];
 
2684
          if (c < 128)
 
2685
            {
 
2686
              if (c == '\\' || c == '"')
 
2687
                {
 
2688
                  [m appendFormat: @"\\%c", c];
 
2689
                }
 
2690
              else
 
2691
                {
 
2692
                  [m appendFormat: @"%c", c];
 
2693
                }
 
2694
            }
 
2695
          else
 
2696
            {
 
2697
              NSLog(@"NON ASCII characters not yet implemented");
 
2698
            }
 
2699
          r = [v rangeOfCharacterFromSet: nonToken
 
2700
                                 options: NSLiteralSearch
 
2701
                                   range: NSMakeRange(pos, l - pos)];
 
2702
        }
 
2703
      if (l > pos)
 
2704
        {
 
2705
          [m appendString:
 
2706
            [v substringWithRange: NSMakeRange(pos, l - pos)]];
 
2707
        }
 
2708
      [m appendString: @"\""];
 
2709
      v = AUTORELEASE(m);
 
2710
    }
 
2711
  return v;
 
2712
}
 
2713
 
 
2714
/**
 
2715
 * Convert the supplied string to a standardized token by making it
 
2716
 * lowercase and removing all illegal characters.
 
2717
 */
 
2718
+ (NSString*) makeToken: (NSString*)t
 
2719
{
 
2720
  NSRange       r;
 
2721
 
 
2722
  t = [t lowercaseString];
 
2723
  r = [t rangeOfCharacterFromSet: nonToken];
 
2724
  if (r.length > 0)
 
2725
    {
 
2726
      NSMutableString   *m = [t mutableCopy];
 
2727
 
 
2728
      while (r.length > 0)
 
2729
        {
 
2730
          [m deleteCharactersInRange: r];
 
2731
          r = [m rangeOfCharacterFromSet: nonToken];
 
2732
        }
 
2733
      t = AUTORELEASE(m);
 
2734
    }
 
2735
  return t;
 
2736
}
 
2737
 
 
2738
- (id) copyWithZone: (NSZone*)z
 
2739
{
 
2740
  GSMimeHeader  *c = [GSMimeHeader allocWithZone: z];
 
2741
  NSEnumerator  *e;
 
2742
  NSString      *k;
 
2743
 
 
2744
  c = [c initWithName: [self name]
 
2745
                value: [self value]
 
2746
           parameters: [self parameters]];
 
2747
  e = [objects keyEnumerator];
 
2748
  while ((k = [e nextObject]) != nil)
 
2749
    {
 
2750
      [c setObject: [self objectForKey: k] forKey: k];
 
2751
    }
 
2752
  return c;
 
2753
}
 
2754
 
 
2755
- (void) dealloc
 
2756
{
 
2757
  RELEASE(name);
 
2758
  RELEASE(value);
 
2759
  RELEASE(objects);
 
2760
  RELEASE(params);
 
2761
  [super dealloc];
 
2762
}
 
2763
 
 
2764
- (NSString*) description
 
2765
{
 
2766
  NSMutableString       *desc;
 
2767
 
 
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]];
 
2772
  return desc;
 
2773
}
 
2774
 
 
2775
- (id) init
 
2776
{
 
2777
  return [self initWithName: @"unknown" value: @"none" parameters: nil];
 
2778
}
 
2779
 
 
2780
/**
 
2781
 * Convenience method calling -initWithName:value:parameters: with the
 
2782
 * supplied argument and nil parameters.
 
2783
 */
 
2784
- (id) initWithName: (NSString*)n
 
2785
              value: (NSString*)v
 
2786
{
 
2787
  return [self initWithName: n value: v parameters: nil];
 
2788
}
 
2789
 
 
2790
/**
 
2791
 * <init />
 
2792
 * Initialise a GSMimeHeader supplying a name, a value and a dictionary
 
2793
 * of any parameters occurring after the value.
 
2794
 */
 
2795
- (id) initWithName: (NSString*)n
 
2796
              value: (NSString*)v
 
2797
         parameters: (NSDictionary*)p
 
2798
{
 
2799
  objects = [NSMutableDictionary new];
 
2800
  params = [NSMutableDictionary new];
 
2801
  [self setName: n];
 
2802
  [self setValue: v];
 
2803
  [self setParameters: p];
 
2804
  return self;
 
2805
}
 
2806
 
 
2807
/**
 
2808
 * Returns the name of this header ... a lowercase string.
 
2809
 */
 
2810
- (NSString*) name
 
2811
{
 
2812
  return name;
 
2813
}
 
2814
 
 
2815
/**
 
2816
 * Return extra information specific to a particular header type.
 
2817
 */
 
2818
- (id) objectForKey: (NSString*)k
 
2819
{
 
2820
  return [objects objectForKey: k];
 
2821
}
 
2822
 
 
2823
/**
 
2824
 * Returns a dictionary of all the additional objects for the header.
 
2825
 */
 
2826
- (NSDictionary*) objects
 
2827
{
 
2828
  return AUTORELEASE([objects copy]);
 
2829
}
 
2830
 
 
2831
/**
 
2832
 * Return the named parameter value.
 
2833
 */
 
2834
- (NSString*) parameterForKey: (NSString*)k
 
2835
{
 
2836
  NSString      *p = [params objectForKey: k];
 
2837
 
 
2838
  if (p == nil)
 
2839
    {
 
2840
      k = [GSMimeHeader makeToken: k];
 
2841
      p = [params objectForKey: k];
 
2842
    }
 
2843
  return p;     
 
2844
}
 
2845
 
 
2846
/**
 
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.
 
2850
 */
 
2851
- (NSDictionary*) parameters
 
2852
{
 
2853
  return AUTORELEASE([params copy]);
 
2854
}
 
2855
 
 
2856
/**
 
2857
 * Returns the full text of the header, built from its component parts,
 
2858
 * and including a terminating CR-LF
 
2859
 */
 
2860
- (NSMutableData*) rawMimeData
 
2861
{
 
2862
  NSMutableData *md = [NSMutableData dataWithCapacity: 128];
 
2863
  NSEnumerator  *e = [params keyEnumerator];
 
2864
  NSString      *k;
 
2865
  NSData        *d = [[self name] dataUsingEncoding: NSASCIIStringEncoding];
 
2866
  unsigned      l = [d length];
 
2867
  char          buf[l];
 
2868
  unsigned int  i = 0;
 
2869
  BOOL          conv = YES;
 
2870
 
 
2871
#define LIM     120
 
2872
  /*
 
2873
   * Capitalise the header name.  However, the version header is a special
 
2874
   * case - it is defined as being literally 'MIME-Version'
 
2875
   */
 
2876
  memcpy(buf, [d bytes], l);
 
2877
  if (l == 12 && memcmp(buf, "mime-version", 12) == 0)
 
2878
    {
 
2879
      memcpy(buf, "MIME-Version", 12);
 
2880
    }
 
2881
  else
 
2882
    {
 
2883
      while (i < l)
 
2884
        {
 
2885
          if (conv == YES)
 
2886
            {
 
2887
              if (islower(buf[i]))
 
2888
                {
 
2889
                  buf[i] = toupper(buf[i]);
 
2890
                }
 
2891
            }
 
2892
          if (buf[i++] == '-')
 
2893
            {
 
2894
              conv = YES;
 
2895
            }
 
2896
          else
 
2897
            {
 
2898
              conv = NO;
 
2899
            }
 
2900
        }
 
2901
    }
 
2902
  [md appendBytes: buf length: l];
 
2903
  d = wordData(value);
 
2904
  if ([md length] + [d length] + 2 > LIM)
 
2905
    {
 
2906
      [md appendBytes: ":\r\n\t" length: 4];
 
2907
      [md appendData: d];
 
2908
      l = [md length] + 8;
 
2909
    }
 
2910
  else
 
2911
    {
 
2912
      [md appendBytes: ": " length: 2];
 
2913
      [md appendData: d];
 
2914
      l = [md length];
 
2915
    }
 
2916
 
 
2917
  while ((k = [e nextObject]) != nil)
 
2918
    {
 
2919
      NSString  *v;
 
2920
      NSData    *kd;
 
2921
      NSData    *vd;
 
2922
      unsigned  kl;
 
2923
      unsigned  vl;
 
2924
 
 
2925
      v = [GSMimeHeader makeQuoted: [params objectForKey: k] always: NO];
 
2926
      kd = wordData(k);
 
2927
      vd = wordData(v);
 
2928
      kl = [kd length];
 
2929
      vl = [vd length];
 
2930
 
 
2931
      if ((l + kl + vl + 3) > LIM)
 
2932
        {
 
2933
          [md appendBytes: ";\r\n\t" length: 4];
 
2934
          [md appendData: kd];
 
2935
          [md appendBytes: "=" length: 1];
 
2936
          [md appendData: vd];
 
2937
          l = kl + vl + 9;
 
2938
        }
 
2939
      else
 
2940
        {
 
2941
          [md appendBytes: "; " length: 2];
 
2942
          [md appendData: kd];
 
2943
          [md appendBytes: "=" length: 1];
 
2944
          [md appendData: vd];
 
2945
          l += kl + vl + 3;
 
2946
        }
 
2947
    }
 
2948
  [md appendBytes: "\r\n" length: 2];
 
2949
 
 
2950
  return md;
 
2951
}
 
2952
 
 
2953
/**
 
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'.
 
2957
 */
 
2958
- (void) setName: (NSString*)s
 
2959
{
 
2960
  s = [GSMimeHeader makeToken: s];
 
2961
  if ([s length] == 0)
 
2962
    {
 
2963
      s = @"unknown";
 
2964
    }
 
2965
  ASSIGN(name, s);
 
2966
}
 
2967
 
 
2968
/**
 
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.
 
2973
 */
 
2974
- (void) setObject: (id)o forKey: (NSString*)k
 
2975
{
 
2976
  if (o == nil)
 
2977
    {
 
2978
      [objects removeObjectForKey: k];
 
2979
    }
 
2980
  else
 
2981
    {
 
2982
      [objects setObject: o forKey: k];
 
2983
    }
 
2984
}
 
2985
 
 
2986
/**
 
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
 
2990
 * specified key.
 
2991
 */
 
2992
- (void) setParameter: (NSString*)v forKey: (NSString*)k
 
2993
{
 
2994
  k = [GSMimeHeader makeToken: k];
 
2995
  if (v == nil)
 
2996
    {
 
2997
      [params removeObjectForKey: k];
 
2998
    }
 
2999
  else
 
3000
    {
 
3001
      [params setObject: v forKey: k];
 
3002
    }
 
3003
}
 
3004
 
 
3005
/**
 
3006
 * Sets all parameters of this header ... converts names to lowercase
 
3007
 * and removes illegal characters from them.
 
3008
 */
 
3009
- (void) setParameters: (NSDictionary*)d
 
3010
{
 
3011
  NSMutableDictionary   *m = [NSMutableDictionary new];
 
3012
  NSEnumerator          *e = [d keyEnumerator];
 
3013
  NSString              *k;
 
3014
 
 
3015
  while ((k = [e nextObject]) != nil)
 
3016
    {
 
3017
      [m setObject: [d objectForKey: k] forKey: [GSMimeHeader makeToken: k]];
 
3018
    }
 
3019
  DESTROY(params);
 
3020
  params = m;
 
3021
}
 
3022
 
 
3023
/**
 
3024
 * Sets the value of this header (without changing parameters)<br />
 
3025
 * If given a nil argument, set an empty string value.
 
3026
 */
 
3027
- (void) setValue: (NSString*)s
 
3028
{
 
3029
  if (s == nil)
 
3030
    {
 
3031
      s = @"";
 
3032
    }
 
3033
  ASSIGN(value, s);
 
3034
}
 
3035
 
 
3036
/**
 
3037
 * Returns the full text of the header, built from its component parts,
 
3038
 * and including a terminating CR-LF
 
3039
 */
 
3040
- (NSString*) text
 
3041
{
 
3042
  NSString      *s = [NSString alloc];
 
3043
 
 
3044
  s = [s initWithData: [self rawMimeData] encoding: NSASCIIStringEncoding];
 
3045
  return AUTORELEASE(s);
 
3046
}
 
3047
 
 
3048
/**
 
3049
 * Returns the value of this header (excluding any parameters)
 
3050
 */
 
3051
- (NSString*) value
 
3052
{
 
3053
  return value;
 
3054
}
 
3055
@end
 
3056
 
 
3057
 
 
3058
 
 
3059
@interface GSMimeDocument (Private)
 
3060
- (unsigned) _indexOfHeaderNamed: (NSString*)name;
 
3061
@end
2165
3062
 
2166
3063
/**
2167
3064
 * <p>
2172
3069
 * </p>
2173
3070
 * <p>
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.
2178
 
 * </p>
2179
 
 * <p>
2180
 
 *   The common dictionary keys used for elements provided for
2181
 
 *   <em>all</em> headers are -
2182
 
 * </p>
2183
 
 * <deflist>
2184
 
 *   <term>RawHeader</term>
2185
 
 *   <desc>This is the unmodified text of the header
2186
 
 *   </desc>
2187
 
 *   <term>BaseName</term>
2188
 
 *   <desc>This is the header name.
2189
 
 *   </desc>
2190
 
 *   <term>BaseValue</term>
2191
 
 *   <desc>This is the text after the header name and colon.
2192
 
 *   </desc>
2193
 
 *   <term>Name</term>
2194
 
 *   <desc>This is a lowercase representation of the header name.
2195
 
 *   </desc>
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.
2201
 
 *   </desc>
2202
 
 * </deflist>
 
3072
 *   methods for modifying and examining the headers that apply to a
 
3073
 *   document.
 
3074
 * </p>
2203
3075
 */
2204
3076
@implementation GSMimeDocument
2205
3077
 
 
3078
/**
 
3079
 * Return the MIME characterset name corresponding to the
 
3080
 * specified string encoding.
 
3081
 */
 
3082
+ (NSString*) charsetFromEncoding: (NSStringEncoding)enc
 
3083
{
 
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)
 
3123
    return @"big5";
 
3124
  if (enc == NSShiftJISStringEncoding)
 
3125
    return @"shift_JIS";
 
3126
  return @"utf-8";
 
3127
}
 
3128
 
 
3129
/**
 
3130
 * Decode the source data from base64 encoding and return the result.
 
3131
 */
 
3132
+ (NSData*) decodeBase64: (NSData*)source
 
3133
{
 
3134
  int           length;
 
3135
  int           declen ;
 
3136
  const signed char     *src;
 
3137
  const signed char     *end;
 
3138
  unsigned char *result;
 
3139
  unsigned char *dst;
 
3140
  unsigned char buf[4];
 
3141
  unsigned      pos = 0;
 
3142
 
 
3143
  if (source == nil)
 
3144
    {
 
3145
      return nil;
 
3146
    }
 
3147
  length = [source length];
 
3148
  if (length == 0)
 
3149
    {
 
3150
      return [NSData data];
 
3151
    }
 
3152
  declen = ((length + 3) * 3)/4;
 
3153
  src = (const char*)[source bytes];
 
3154
  end = &src[length];
 
3155
 
 
3156
  result = (unsigned char*)NSZoneMalloc(NSDefaultMallocZone(), declen);
 
3157
  dst = result;
 
3158
 
 
3159
  while ((src != end) && *src != '\0')
 
3160
    {
 
3161
      int       c = *src++;
 
3162
 
 
3163
      if (isupper(c))
 
3164
        {
 
3165
          c -= 'A';
 
3166
        }
 
3167
      else if (islower(c))
 
3168
        {
 
3169
          c = c - 'a' + 26;
 
3170
        }
 
3171
      else if (isdigit(c))
 
3172
        {
 
3173
          c = c - '0' + 52;
 
3174
        }
 
3175
      else if (c == '/')
 
3176
        {
 
3177
          c = 63;
 
3178
        }
 
3179
      else if (c == '+')
 
3180
        {
 
3181
          c = 62;
 
3182
        }
 
3183
      else if  (c == '=')
 
3184
        {
 
3185
          c = -1;
 
3186
        }
 
3187
      else if (c == '-')
 
3188
        {
 
3189
          break;                /* end    */
 
3190
        }
 
3191
      else
 
3192
        {
 
3193
          c = -1;               /* ignore */
 
3194
        }
 
3195
 
 
3196
      if (c >= 0)
 
3197
        {
 
3198
          buf[pos++] = c;
 
3199
          if (pos == 4)
 
3200
            {
 
3201
              pos = 0;
 
3202
              decodebase64(dst, buf);
 
3203
              dst += 3;
 
3204
            }
 
3205
        }
 
3206
    }
 
3207
 
 
3208
  if (pos > 0)
 
3209
    {
 
3210
      unsigned  i;
 
3211
 
 
3212
      for (i = pos; i < 4; i++)
 
3213
        {
 
3214
          buf[i] = '\0';
 
3215
        }
 
3216
      pos--;
 
3217
      if (pos > 0)
 
3218
        {
 
3219
          unsigned char tail[3];
 
3220
          decodebase64(tail, buf);
 
3221
          memcpy(dst, tail, pos);
 
3222
          dst += pos;
 
3223
        }
 
3224
    }
 
3225
  return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
 
3226
    initWithBytesNoCopy: result length: dst - result]);
 
3227
}
 
3228
 
 
3229
/**
 
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.
 
3233
 */
 
3234
+ (NSString*) decodeBase64String: (NSString*)source
 
3235
{
 
3236
  NSData        *d = [source dataUsingEncoding: NSASCIIStringEncoding];
 
3237
  NSString      *r = nil;
 
3238
 
 
3239
  d = [self decodeBase64: d];
 
3240
  if (d != nil)
 
3241
    {
 
3242
      r = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding];
 
3243
      AUTORELEASE(r);
 
3244
    }
 
3245
  return r;
 
3246
}
 
3247
 
 
3248
/**
 
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.
 
3252
 */
 
3253
+ (GSMimeDocument*) documentWithContent: (id)newContent
 
3254
                                   type: (NSString*)type
 
3255
                                   name: (NSString*)name
 
3256
{
 
3257
  GSMimeDocument        *doc = AUTORELEASE([self new]);
 
3258
 
 
3259
  [doc setContent: newContent type: type name: name];
 
3260
  return doc;
 
3261
}
 
3262
 
 
3263
/**
 
3264
 * Encode the source data to base64 encoding and return the result.
 
3265
 */
 
3266
+ (NSData*) encodeBase64: (NSData*)source
 
3267
{
 
3268
  int           length;
 
3269
  int           destlen;
 
3270
  unsigned char *sBuf;
 
3271
  unsigned char *dBuf;
 
3272
 
 
3273
  if (source == nil)
 
3274
    {
 
3275
      return nil;
 
3276
    }
 
3277
  length = [source length];
 
3278
  if (length == 0)
 
3279
    {
 
3280
      return [NSData data];
 
3281
    }
 
3282
  destlen = 4 * ((length + 2) / 3);
 
3283
  sBuf = (unsigned char*)[source bytes];
 
3284
  dBuf = NSZoneMalloc(NSDefaultMallocZone(), destlen);
 
3285
 
 
3286
  destlen = encodebase64(dBuf, sBuf, length);
 
3287
 
 
3288
  return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
 
3289
    initWithBytesNoCopy: dBuf length: destlen]);
 
3290
}
 
3291
 
 
3292
/**
 
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.
 
3296
 */
 
3297
+ (NSString*) encodeBase64String: (NSString*)source
 
3298
{
 
3299
  NSData        *d = [source dataUsingEncoding: NSASCIIStringEncoding];
 
3300
  NSString      *r = nil;
 
3301
 
 
3302
  d = [self encodeBase64: d];
 
3303
  if (d != nil)
 
3304
    {
 
3305
      r = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding];
 
3306
      AUTORELEASE(r);
 
3307
    }
 
3308
  return r;
 
3309
}
 
3310
 
 
3311
/**
 
3312
 * Return the string encoding corresponding to the specified MIME
 
3313
 * characterset name.
 
3314
 */
 
3315
+ (NSStringEncoding) encodingFromCharset: (NSString*)charset
 
3316
{
 
3317
  if (charset == nil)
 
3318
    {
 
3319
      return NSASCIIStringEncoding;     // Default character set.
 
3320
    }
 
3321
 
 
3322
  charset = [charset lowercaseString];
 
3323
 
 
3324
  /*
 
3325
   * Try the three most popular charactersets first - for efficiency.
 
3326
   */
 
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;
 
3333
 
 
3334
  /*
 
3335
   * Now try all remaining character sets in alphabetical order.
 
3336
   */
 
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;
 
3381
 
 
3382
  return NSASCIIStringEncoding;         // Default character set.
 
3383
}
 
3384
 
2206
3385
+ (void) initialize
2207
3386
{
2208
3387
  if (self == [GSMimeDocument class])
2211
3390
 
2212
3391
      [m formUnionWithCharacterSet:
2213
3392
        [NSCharacterSet characterSetWithCharactersInString:
2214
 
        @"()<>@,;:/[]?=\"\\"]];
 
3393
        @".()<>@,;:[]\"\\"]];
2215
3394
      [m formUnionWithCharacterSet:
2216
3395
        [NSCharacterSet whitespaceAndNewlineCharacterSet]];
2217
3396
      [m formUnionWithCharacterSet:
2218
3397
        [NSCharacterSet controlCharacterSet]];
2219
3398
      [m formUnionWithCharacterSet:
2220
3399
        [NSCharacterSet illegalCharacterSet]];
2221
 
      specials = [m copy];
 
3400
      rfc822Specials = [m copy];
 
3401
      [m formUnionWithCharacterSet:
 
3402
        [NSCharacterSet characterSetWithCharactersInString:
 
3403
        @"/?="]];
 
3404
      [m removeCharactersInString: @"."];
 
3405
      rfc2045Specials = [m copy];
 
3406
      whitespace = RETAIN([NSCharacterSet whitespaceAndNewlineCharacterSet]);
 
3407
      if (NSArrayClass == 0)
 
3408
        {
 
3409
          NSArrayClass = [NSArray class];
 
3410
        }
2222
3411
    }
2223
3412
}
2224
3413
 
2225
3414
/**
2226
 
 * Create an empty MIME document.
 
3415
 * Adds a part to a multipart document
2227
3416
 */
2228
 
+ (GSMimeDocument*) mimeDocument
 
3417
- (void) addContent: (id)newContent
2229
3418
{
2230
 
  return AUTORELEASE([[self alloc] init]);
 
3419
  if ([newContent isKindOfClass: [GSMimeDocument class]] == NO)
 
3420
    {
 
3421
      [NSException raise: NSInvalidArgumentException
 
3422
                  format: @"Content to add is not a GSMimeDocument"];
 
3423
    }
 
3424
  if (content == nil)
 
3425
    {
 
3426
      content = [NSMutableArray new];
 
3427
    }
 
3428
  if ([content isKindOfClass: [NSMutableArray class]] == YES)
 
3429
    {
 
3430
      [content addObject: newContent];
 
3431
    }
 
3432
  else
 
3433
    {
 
3434
      [NSException raise: NSInvalidArgumentException
 
3435
                  format: @"[%@ -%@] passed bad content",
 
3436
        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
 
3437
    }
2231
3438
}
2232
3439
 
2233
3440
/**
2236
3443
 *   The header must be a mutable dictionary object that contains
2237
3444
 *   at least the fields that are standard for all headers.
2238
3445
 * </p>
2239
 
 */
2240
 
- (BOOL) addHeader: (NSDictionary*)info
2241
 
{
2242
 
  NSString      *name = [info objectForKey: @"Name"];
2243
 
 
2244
 
  if (name == nil)
2245
 
    {
2246
 
      NSLog(@"addHeader: supplied with header info without 'Name' field");
2247
 
      return NO;
2248
 
    }
2249
 
 
2250
 
  info = [info copy];
2251
 
  [headers addObject: info];
2252
 
  RELEASE(info);
2253
 
  return YES;
2254
 
}
2255
 
 
2256
 
/**
2257
 
 * <p>
2258
 
 *   This method returns an array containing NSDictionary objects
 
3446
 * <p>
 
3447
 *   Certain well-known headers are restricted to one occurrance in
 
3448
 *   an email, and when extra copies are added they replace originals.
 
3449
 * </p>
 
3450
 * <p>
 
3451
 *  The mime-version header is special ... it is inserted before any
 
3452
 *  other mime headers rather than being added at the end.
 
3453
 * </p>
 
3454
 */
 
3455
- (void) addHeader: (GSMimeHeader*)info
 
3456
{
 
3457
  NSString      *name = [info name];
 
3458
 
 
3459
  if (name == nil || [name isEqualToString: @"unknown"] == YES)
 
3460
    {
 
3461
      [NSException raise: NSInvalidArgumentException
 
3462
                  format: @"[%@ -%@] header with invalid name",
 
3463
        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
 
3464
    }
 
3465
  if ([name isEqualToString: @"mime-version"] == YES
 
3466
    || [name isEqualToString: @"content-disposition"] == YES
 
3467
    || [name isEqualToString: @"content-transfer-encoding"] == YES
 
3468
    || [name isEqualToString: @"content-type"] == YES
 
3469
    || [name isEqualToString: @"subject"] == YES)
 
3470
    {
 
3471
      unsigned  index = [self _indexOfHeaderNamed: name];
 
3472
 
 
3473
      if (index != NSNotFound)
 
3474
        {
 
3475
          [headers replaceObjectAtIndex: index withObject: info];
 
3476
        }
 
3477
      else if ([name isEqualToString: @"mime-version"] == YES)
 
3478
        {
 
3479
          unsigned      tmp;
 
3480
 
 
3481
          index = [headers count];
 
3482
          tmp = [self _indexOfHeaderNamed: @"content-disposition"];
 
3483
          if (tmp != NSNotFound && tmp < index)
 
3484
            {
 
3485
              index = tmp;
 
3486
            }
 
3487
          tmp = [self _indexOfHeaderNamed: @"content-transfer-encoding"];
 
3488
          if (tmp != NSNotFound && tmp < index)
 
3489
            {
 
3490
              index = tmp;
 
3491
            }
 
3492
          tmp = [self _indexOfHeaderNamed: @"content-type"];
 
3493
          if (tmp != NSNotFound && tmp < index)
 
3494
            {
 
3495
              index = tmp;
 
3496
            }
 
3497
          [headers insertObject: info atIndex: index];
 
3498
        }
 
3499
      else
 
3500
        {
 
3501
          [headers addObject: info];
 
3502
        }
 
3503
    }
 
3504
  else
 
3505
    {
 
3506
      [headers addObject: info];
 
3507
    }
 
3508
}
 
3509
 
 
3510
/**
 
3511
 * Convenience method to create a new header and add it to the receiver.<br />
 
3512
 * Returns the newly created header.<br />
 
3513
 * See [GSMimeHeader-initWithName:value:parameters:] and -addHeader: methods.
 
3514
 */
 
3515
- (GSMimeHeader*) addHeader: (NSString*)name
 
3516
                      value: (NSString*)value
 
3517
                 parameters: (NSDictionary*)parameters
 
3518
{
 
3519
  GSMimeHeader  *hdr;
 
3520
 
 
3521
  hdr = [[GSMimeHeader alloc] initWithName: name
 
3522
                                     value: value
 
3523
                                parameters: parameters];
 
3524
  [self addHeader: hdr];
 
3525
  RELEASE(hdr);
 
3526
  return hdr;
 
3527
}
 
3528
 
 
3529
/**
 
3530
 * <p>
 
3531
 *   This method returns an array containing GSMimeHeader objects
2259
3532
 *   representing the headers associated with the document.
2260
3533
 * </p>
2261
3534
 * <p>
2269
3542
}
2270
3543
 
2271
3544
/**
2272
 
 * This returns the content data of the document in the
2273
 
 * appropriate format for the type of data -
 
3545
 * This returns the content data of the document in the same format in
 
3546
 * which the data was placed in the document.  This may be one of -
2274
3547
 * <deflist>
2275
3548
 *   <term>text</term>
2276
3549
 *   <desc>an NSString object</desc>
2279
3552
 *   <term>multipart</term>
2280
3553
 *   <desc>an NSArray object containing GSMimeDocument objects</desc>
2281
3554
 * </deflist>
 
3555
 * If you want to be sure that you get a particular type of data, use the
 
3556
 * -convertToData or -convertToText method.
2282
3557
 */
2283
3558
- (id) content
2284
3559
{
2285
3560
  return content;
2286
3561
}
2287
3562
 
 
3563
/**
 
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.
 
3568
 */
 
3569
- (id) contentByID: (NSString*)key
 
3570
{
 
3571
  if ([key hasPrefix: @"<"] == NO)
 
3572
    {
 
3573
      key = [NSString stringWithFormat: @"<%@>", key];
 
3574
    }
 
3575
  if ([content isKindOfClass: NSArrayClass] == YES)
 
3576
    {
 
3577
      NSEnumerator      *e = [content objectEnumerator];
 
3578
      GSMimeDocument    *d;
 
3579
 
 
3580
      while ((d = [e nextObject]) != nil)
 
3581
        {
 
3582
          if ([[d contentID] isEqualToString: key] == YES)
 
3583
            {
 
3584
              return d;
 
3585
            }
 
3586
          d = [d contentByID: key];
 
3587
          if (d != nil)
 
3588
            {
 
3589
              return d;
 
3590
            }
 
3591
        }
 
3592
    }
 
3593
  return nil;
 
3594
}
 
3595
 
 
3596
/**
 
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.
 
3601
 */
 
3602
- (id) contentByLocation: (NSString*)key
 
3603
{
 
3604
  if ([content isKindOfClass: NSArrayClass] == YES)
 
3605
    {
 
3606
      NSEnumerator      *e = [content objectEnumerator];
 
3607
      GSMimeDocument    *d;
 
3608
 
 
3609
      while ((d = [e nextObject]) != nil)
 
3610
        {
 
3611
          if ([[d contentLocation] isEqualToString: key] == YES)
 
3612
            {
 
3613
              return d;
 
3614
            }
 
3615
          d = [d contentByLocation: key];
 
3616
          if (d != nil)
 
3617
            {
 
3618
              return d;
 
3619
            }
 
3620
        }
 
3621
    }
 
3622
  return nil;
 
3623
}
 
3624
 
 
3625
/**
 
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.
 
3630
 */
 
3631
- (id) contentByName: (NSString*)key
 
3632
{
 
3633
 
 
3634
  if ([content isKindOfClass: NSArrayClass] == YES)
 
3635
    {
 
3636
      NSEnumerator      *e = [content objectEnumerator];
 
3637
      GSMimeDocument    *d;
 
3638
 
 
3639
      while ((d = [e nextObject]) != nil)
 
3640
        {
 
3641
          GSMimeHeader  *hdr;
 
3642
 
 
3643
          hdr = [d headerNamed: @"content-type"];
 
3644
          if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
 
3645
            {
 
3646
              return d;
 
3647
            }
 
3648
          hdr = [d headerNamed: @"content-disposition"];
 
3649
          if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
 
3650
            {
 
3651
              return d;
 
3652
            }
 
3653
          d = [d contentByName: key];
 
3654
          if (d != nil)
 
3655
            {
 
3656
              return d;
 
3657
            }
 
3658
        }
 
3659
    }
 
3660
  return nil;
 
3661
}
 
3662
 
 
3663
/**
 
3664
 * Convenience method to fetch the content file name from the header.
 
3665
 */
 
3666
- (NSString*) contentFile
 
3667
{
 
3668
  GSMimeHeader  *hdr = [self headerNamed: @"content-disposition"];
 
3669
 
 
3670
  return [hdr parameterForKey: @"filename"];
 
3671
}
 
3672
 
 
3673
/**
 
3674
 * Convenience method to fetch the content ID from the header.
 
3675
 */
 
3676
- (NSString*) contentID
 
3677
{
 
3678
  GSMimeHeader  *hdr = [self headerNamed: @"content-id"];
 
3679
 
 
3680
  return [hdr value];
 
3681
}
 
3682
 
 
3683
/**
 
3684
 * Convenience method to fetch the content location from the header.
 
3685
 */
 
3686
- (NSString*) contentLocation
 
3687
{
 
3688
  GSMimeHeader  *hdr = [self headerNamed: @"content-location"];
 
3689
 
 
3690
  return [hdr value];
 
3691
}
 
3692
 
 
3693
/**
 
3694
 * Convenience method to fetch the content name from the header.
 
3695
 */
 
3696
- (NSString*) contentName
 
3697
{
 
3698
  GSMimeHeader  *hdr = [self headerNamed: @"content-type"];
 
3699
 
 
3700
  return [hdr parameterForKey: @"name"];
 
3701
}
 
3702
 
 
3703
/**
 
3704
 * Convenience method to fetch the content sub-type from the header.
 
3705
 */
 
3706
- (NSString*) contentSubtype
 
3707
{
 
3708
  GSMimeHeader  *hdr = [self headerNamed: @"content-type"];
 
3709
  NSString      *val = nil;
 
3710
 
 
3711
  if (hdr != nil)
 
3712
    {
 
3713
      val = [hdr objectForKey: @"Subtype"];
 
3714
      if (val == nil)
 
3715
        {
 
3716
          val = [hdr value];
 
3717
          if (val != nil)
 
3718
            {
 
3719
              NSRange   r;
 
3720
 
 
3721
              r = [val rangeOfString: @"/"];
 
3722
              if (r.length > 0)
 
3723
                {
 
3724
                  val = [val substringFromIndex: r.location + 1];
 
3725
                  r = [val rangeOfString: @"/"];
 
3726
                  if (r.length > 0)
 
3727
                    {
 
3728
                      val = [val substringToIndex: r.location];
 
3729
                    }
 
3730
                  val = [val stringByTrimmingSpaces];
 
3731
                  [hdr setObject: val forKey: @"Subtype"];
 
3732
                }
 
3733
              else
 
3734
                {
 
3735
                  val = nil;
 
3736
                }
 
3737
            }
 
3738
        }
 
3739
    }
 
3740
 
 
3741
  return val;
 
3742
}
 
3743
 
 
3744
/**
 
3745
 * Convenience method to fetch the content type from the header.
 
3746
 */
 
3747
- (NSString*) contentType
 
3748
{
 
3749
  GSMimeHeader  *hdr = [self headerNamed: @"content-type"];
 
3750
  NSString      *val = nil;
 
3751
 
 
3752
  if (hdr != nil)
 
3753
    {
 
3754
      val = [hdr objectForKey: @"Type"];
 
3755
      if (val == nil)
 
3756
        {
 
3757
          val = [hdr value];
 
3758
          if (val != nil)
 
3759
            {
 
3760
              NSRange   r;
 
3761
 
 
3762
              r = [val rangeOfString: @"/"];
 
3763
              if (r.length > 0)
 
3764
                {
 
3765
                  val = [val substringToIndex: r.location];
 
3766
                  val = [val stringByTrimmingSpaces];
 
3767
                }
 
3768
              [hdr setObject: val forKey: @"Type"];
 
3769
            }
 
3770
        }
 
3771
    }
 
3772
 
 
3773
  return val;
 
3774
}
 
3775
 
 
3776
/**
 
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.
 
3782
 */
 
3783
- (NSArray*) contentsByName: (NSString*)key
 
3784
{
 
3785
  NSMutableArray        *a = nil;
 
3786
 
 
3787
  if ([content isKindOfClass: NSArrayClass] == YES)
 
3788
    {
 
3789
      NSEnumerator      *e = [content objectEnumerator];
 
3790
      GSMimeDocument    *d;
 
3791
 
 
3792
      while ((d = [e nextObject]) != nil)
 
3793
        {
 
3794
          GSMimeHeader  *hdr;
 
3795
          BOOL          match = YES;
 
3796
 
 
3797
          hdr = [d headerNamed: @"content-type"];
 
3798
          if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
 
3799
            {
 
3800
              hdr = [d headerNamed: @"content-disposition"];
 
3801
              if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
 
3802
                {
 
3803
                  match = NO;
 
3804
                }
 
3805
            }
 
3806
          if (match == YES)
 
3807
            {
 
3808
              if (a == nil)
 
3809
                {
 
3810
                  a = [NSMutableArray arrayWithCapacity: 4];
 
3811
                }
 
3812
              [a addObject: d];
 
3813
            }
 
3814
        }
 
3815
    }
 
3816
  return a;
 
3817
}
 
3818
 
 
3819
/**
 
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.
 
3826
 */
 
3827
- (NSData*) convertToData
 
3828
{
 
3829
  NSData        *d = nil;
 
3830
 
 
3831
  if ([content isKindOfClass: [NSString class]] == YES)
 
3832
    {
 
3833
      GSMimeHeader      *hdr = [self headerNamed: @"content-type"];
 
3834
      NSString          *charset = [hdr parameterForKey: @"charset"];
 
3835
      NSStringEncoding  enc;
 
3836
 
 
3837
      enc = [GSMimeDocument encodingFromCharset: charset];
 
3838
      d = [content dataUsingEncoding: enc];
 
3839
      if (d == nil)
 
3840
        {
 
3841
          charset = selectCharacterSet(content, &d);
 
3842
          [hdr setParameter: charset forKey: @"charset"];
 
3843
        }
 
3844
    }
 
3845
  else if ([content isKindOfClass: [NSData class]] == YES)
 
3846
    {
 
3847
      d = content;
 
3848
    }
 
3849
  return d;
 
3850
}
 
3851
 
 
3852
/**
 
3853
 * Return the content as an NSString object (unless it is multipart)
 
3854
 * If the content cannot be represented as text, this returns nil.
 
3855
 */
 
3856
- (NSString*) convertToText
 
3857
{
 
3858
  NSString      *s = nil;
 
3859
 
 
3860
  if ([content isKindOfClass: [NSString class]] == YES)
 
3861
    {
 
3862
      s = content;
 
3863
    }
 
3864
  else if ([content isKindOfClass: [NSData class]] == YES)
 
3865
    {
 
3866
      GSMimeHeader      *hdr = [self headerNamed: @"content-type"];
 
3867
      NSString          *charset = [hdr parameterForKey: @"charset"];
 
3868
      NSStringEncoding  enc;
 
3869
 
 
3870
      enc = [GSMimeDocument encodingFromCharset: charset];
 
3871
      s = [[NSString alloc] initWithData: content encoding: enc];
 
3872
      AUTORELEASE(s);
 
3873
    }
 
3874
  return s;
 
3875
}
 
3876
 
 
3877
/**
 
3878
 * Returns a copy of the receiver.
 
3879
 */
2288
3880
- (id) copyWithZone: (NSZone*)z
2289
3881
{
2290
 
  return RETAIN(self);
 
3882
  GSMimeDocument        *c = [GSMimeDocument allocWithZone: z];
 
3883
 
 
3884
  c->headers = [[NSMutableArray allocWithZone: z] initWithArray: headers
 
3885
                                                      copyItems: YES];
 
3886
 
 
3887
  if ([content isKindOfClass: NSArrayClass] == YES)
 
3888
    {
 
3889
      c->content = [[NSMutableArray allocWithZone: z] initWithArray: content
 
3890
                                                          copyItems: YES];
 
3891
    }
 
3892
  else
 
3893
    {
 
3894
      c->content = [content copyWithZone: z];
 
3895
    }
 
3896
  return c;
2291
3897
}
2292
3898
 
2293
3899
- (void) dealloc
2298
3904
}
2299
3905
 
2300
3906
/**
2301
 
 * This method removes all occurrances of headers whose raw data
2302
 
 * exactly matches the supplied string.
2303
 
 */
2304
 
- (void) deleteHeader: (NSString*)aHeader
 
3907
 * Deletes all ocurrances of parts identical to aPart from the receiver.<br />
 
3908
 * Recursively deletes from enclosed documents as necessary.
 
3909
 */
 
3910
- (void) deleteContent: (GSMimeDocument*)aPart
 
3911
{
 
3912
  if (aPart != nil)
 
3913
    {
 
3914
      if ([content isKindOfClass: [NSMutableArray class]] == YES)
 
3915
        {
 
3916
          unsigned      count = [content count];
 
3917
 
 
3918
          while (count-- > 0)
 
3919
            {
 
3920
              GSMimeDocument    *part = [content objectAtIndex: count];
 
3921
 
 
3922
              if (part == aPart)
 
3923
                {
 
3924
                  [content removeObjectAtIndex: count];
 
3925
                }
 
3926
              else
 
3927
                {
 
3928
                  [part deleteContent: part];   // Recursive.
 
3929
                }
 
3930
            }
 
3931
        }
 
3932
    }
 
3933
}
 
3934
 
 
3935
/**
 
3936
 * This method removes all occurrances of header objects identical to
 
3937
 * the one supplied as an argument.
 
3938
 */
 
3939
- (void) deleteHeader: (GSMimeHeader*)aHeader
2305
3940
{
2306
3941
  unsigned      count = [headers count];
2307
3942
 
2308
3943
  while (count-- > 0)
2309
3944
    {
2310
 
      NSDictionary      *info = [headers objectAtIndex: count];
2311
 
 
2312
 
      if ([aHeader isEqualToString: [info objectForKey: @"RawHeader"]] == YES)
 
3945
      if ([aHeader isEqual: [headers objectAtIndex: count]] == YES)
2313
3946
        {
2314
3947
          [headers removeObjectAtIndex: count];
2315
3948
        }
2318
3951
 
2319
3952
/**
2320
3953
 * This method removes all occurrances of headers whose name
2321
 
 * exactly matches the supplied string.
 
3954
 * matches the supplied string.
2322
3955
 */
2323
3956
- (void) deleteHeaderNamed: (NSString*)name
2324
3957
{
2327
3960
  name = [name lowercaseString];
2328
3961
  while (count-- > 0)
2329
3962
    {
2330
 
      NSDictionary      *info = [headers objectAtIndex: count];
 
3963
      GSMimeHeader      *info = [headers objectAtIndex: count];
2331
3964
 
2332
 
      if ([name isEqualToString: [info objectForKey: @"Name"]] == YES)
 
3965
      if ([name isEqualToString: [info name]] == YES)
2333
3966
        {
2334
3967
          [headers removeObjectAtIndex: count];
2335
3968
        }
2349
3982
}
2350
3983
 
2351
3984
/**
2352
 
 * This method returns the info dictionary for the first header
2353
 
 * whose name equals the supplied argument.
 
3985
 * This method returns the first header whose name equals the supplied argument.
2354
3986
 */
2355
 
- (NSDictionary*) headerNamed: (NSString*)name
 
3987
- (GSMimeHeader*) headerNamed: (NSString*)name
2356
3988
{
2357
 
  unsigned              count = [headers count];
2358
 
  unsigned              index;
 
3989
  NSArray       *a = [self headersNamed: name];
2359
3990
 
2360
 
  name = [name lowercaseString];
2361
 
  for (index = 0; index < count; index++)
 
3991
  if ([a count] > 0)
2362
3992
    {
2363
 
      NSDictionary      *info = [headers objectAtIndex: index];
2364
 
      NSString          *other = [info objectForKey: @"Name"];
2365
 
 
2366
 
      if ([name isEqualToString: other] == YES)
2367
 
        {
2368
 
          return info;
2369
 
        }
2370
 
    } 
 
3993
      return [a objectAtIndex: 0];
 
3994
    }
2371
3995
  return nil;
2372
3996
}
2373
3997
 
2374
3998
/**
2375
 
 * This method returns an array of info dictionaries for all headers
 
3999
 * This method returns an array of GSMimeHeader objects for all headers
2376
4000
 * whose names equal the supplied argument.
2377
4001
 */
2378
4002
- (NSArray*) headersNamed: (NSString*)name
2381
4005
  unsigned              index;
2382
4006
  NSMutableArray        *array;
2383
4007
 
2384
 
  name = [name lowercaseString];
 
4008
  name = [GSMimeHeader makeToken: name];
2385
4009
  array = [NSMutableArray array];
2386
4010
  for (index = 0; index < count; index++)
2387
4011
    {
2388
 
      NSDictionary      *info = [headers objectAtIndex: index];
2389
 
      NSString          *other = [info objectForKey: @"Name"];
 
4012
      GSMimeHeader      *info = [headers objectAtIndex: index];
2390
4013
 
2391
 
      if ([name isEqualToString: other] == YES)
 
4014
      if ([name isEqualToString: [info name]] == YES)
2392
4015
        {
2393
4016
          [array addObject: info];
2394
4017
        }
2395
 
    } 
 
4018
    }
2396
4019
  return array;
2397
4020
}
2398
4021
 
2406
4029
}
2407
4030
 
2408
4031
/**
 
4032
 * <p>Make a probably unique string suitable for use as the
 
4033
 * boundary parameter in the content of a multipart document.
 
4034
 * </p>
 
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.
 
4040
 * </p>
 
4041
 */
 
4042
- (NSString*) makeBoundary
 
4043
{
 
4044
  static int            count = 0;
 
4045
  unsigned char         output[20];
 
4046
  NSMutableData         *md;
 
4047
  NSString              *result;
 
4048
  NSData                *source;
 
4049
  NSData                *digest;
 
4050
  int                   sequence = ++count;
 
4051
 
 
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;
 
4060
 
 
4061
  md = [[NSMutableData alloc] initWithLength: 40];
 
4062
  [md setLength: encodebase64([md mutableBytes], output, 20)];
 
4063
  result = [[NSString alloc] initWithData: md encoding: NSASCIIStringEncoding];
 
4064
  RELEASE(md);
 
4065
  return AUTORELEASE(result);
 
4066
}
 
4067
 
 
4068
/**
 
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.
 
4073
 */
 
4074
- (GSMimeHeader*) makeContentID
 
4075
{
 
4076
  GSMimeHeader  *hdr;
 
4077
  NSString      *str = [[NSProcessInfo processInfo] globallyUniqueString];
 
4078
 
 
4079
  str = [NSString stringWithFormat: @"<%@>", str];
 
4080
  hdr = [[GSMimeHeader alloc] initWithName: @"content-id"
 
4081
                                     value: str
 
4082
                                parameters: nil];
 
4083
  [self setHeader: hdr];
 
4084
  RELEASE(hdr);
 
4085
  return hdr;
 
4086
}
 
4087
 
 
4088
/**
 
4089
 * Deprecated ... use -setHeader:value:parameters:
 
4090
 */
 
4091
- (GSMimeHeader*) makeHeader: (NSString*)name
 
4092
                       value: (NSString*)value
 
4093
                  parameters: (NSDictionary*)parameters
 
4094
{
 
4095
  GSMimeHeader  *hdr;
 
4096
 
 
4097
  hdr = [[GSMimeHeader alloc] initWithName: name
 
4098
                                     value: value
 
4099
                                parameters: parameters];
 
4100
  [self setHeader: hdr];
 
4101
  RELEASE(hdr);
 
4102
  return hdr;
 
4103
}
 
4104
 
 
4105
/**
 
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.
 
4110
 */
 
4111
- (GSMimeHeader*) makeMessageID
 
4112
{
 
4113
  GSMimeHeader  *hdr;
 
4114
  NSString      *str = [[NSProcessInfo processInfo] globallyUniqueString];
 
4115
 
 
4116
  str = [NSString stringWithFormat: @"<%@>", str];
 
4117
  hdr = [[GSMimeHeader alloc] initWithName: @"message-id"
 
4118
                                     value: str
 
4119
                                parameters: nil];
 
4120
  [self setHeader: hdr];
 
4121
  RELEASE(hdr);
 
4122
  return hdr;
 
4123
}
 
4124
 
 
4125
/**
 
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.
 
4129
 */
 
4130
- (NSMutableData*) rawMimeData
 
4131
{
 
4132
  return [self rawMimeData: YES];
 
4133
}
 
4134
 
 
4135
/**
 
4136
 * <p>Return an NSData object representing the MIME document as raw data
 
4137
 * ready to be sent via an email system.
 
4138
 * </p>
 
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.
 
4141
 * </p>
 
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.
 
4148
 * </p>
 
4149
 */
 
4150
- (NSMutableData*) rawMimeData: (BOOL)isOuter
 
4151
{
 
4152
  NSMutableArray        *partData = nil;
 
4153
  NSMutableData         *md = [NSMutableData dataWithCapacity: 1024];
 
4154
  NSData        *d = nil;
 
4155
  NSEnumerator  *enumerator;
 
4156
  GSMimeHeader  *type;
 
4157
  GSMimeHeader  *enc;
 
4158
  GSMimeHeader  *hdr;
 
4159
  NSData        *boundary = 0;
 
4160
  BOOL          contentIsBinary = NO;
 
4161
  BOOL          contentIs7bit = YES;
 
4162
  unsigned int  count;
 
4163
  unsigned int  i;
 
4164
  CREATE_AUTORELEASE_POOL(arp);
 
4165
 
 
4166
  if (isOuter == YES)
 
4167
    {
 
4168
      /*
 
4169
       * Ensure there is a mime version header.
 
4170
       */
 
4171
      hdr = [self headerNamed: @"mime-version"];
 
4172
      if (hdr == nil)
 
4173
        {
 
4174
          hdr = [GSMimeHeader alloc];
 
4175
          hdr = [hdr initWithName: @"mime-version"
 
4176
                            value: @"1.0"
 
4177
                       parameters: nil];
 
4178
          [self addHeader: hdr];
 
4179
          RELEASE(hdr);
 
4180
        }
 
4181
    }
 
4182
  else
 
4183
    {
 
4184
      /*
 
4185
       * Inner documents should not contain the mime version header.
 
4186
       */
 
4187
      hdr = [self headerNamed: @"mime-version"];
 
4188
      if (hdr != nil)
 
4189
        {
 
4190
          [self deleteHeader: hdr];
 
4191
        }
 
4192
    }
 
4193
 
 
4194
  if ([content isKindOfClass: NSArrayClass] == YES)
 
4195
    {
 
4196
      count = [content count];
 
4197
      partData = [NSMutableArray arrayWithCapacity: count];
 
4198
      for (i = 0; i < count; i++)
 
4199
        {
 
4200
          GSMimeDocument        *part = [content objectAtIndex: i];
 
4201
 
 
4202
          [partData addObject: [part rawMimeData: NO]];
 
4203
 
 
4204
          /*
 
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.
 
4210
           */
 
4211
          if (contentIs7bit == YES)
 
4212
            {
 
4213
              NSString          *v;
 
4214
 
 
4215
              enc = [part headerNamed: @"content-transfer-encoding"];
 
4216
              v = [enc value];
 
4217
              if ([v isEqualToString: @"8bit"] == YES
 
4218
                || [v isEqualToString: @"binary"] == YES)
 
4219
                {
 
4220
                  contentIs7bit = NO;
 
4221
                  if ([v isEqualToString: @"binary"] == YES)
 
4222
                    {
 
4223
                      contentIsBinary = YES;
 
4224
                    }
 
4225
                }
 
4226
            }
 
4227
        }
 
4228
    }
 
4229
 
 
4230
  type = [self headerNamed: @"content-type"];
 
4231
  if (type == nil)
 
4232
    {
 
4233
      /*
 
4234
       * Attempt to infer the content type from the content.
 
4235
       */
 
4236
      if (partData != nil)
 
4237
        {
 
4238
          [self setContent: content type: @"multipart/mixed" name: nil];
 
4239
        }
 
4240
      else if ([content isKindOfClass: [NSString class]] == YES)
 
4241
        {
 
4242
          [self setContent: content type: @"text/plain" name: nil];
 
4243
        }
 
4244
      else if ([content isKindOfClass: [NSData class]] == YES)
 
4245
        {
 
4246
          [self setContent: content
 
4247
                      type: @"application/octet-stream"
 
4248
                      name: nil];
 
4249
        }
 
4250
      else
 
4251
        {
 
4252
          [NSException raise: NSInternalInconsistencyException
 
4253
                      format: @"[%@ -%@] with bad content",
 
4254
            NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
 
4255
        }
 
4256
      type = [self headerNamed: @"content-type"];
 
4257
    }
 
4258
 
 
4259
  if (partData != nil)
 
4260
    {
 
4261
      NSString  *v;
 
4262
      BOOL      shouldSet;
 
4263
 
 
4264
      enc = [self headerNamed: @"content-transfer-encoding"];
 
4265
      v = [enc value];
 
4266
      if ([v isEqualToString: @"binary"])
 
4267
        {
 
4268
          /*
 
4269
           * For binary encoding, we can just accept the setting.
 
4270
           */
 
4271
          shouldSet = NO;
 
4272
        }
 
4273
      else if ([v isEqualToString: @"8bit"])
 
4274
        {
 
4275
          if (contentIsBinary == YES)
 
4276
            {
 
4277
              shouldSet = YES;  // Need to promote from 8bit to binary
 
4278
            }
 
4279
          else
 
4280
            {
 
4281
              shouldSet = NO;
 
4282
            }
 
4283
        }
 
4284
      else if (v == nil || [v isEqualToString: @"7bit"] == YES)
 
4285
        {
 
4286
          /*
 
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.
 
4290
           */
 
4291
          if (contentIs7bit == YES)
 
4292
            {
 
4293
              shouldSet = NO;
 
4294
            }
 
4295
          else
 
4296
            {
 
4297
              shouldSet = YES;
 
4298
            }
 
4299
        }
 
4300
      else
 
4301
        {
 
4302
          /*
 
4303
           * A multipart document can't have any other encoding, so we need
 
4304
           * to fix it.
 
4305
           */
 
4306
          shouldSet = YES;
 
4307
        }
 
4308
 
 
4309
      if (shouldSet == YES)
 
4310
        {
 
4311
          NSString      *encoding;
 
4312
 
 
4313
          /*
 
4314
           * Force a change to the current transfer encoding setting.
 
4315
           */
 
4316
          if (contentIs7bit == YES)
 
4317
            {
 
4318
              encoding = @"7bit";
 
4319
            }
 
4320
          else if (contentIsBinary == YES)
 
4321
            {
 
4322
              encoding = @"binary";
 
4323
            }
 
4324
          else
 
4325
            {
 
4326
              encoding = @"8bit";
 
4327
            }
 
4328
          if (enc == nil)
 
4329
            {
 
4330
              enc = [GSMimeHeader alloc];
 
4331
              enc = [enc initWithName: @"content-transfer-encoding"
 
4332
                                value: encoding
 
4333
                           parameters: nil];
 
4334
              [self setHeader: enc];
 
4335
              RELEASE(enc);
 
4336
            }
 
4337
          else
 
4338
            {
 
4339
              [enc setValue: encoding];
 
4340
            }
 
4341
        }
 
4342
 
 
4343
      v = [type parameterForKey: @"boundary"];
 
4344
      if (v == nil)
 
4345
        {
 
4346
          v = [self makeBoundary];
 
4347
          [type setParameter: v forKey: @"boundary"];
 
4348
        }
 
4349
      boundary = [v dataUsingEncoding: NSASCIIStringEncoding];
 
4350
 
 
4351
      v = [type objectForKey: @"Subtype"];
 
4352
      if ([v isEqualToString: @"related"] == YES)
 
4353
        {
 
4354
          GSMimeDocument        *start;
 
4355
 
 
4356
          v = [type parameterForKey: @"start"];
 
4357
          if (v == nil)
 
4358
            {
 
4359
              start = [content objectAtIndex: 0];
 
4360
#if 0
 
4361
              /*
 
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?
 
4365
               */
 
4366
              v = [start contentID];
 
4367
              if (v == nil)
 
4368
                {
 
4369
                  hdr = [start makeContentID];
 
4370
                  v = [hdr value];
 
4371
                }
 
4372
              [type setParameter: v forKey: @"start"];
 
4373
#endif
 
4374
            }
 
4375
          else
 
4376
            {
 
4377
              start = [self contentByID: v];
 
4378
            }
 
4379
          hdr = [start headerNamed: @"content-type"];
 
4380
          v = [hdr value];
 
4381
          /*
 
4382
           * If there is no 'type' parameter, we can fill it in automatically.
 
4383
           */
 
4384
          if ([type parameterForKey: @"type"] == nil)
 
4385
            {
 
4386
              [type setParameter: v forKey: @"type"];
 
4387
            }
 
4388
          if ([v isEqual: [type parameterForKey: @"type"]] == NO)
 
4389
            {
 
4390
              [NSException raise: NSInvalidArgumentException
 
4391
                format: @"multipart/related 'type' (%@) does not match "
 
4392
                @"that of the 'start' part (%@)",
 
4393
                [type parameterForKey: @"type"], v];
 
4394
            }
 
4395
        }
 
4396
    }
 
4397
  else
 
4398
    {
 
4399
      NSString  *encoding;
 
4400
 
 
4401
      d = [self convertToData];
 
4402
      enc = [self headerNamed: @"content-transfer-encoding"];
 
4403
      encoding = [enc value];
 
4404
      if (encoding == nil)
 
4405
        {
 
4406
          if ([[type objectForKey: @"Type"] isEqualToString: @"text"] == YES)
 
4407
            {
 
4408
              NSString          *charset = [type parameterForKey: @"charset"];
 
4409
 
 
4410
              if (charset != nil
 
4411
                && [charset isEqualToString: @"ascii"] == NO
 
4412
                && [charset isEqualToString: @"us-ascii"] == NO)
 
4413
                {
 
4414
                  encoding = @"8bit";
 
4415
                  enc = [GSMimeHeader alloc];
 
4416
                  enc = [enc initWithName: @"content-transfer-encoding"
 
4417
                                    value: encoding
 
4418
                               parameters: nil];
 
4419
                  [self addHeader: enc];
 
4420
                  RELEASE(enc);
 
4421
                }
 
4422
            }
 
4423
          else
 
4424
            {
 
4425
              enc = [GSMimeHeader alloc];
 
4426
              enc = [enc initWithName: @"content-transfer-encoding"
 
4427
                                value: @"base64"
 
4428
                           parameters: nil];
 
4429
              [self addHeader: enc];
 
4430
              RELEASE(enc);
 
4431
            }
 
4432
        }
 
4433
 
 
4434
      if (encoding == nil
 
4435
        || [encoding isEqualToString: @"7bit"] == YES
 
4436
        || [encoding isEqualToString: @"8bit"] == YES)
 
4437
        {
 
4438
          unsigned char *bytes = (unsigned char*)[d bytes];
 
4439
          unsigned      length = [d length];
 
4440
          BOOL          hadCarriageReturn = NO;
 
4441
          unsigned      lineLength = 0;
 
4442
          unsigned      i;
 
4443
 
 
4444
          for (i = 0; i < length; i++)
 
4445
            {
 
4446
              unsigned char     c = bytes[i];
 
4447
 
 
4448
              if (hadCarriageReturn == YES)
 
4449
                {
 
4450
                  if (c != '\n')
 
4451
                    {
 
4452
                      encoding = @"binary";     // CR not part of CRLF
 
4453
                      break;
 
4454
                    }
 
4455
                  hadCarriageReturn = NO;
 
4456
                  lineLength = 0;
 
4457
                }
 
4458
              else if (c == '\n')
 
4459
                {
 
4460
                  encoding = @"binary";         // LF not part of CRLF
 
4461
                  break;
 
4462
                }
 
4463
              else if (c == '\r')
 
4464
                {
 
4465
                  hadCarriageReturn = YES;
 
4466
                }
 
4467
              else if (++lineLength > 998)
 
4468
                {
 
4469
                  encoding = @"binary"; // Line of more than 998
 
4470
                  break;
 
4471
                }
 
4472
 
 
4473
              if (c == 0)
 
4474
                {
 
4475
                  encoding = @"binary";
 
4476
                  break;
 
4477
                }
 
4478
              else if (c > 127)
 
4479
                {
 
4480
                  encoding = @"8bit";   // Not 7bit data
 
4481
                }
 
4482
            }
 
4483
 
 
4484
          if (encoding != nil)
 
4485
            {
 
4486
              if (enc == nil)
 
4487
                {
 
4488
                  enc = [GSMimeHeader alloc];
 
4489
                  enc = [enc initWithName: @"content-transfer-encoding"
 
4490
                                    value: encoding
 
4491
                               parameters: nil];
 
4492
                  [self addHeader: enc];
 
4493
                  RELEASE(enc);
 
4494
                }
 
4495
              else
 
4496
                {
 
4497
                  [enc setValue: encoding];
 
4498
                }
 
4499
            }
 
4500
        }
 
4501
    }
 
4502
 
 
4503
  /*
 
4504
   * Add all the headers.
 
4505
   */
 
4506
  enumerator = [headers objectEnumerator];
 
4507
  while ((hdr = [enumerator nextObject]) != nil)
 
4508
    {
 
4509
      [md appendData: [hdr rawMimeData]];
 
4510
    }
 
4511
 
 
4512
  if (partData != nil)
 
4513
    {
 
4514
      count = [content count];
 
4515
      for (i = 0; i < count; i++)
 
4516
        {
 
4517
          GSMimeDocument        *part = [content objectAtIndex: i];
 
4518
          NSMutableData         *rawPart = [partData objectAtIndex: i];
 
4519
 
 
4520
          if (contentIs7bit == YES)
 
4521
            {
 
4522
              NSString  *v;
 
4523
 
 
4524
              enc = [part headerNamed: @"content-transport-encoding"];
 
4525
              v = [enc value];
 
4526
              if (v != nil && ([v isEqualToString: @"8bit"]
 
4527
                || [v isEqualToString: @"binary"]))
 
4528
                {
 
4529
                  [NSException raise: NSInternalInconsistencyException
 
4530
                    format: @"[%@ -%@] bad part encoding for 7bit container",
 
4531
                    NSStringFromClass([self class]),
 
4532
                    NSStringFromSelector(_cmd)];
 
4533
                }
 
4534
            }
 
4535
          /*
 
4536
           * For a multipart document, insert the boundary before each part.
 
4537
           */
 
4538
          [md appendBytes: "\r\n--" length: 4];
 
4539
          [md appendData: boundary];
 
4540
          [md appendBytes: "\r\n" length: 2];
 
4541
          [md appendData: rawPart];
 
4542
        }
 
4543
      [md appendBytes: "\r\n--" length: 4];
 
4544
      [md appendData: boundary];
 
4545
      [md appendBytes: "--\r\n" length: 4];
 
4546
    }
 
4547
  else
 
4548
    {
 
4549
      /*
 
4550
       * Separate headers from body.
 
4551
       */
 
4552
      [md appendBytes: "\r\n" length: 2];
 
4553
 
 
4554
      if ([[enc value] isEqualToString: @"base64"] == YES)
 
4555
        {
 
4556
          const char    *ptr;
 
4557
          unsigned      len;
 
4558
          unsigned      pos = 0;
 
4559
 
 
4560
          d = [GSMimeDocument encodeBase64: d];
 
4561
          ptr = [d bytes];
 
4562
          len = [d length];
 
4563
 
 
4564
          while (len - pos > 76)
 
4565
            {
 
4566
              [md appendBytes: &ptr[pos] length: 76];
 
4567
              [md appendBytes: "\r\n" length: 2];
 
4568
              pos += 76;
 
4569
            }
 
4570
          [md appendBytes: &ptr[pos] length: len-pos];
 
4571
        }
 
4572
      else if ([[enc value] isEqualToString: @"x-uuencode"] == YES)
 
4573
        {
 
4574
          NSString      *name;
 
4575
 
 
4576
          name = [[self headerNamed: @"content-type"] parameterForKey: @"name"];
 
4577
          if (name == nil)
 
4578
            {
 
4579
              name = @"data";
 
4580
            }
 
4581
          [d uuencodeInto: md name: @"untitled" mode: 0644];
 
4582
        }
 
4583
      else
 
4584
        {
 
4585
          [md appendData: d];
 
4586
        }
 
4587
    }
 
4588
  RELEASE(arp);
 
4589
  return md;
 
4590
}
 
4591
 
 
4592
/**
2409
4593
 * Sets a new value for the content of the document.
2410
4594
 */
2411
 
- (BOOL) setContent: (id)newContent
2412
 
{
2413
 
  ASSIGN(content, newContent);
2414
 
  return YES;
 
4595
- (void) setContent: (id)newContent
 
4596
{
 
4597
  if ([newContent isKindOfClass: [NSString class]] == YES)
 
4598
    {
 
4599
      if (newContent != content)
 
4600
        {
 
4601
          ASSIGNCOPY(content, newContent);
 
4602
        }
 
4603
    }
 
4604
  else if ([newContent isKindOfClass: [NSData class]] == YES)
 
4605
    {
 
4606
      if (newContent != content)
 
4607
        {
 
4608
          ASSIGNCOPY(content, newContent);
 
4609
        }
 
4610
    }
 
4611
  else if ([newContent isKindOfClass: NSArrayClass] == YES)
 
4612
    {
 
4613
      if (newContent != content)
 
4614
        {
 
4615
          unsigned      c = [newContent count];
 
4616
 
 
4617
          while (c-- > 0)
 
4618
            {
 
4619
              id        o = [newContent objectAtIndex: c];
 
4620
 
 
4621
              if ([o isKindOfClass: [GSMimeDocument class]] == NO)
 
4622
                {
 
4623
                  [NSException raise: NSInvalidArgumentException
 
4624
                              format: @"Content contains non-GSMimeDocument"];
 
4625
                }
 
4626
            }
 
4627
          newContent = [newContent mutableCopy];
 
4628
          ASSIGN(content, newContent);
 
4629
          RELEASE(newContent);
 
4630
        }
 
4631
    }
 
4632
  else
 
4633
    {
 
4634
      [NSException raise: NSInvalidArgumentException
 
4635
                  format: @"[%@ -%@] passed bad content",
 
4636
        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
 
4637
    }
 
4638
}
 
4639
 
 
4640
/**
 
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
 
4644
 * be named).
 
4645
 */
 
4646
- (void) setContent: (id)newContent
 
4647
               type: (NSString*)type
 
4648
{
 
4649
  [self setContent: newContent type: type name: nil];
 
4650
}
 
4651
 
 
4652
/**
 
4653
 * <p>Convenience method to set the content of the document along with
 
4654
 * creating a content-type header for it.
 
4655
 * </p>
 
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.
 
4664
 * </p>
 
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.
 
4670
 * </p>
 
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.
 
4676
 * </p>
 
4677
 */
 
4678
- (void) setContent: (id)newContent
 
4679
               type: (NSString*)type
 
4680
               name: (NSString*)name
 
4681
{
 
4682
  CREATE_AUTORELEASE_POOL(arp);
 
4683
  NSString      *subtype = nil;
 
4684
  GSMimeHeader  *hdr = nil;
 
4685
 
 
4686
  if (type == nil)
 
4687
    {
 
4688
      type = @"text";
 
4689
    }
 
4690
 
 
4691
  if ([type isEqualToString: @"text"] == YES)
 
4692
    {
 
4693
      subtype = @"plain";
 
4694
    }
 
4695
  else if ([type isEqualToString: @"multipart"] == YES)
 
4696
    {
 
4697
      subtype = @"mixed";
 
4698
    }
 
4699
  else if ([type isEqualToString: @"application"] == YES)
 
4700
    {
 
4701
      subtype = @"octet-stream";
 
4702
    }
 
4703
  else
 
4704
    {
 
4705
      GSMimeParser      *p = AUTORELEASE([GSMimeParser new]);
 
4706
      NSScanner         *scanner = [NSScanner scannerWithString: type];
 
4707
 
 
4708
      hdr = AUTORELEASE([GSMimeHeader new]);
 
4709
      [hdr setName: @"content-type"];
 
4710
      if ([p scanHeaderBody: scanner into: hdr] == NO)
 
4711
        {
 
4712
          [NSException raise: NSInvalidArgumentException
 
4713
                      format: @"Unable to parse type information"];
 
4714
        }
 
4715
    }
 
4716
 
 
4717
  if (hdr == nil)
 
4718
    {
 
4719
      NSString  *val;
 
4720
 
 
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"];
 
4726
      AUTORELEASE(hdr);
 
4727
    }
 
4728
  else
 
4729
    {
 
4730
      type = [hdr objectForKey: @"Type"];
 
4731
      subtype = [hdr objectForKey: @"Subtype"];
 
4732
    }
 
4733
 
 
4734
  if (name != nil)
 
4735
    {
 
4736
      [hdr setParameter: name forKey: @"name"];
 
4737
    }
 
4738
 
 
4739
  if ([type isEqualToString: @"multipart"] == NO
 
4740
    && [type isEqualToString: @"application"] == NO
 
4741
    && [content isKindOfClass: NSArrayClass] == YES)
 
4742
    {
 
4743
      [NSException raise: NSInvalidArgumentException
 
4744
                  format: @"[%@ -%@] content doesn't match content-type",
 
4745
        NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
 
4746
    }
 
4747
 
 
4748
  [self setContent: newContent];
 
4749
  [self setHeader: hdr];
 
4750
  RELEASE(arp);
 
4751
}
 
4752
 
 
4753
/**
 
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.
 
4758
 * </p>
 
4759
 */
 
4760
- (void) setContentType: (NSString *)newType
 
4761
{
 
4762
  CREATE_AUTORELEASE_POOL(arp);
 
4763
  GSMimeHeader  *hdr = nil;
 
4764
  GSMimeParser  *p = AUTORELEASE([GSMimeParser new]);
 
4765
  NSScanner     *scanner = [NSScanner scannerWithString: newType];
 
4766
 
 
4767
  hdr = AUTORELEASE([GSMimeHeader new]);
 
4768
  [hdr setName: @"content-type"];
 
4769
  if ([p scanHeaderBody: scanner into: hdr] == NO)
 
4770
    {
 
4771
      [NSException raise: NSInvalidArgumentException
 
4772
                  format: @"Unable to parse type information"];
 
4773
    }
 
4774
  [self setHeader: hdr];
 
4775
  RELEASE(arp);
2415
4776
}
2416
4777
 
2417
4778
/**
2419
4780
 * Any other headers with the same name will be removed from
2420
4781
 * the document.
2421
4782
 */
2422
 
- (BOOL) setHeader: (NSDictionary*)info
2423
 
{
2424
 
  NSString      *name = [info objectForKey: @"Name"];
2425
 
  unsigned      count = [headers count];
2426
 
 
2427
 
  if (name == nil)
2428
 
    {
2429
 
      NSLog(@"setHeader: supplied with header info without 'Name' field");
2430
 
      return NO;
2431
 
    }
2432
 
 
2433
 
  /*
2434
 
   * Remove any existing headers with this name.
2435
 
   */
2436
 
  while (count-- > 0)
2437
 
    {
2438
 
      NSDictionary      *tmp = [headers objectAtIndex: count];
2439
 
 
2440
 
      if ([name isEqualToString: [tmp objectForKey: @"Name"]] == YES)
2441
 
        {
2442
 
          [headers removeObjectAtIndex: count];
2443
 
        }
2444
 
    }
2445
 
 
2446
 
  return [self addHeader: info];
 
4783
- (void) setHeader: (GSMimeHeader*)info
 
4784
{
 
4785
  NSString      *name = [info name];
 
4786
 
 
4787
  if (name != nil)
 
4788
    {
 
4789
      unsigned  count = [headers count];
 
4790
 
 
4791
      /*
 
4792
       * Remove any existing headers with this name.
 
4793
       */
 
4794
      while (count-- > 0)
 
4795
        {
 
4796
          GSMimeHeader  *tmp = [headers objectAtIndex: count];
 
4797
 
 
4798
          if ([name isEqualToString: [tmp name]] == YES)
 
4799
            {
 
4800
              [headers removeObjectAtIndex: count];
 
4801
            }
 
4802
        }
 
4803
    }
 
4804
  [self addHeader: info];
 
4805
}
 
4806
 
 
4807
/**
 
4808
 * Convenience method to create a new header and add it to the receiver
 
4809
 * replacing any existing header of the same name.<br />
 
4810
 * Returns the newly created header.<br />
 
4811
 * See [GSMimeHeader-initWithName:value:parameters:] and -setHeader: methods.
 
4812
 */
 
4813
- (GSMimeHeader*) setHeader: (NSString*)name
 
4814
                      value: (NSString*)value
 
4815
                 parameters: (NSDictionary*)parameters
 
4816
{
 
4817
  GSMimeHeader  *hdr;
 
4818
 
 
4819
  hdr = [[GSMimeHeader alloc] initWithName: name
 
4820
                                     value: value
 
4821
                                parameters: parameters];
 
4822
  [self setHeader: hdr];
 
4823
  RELEASE(hdr);
 
4824
  return hdr;
 
4825
}
 
4826
 
 
4827
@end
 
4828
 
 
4829
@implementation GSMimeDocument (Private)
 
4830
/**
 
4831
 * Returns the index of the first header matching the specified name
 
4832
 * or NSNotFound if no match is found.<br />
 
4833
 * NB. The supplied name <em>must</em> be lowercase.<br />
 
4834
 * This method is for internal use
 
4835
 */
 
4836
- (unsigned) _indexOfHeaderNamed: (NSString*)name
 
4837
{
 
4838
  unsigned              count = [headers count];
 
4839
  unsigned              index;
 
4840
 
 
4841
  for (index = 0; index < count; index++)
 
4842
    {
 
4843
      GSMimeHeader      *hdr = [headers objectAtIndex: index];
 
4844
 
 
4845
      if ([name isEqualToString: [hdr name]] == YES)
 
4846
        {
 
4847
          return index;
 
4848
        }
 
4849
    }
 
4850
  return NSNotFound;
2447
4851
}
2448
4852
 
2449
4853
@end