2002-03-06 15:50:14 +00:00
|
|
|
|
/** Implementation for GSMIME
|
|
|
|
|
|
|
|
|
|
Copyright (C) 2000,2001 Free Software Foundation, Inc.
|
|
|
|
|
|
2005-11-06 13:53:40 +00:00
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
Date: October 2000
|
2005-02-22 11:22:44 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
This file is part of the GNUstep Base Library.
|
|
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
2007-09-14 11:36:11 +00:00
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
2002-03-06 15:50:14 +00:00
|
|
|
|
License as published by the Free Software Foundation; either
|
2008-06-08 10:38:33 +00:00
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
2007-09-14 11:36:11 +00:00
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
2002-03-06 15:50:14 +00:00
|
|
|
|
License along with this library; if not, write to the Free
|
2024-11-07 13:37:59 +00:00
|
|
|
|
Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
<title>The MIME parsing system</title>
|
|
|
|
|
<chapter>
|
|
|
|
|
<heading>Mime Parser</heading>
|
|
|
|
|
<p>
|
|
|
|
|
The GNUstep Mime parser. This is collection Objective-C classes
|
|
|
|
|
for representing MIME (and HTTP) documents and managing conversions
|
|
|
|
|
to and from convenient internal formats.
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
2002-08-20 10:22:05 +00:00
|
|
|
|
The idea is to center round two classes -
|
2002-03-06 15:50:14 +00:00
|
|
|
|
</p>
|
|
|
|
|
<deflist>
|
|
|
|
|
<term>document</term>
|
|
|
|
|
<desc>
|
2002-08-20 10:22:05 +00:00
|
|
|
|
A container for the actual data (and headers) of a mime/http
|
|
|
|
|
document, this is also used to create raw MIME data for sending.
|
|
|
|
|
</desc>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
<term>parser</term>
|
|
|
|
|
<desc>
|
|
|
|
|
An object that can be fed data and will parse it into a document.
|
|
|
|
|
This object also provides various utility methods and an API
|
|
|
|
|
that permits overriding in order to extend the functionality to
|
|
|
|
|
cope with new document types.
|
|
|
|
|
</desc>
|
|
|
|
|
</deflist>
|
|
|
|
|
</chapter>
|
|
|
|
|
$Date$ $Revision$
|
|
|
|
|
*/
|
|
|
|
|
|
2010-02-19 08:12:46 +00:00
|
|
|
|
#import "common.h"
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#define EXPOSE_GSMimeDocument_IVARS 1
|
|
|
|
|
#define EXPOSE_GSMimeHeader_IVARS 1
|
|
|
|
|
#define EXPOSE_GSMimeParser_IVARS 1
|
2010-03-18 09:56:51 +00:00
|
|
|
|
#define EXPOSE_GSMimeSMTPClient_IVARS 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define GS_GSMimeSMTPClient_IVARS \
|
|
|
|
|
id delegate;\
|
|
|
|
|
NSString *hostname;\
|
|
|
|
|
NSString *identity;\
|
|
|
|
|
NSString *originator;\
|
|
|
|
|
NSString *port;\
|
|
|
|
|
NSString *username;\
|
|
|
|
|
NSTimer *timer;\
|
|
|
|
|
GSMimeDocument *current;\
|
2010-10-28 22:50:38 +00:00
|
|
|
|
GSMimeHeader *version;\
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSMutableArray *queue;\
|
2020-11-14 04:25:34 +00:00
|
|
|
|
NSUInteger maximum;\
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSMutableArray *pending;\
|
|
|
|
|
NSInputStream *istream;\
|
|
|
|
|
NSOutputStream *ostream;\
|
|
|
|
|
NSMutableData *wdata;\
|
|
|
|
|
NSMutableData *rdata;\
|
|
|
|
|
NSMutableString *reply;\
|
|
|
|
|
NSError *lastError;\
|
|
|
|
|
unsigned woffset;\
|
|
|
|
|
BOOL readable;\
|
|
|
|
|
BOOL writable;\
|
2011-02-14 10:57:57 +00:00
|
|
|
|
int cState
|
2010-03-18 09:56:51 +00:00
|
|
|
|
|
2010-02-14 10:48:10 +00:00
|
|
|
|
|
2007-05-14 16:55:16 +00:00
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "Foundation/NSArray.h"
|
|
|
|
|
#import "Foundation/NSAutoreleasePool.h"
|
|
|
|
|
#import "Foundation/NSCharacterSet.h"
|
|
|
|
|
#import "Foundation/NSData.h"
|
|
|
|
|
#import "Foundation/NSDictionary.h"
|
|
|
|
|
#import "Foundation/NSEnumerator.h"
|
|
|
|
|
#import "Foundation/NSException.h"
|
2016-08-03 09:24:53 +00:00
|
|
|
|
#import "Foundation/NSHashTable.h"
|
2010-03-17 15:46:20 +00:00
|
|
|
|
#import "Foundation/NSHost.h"
|
2016-01-21 12:49:15 +00:00
|
|
|
|
#import "Foundation/NSNotification.h"
|
2010-03-17 15:46:20 +00:00
|
|
|
|
#import "Foundation/NSRunLoop.h"
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "Foundation/NSScanner.h"
|
2010-03-17 15:46:20 +00:00
|
|
|
|
#import "Foundation/NSStream.h"
|
|
|
|
|
#import "Foundation/NSTimer.h"
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "Foundation/NSUserDefaults.h"
|
|
|
|
|
#import "Foundation/NSValue.h"
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#import "GNUstepBase/GSObjCRuntime.h"
|
2007-05-14 16:55:16 +00:00
|
|
|
|
#import "GNUstepBase/GSMime.h"
|
|
|
|
|
#import "GNUstepBase/GSXML.h"
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#import "GNUstepBase/NSObject+GNUstepBase.h"
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "GNUstepBase/NSData+GNUstepBase.h"
|
2010-02-25 08:19:52 +00:00
|
|
|
|
#import "GNUstepBase/NSDebug+GNUstepBase.h"
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "GNUstepBase/NSString+GNUstepBase.h"
|
2010-03-17 15:46:20 +00:00
|
|
|
|
#import "GNUstepBase/NSMutableString+GNUstepBase.h"
|
2007-05-14 16:55:16 +00:00
|
|
|
|
#import "GNUstepBase/Unicode.h"
|
|
|
|
|
|
2010-02-14 10:48:10 +00:00
|
|
|
|
#import "../GSPrivate.h"
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2002-05-29 16:44:19 +00:00
|
|
|
|
static NSCharacterSet *whitespace = nil;
|
2002-05-30 05:57:27 +00:00
|
|
|
|
static NSCharacterSet *rfc822Specials = nil;
|
2002-05-29 16:56:58 +00:00
|
|
|
|
static NSCharacterSet *rfc2045Specials = nil;
|
2005-03-11 12:58:54 +00:00
|
|
|
|
static NSMapTable *charsets = 0;
|
2005-03-21 19:16:15 +00:00
|
|
|
|
static NSMapTable *encodings = 0;
|
2004-08-23 14:29:50 +00:00
|
|
|
|
static Class NSArrayClass = 0;
|
2005-03-21 19:51:52 +00:00
|
|
|
|
static Class NSStringClass = 0;
|
2015-08-27 13:35:45 +00:00
|
|
|
|
static Class NSDataClass = 0;
|
2005-03-11 12:58:54 +00:00
|
|
|
|
static Class documentClass = 0;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
|
|
|
|
static BOOL oldStyleFolding = NO;
|
2016-08-03 09:24:53 +00:00
|
|
|
|
static NSString *Cte7bit = @"7bit";
|
|
|
|
|
static NSString *Cte8bit = @"8bit";
|
|
|
|
|
static NSString *CteBase64 = @"base64";
|
|
|
|
|
static NSString *CteBinary = @"binary";
|
2017-06-30 06:37:05 +00:00
|
|
|
|
static NSString *CteContentType = @"content-type";
|
2016-08-03 09:24:53 +00:00
|
|
|
|
static NSString *CteQuotedPrintable = @"quoted-printable";
|
|
|
|
|
static NSString *CteXuuencode = @"x-uuencode";
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2020-04-15 09:31:55 +00:00
|
|
|
|
typedef id (*oaiIMP)(id, SEL, NSUInteger);
|
2011-08-17 17:45:10 +00:00
|
|
|
|
typedef BOOL (*boolIMP)(id, SEL, id);
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
static char *hex = "0123456789ABCDEF";
|
|
|
|
|
|
|
|
|
|
/* This is a test for SMTP standard white space characters.
|
|
|
|
|
* In RCC2822 these are limited to just space and tab
|
|
|
|
|
*/
|
|
|
|
|
static inline BOOL
|
|
|
|
|
isWSP(int c)
|
|
|
|
|
{
|
|
|
|
|
return (c == ' ' || c == '\t') ? YES : NO;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
@interface GSMimeDocument (Private)
|
|
|
|
|
- (GSMimeHeader*) _lastHeaderNamed: (NSString*)name;
|
|
|
|
|
- (NSUInteger) _indexOfHeaderNamed: (NSString*)name;
|
|
|
|
|
@end
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Name - decodebase64()
|
|
|
|
|
* Purpose - Convert 4 bytes in base64 encoding to 3 bytes raw data.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
2005-07-08 11:48:37 +00:00
|
|
|
|
decodebase64(unsigned char *dst, const unsigned char *src)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
dst[0] = (src[0] << 2) | ((src[1] & 0x30) >> 4);
|
|
|
|
|
dst[1] = ((src[1] & 0x0F) << 4) | ((src[2] & 0x3C) >> 2);
|
|
|
|
|
dst[2] = ((src[2] & 0x03) << 6) | (src[3] & 0x3F);
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-30 09:23:27 +00:00
|
|
|
|
void
|
|
|
|
|
GSPrivateEncodeBase64(const uint8_t *src, NSUInteger length, uint8_t *dst)
|
2002-07-03 11:42:02 +00:00
|
|
|
|
{
|
|
|
|
|
int dIndex = 0;
|
|
|
|
|
int sIndex;
|
|
|
|
|
|
|
|
|
|
for (sIndex = 0; sIndex < length; sIndex += 3)
|
|
|
|
|
{
|
2015-08-30 09:23:27 +00:00
|
|
|
|
static char b64[]
|
|
|
|
|
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
2002-07-03 11:42:02 +00:00
|
|
|
|
int c0 = src[sIndex];
|
2004-02-24 14:14:26 +00:00
|
|
|
|
int c1 = (sIndex+1 < length) ? src[sIndex+1] : 0;
|
|
|
|
|
int c2 = (sIndex+2 < length) ? src[sIndex+2] : 0;
|
2002-07-03 11:42:02 +00:00
|
|
|
|
|
|
|
|
|
dst[dIndex++] = b64[(c0 >> 2) & 077];
|
|
|
|
|
dst[dIndex++] = b64[((c0 << 4) & 060) | ((c1 >> 4) & 017)];
|
|
|
|
|
dst[dIndex++] = b64[((c1 << 2) & 074) | ((c2 >> 6) & 03)];
|
|
|
|
|
dst[dIndex++] = b64[c2 & 077];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If len was not a multiple of 3, then we have encoded too
|
|
|
|
|
* many characters. Adjust appropriately.
|
|
|
|
|
*/
|
|
|
|
|
if (sIndex == length + 1)
|
|
|
|
|
{
|
|
|
|
|
/* There were only 2 bytes in that last group */
|
|
|
|
|
dst[dIndex - 1] = '=';
|
|
|
|
|
}
|
|
|
|
|
else if (sIndex == length + 2)
|
|
|
|
|
{
|
|
|
|
|
/* There was only 1 byte in that last group */
|
|
|
|
|
dst[dIndex - 1] = '=';
|
|
|
|
|
dst[dIndex - 2] = '=';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-22 06:22:05 +00:00
|
|
|
|
static void
|
|
|
|
|
encodeQuotedPrintable(NSMutableData *result,
|
|
|
|
|
const unsigned char *src, unsigned length)
|
|
|
|
|
{
|
|
|
|
|
unsigned offset;
|
|
|
|
|
unsigned column = 0;
|
|
|
|
|
unsigned size = 0;
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned char *dst;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
unsigned char c = src[i];
|
|
|
|
|
int add;
|
|
|
|
|
|
|
|
|
|
if ('\r' == c && i < length && '\n' == src[i + 1])
|
|
|
|
|
{
|
|
|
|
|
/* A cr-lf sequence is an end of line, we send that literally
|
|
|
|
|
* as a hard line break.
|
|
|
|
|
*/
|
|
|
|
|
i++;
|
|
|
|
|
size += 2;
|
|
|
|
|
column = 0;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (isWSP(c) && i < length
|
2011-05-22 06:22:05 +00:00
|
|
|
|
&& ('\r' == src[i + 1] || '\n' == src[i + 1]))
|
|
|
|
|
{
|
|
|
|
|
/* RFC 2045 says we have to encode space and tab characters when
|
|
|
|
|
* they occur just before end of line.
|
|
|
|
|
*/
|
|
|
|
|
add = 3;
|
|
|
|
|
}
|
|
|
|
|
else if ('\t' == c || (c >= 32 && c <= 60) || (c >= 62 && c <= 126))
|
|
|
|
|
{
|
|
|
|
|
/* Most characters can be sent literally.
|
|
|
|
|
*/
|
|
|
|
|
add = 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Everything else must be escaped.
|
|
|
|
|
*/
|
|
|
|
|
add = 3;
|
|
|
|
|
}
|
|
|
|
|
if (column + add > 75)
|
|
|
|
|
{
|
|
|
|
|
size += 3; // '=\r\n'
|
|
|
|
|
column = 0;
|
|
|
|
|
}
|
|
|
|
|
size += add;
|
|
|
|
|
column += add;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offset = [result length];
|
|
|
|
|
[result setLength: offset + size];
|
|
|
|
|
dst = (unsigned char*)[result mutableBytes];
|
2011-10-08 17:53:17 +00:00
|
|
|
|
column = 0;
|
2011-05-22 06:22:05 +00:00
|
|
|
|
|
|
|
|
|
for (i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
unsigned char c = src[i];
|
|
|
|
|
int add;
|
|
|
|
|
|
|
|
|
|
if ('\r' == c && i < length && '\n' == src[i + 1])
|
|
|
|
|
{
|
|
|
|
|
/* A cr-lf sequence is an end of line, we send that literally
|
|
|
|
|
* as a hard line break.
|
|
|
|
|
*/
|
|
|
|
|
i++;
|
|
|
|
|
dst[offset++] = '\r';
|
|
|
|
|
dst[offset++] = '\n';
|
|
|
|
|
column = 0;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (isWSP(c) && i < length
|
2011-05-22 06:22:05 +00:00
|
|
|
|
&& ('\r' == src[i + 1] || '\n' == src[i + 1]))
|
|
|
|
|
{
|
|
|
|
|
/* RFC 2045 says we have to encode space and tab characters when
|
|
|
|
|
* they occur just before end of line.
|
|
|
|
|
*/
|
|
|
|
|
add = 3;
|
|
|
|
|
}
|
|
|
|
|
else if ('\t' == c || (c >= 32 && c <= 60) || (c >= 62 && c <= 126))
|
|
|
|
|
{
|
|
|
|
|
/* Most characters can be sent literally.
|
|
|
|
|
*/
|
|
|
|
|
add = 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Everything else must be escaped.
|
|
|
|
|
*/
|
|
|
|
|
add = 3;
|
|
|
|
|
}
|
|
|
|
|
if (column + add > 75)
|
|
|
|
|
{
|
|
|
|
|
dst[offset++] = '=';
|
|
|
|
|
dst[offset++] = '\r';
|
|
|
|
|
dst[offset++] = '\n';
|
|
|
|
|
column = 0;
|
|
|
|
|
}
|
|
|
|
|
if (3 == add)
|
|
|
|
|
{
|
|
|
|
|
dst[offset++] = '=';
|
|
|
|
|
dst[offset++] = hex[c >> 4];
|
|
|
|
|
dst[offset++] = hex[c & 15];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dst[offset++] = c;
|
|
|
|
|
}
|
|
|
|
|
column += add;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
typedef enum {
|
|
|
|
|
WE_QUOTED,
|
|
|
|
|
WE_BASE64
|
|
|
|
|
} WE;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Name - decodeWord()
|
|
|
|
|
* Params - dst destination
|
|
|
|
|
* src where to start decoding from
|
|
|
|
|
* end where to stop decoding (or NULL if end of buffer).
|
|
|
|
|
* enc content-transfer-encoding
|
|
|
|
|
* Purpose - Decode text with BASE64 or QUOTED-PRINTABLE codes.
|
|
|
|
|
*/
|
|
|
|
|
static unsigned char*
|
2016-01-21 12:49:15 +00:00
|
|
|
|
decodeWord(unsigned char *dst, const unsigned char *src,
|
|
|
|
|
const unsigned char *end, WE enc)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
int c;
|
|
|
|
|
|
|
|
|
|
if (enc == WE_QUOTED)
|
|
|
|
|
{
|
|
|
|
|
while (*src && (src != end))
|
|
|
|
|
{
|
|
|
|
|
if (*src == '=')
|
|
|
|
|
{
|
|
|
|
|
src++;
|
|
|
|
|
if (*src == '\0')
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2008-06-15 09:16:08 +00:00
|
|
|
|
if (('\n' == *src) || ('\r' == *src))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!isxdigit(src[0]) || !isxdigit(src[1]))
|
|
|
|
|
{
|
|
|
|
|
/* Strictly speaking the '=' must be followed by
|
|
|
|
|
* two hexadecimal characters, but RFC2045 says that
|
|
|
|
|
* 'A reasonable approach by a robust implementation might be
|
|
|
|
|
* to include the "=" character and the following character
|
|
|
|
|
* in the decoded data without any transformation'
|
|
|
|
|
*/
|
|
|
|
|
*dst++ = '=';
|
|
|
|
|
*dst = *src;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int h;
|
|
|
|
|
int l;
|
|
|
|
|
|
|
|
|
|
/* Strictly speaking only uppercase characters are legal
|
|
|
|
|
* here, but we tolerate lowercase too.
|
|
|
|
|
*/
|
|
|
|
|
h = isdigit(*src) ? (*src - '0') : (*src - 55);
|
|
|
|
|
if (h > 15) h -= 32; // lowercase a-f
|
|
|
|
|
src++;
|
|
|
|
|
l = isdigit(*src) ? (*src - '0') : (*src - 55);
|
|
|
|
|
if (l > 15) l -= 32; // lowercase a-f
|
|
|
|
|
*dst = (h << 4) + l;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else if (*src == '_')
|
|
|
|
|
{
|
|
|
|
|
*dst = '\040';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*dst = *src;
|
|
|
|
|
}
|
|
|
|
|
dst++;
|
|
|
|
|
src++;
|
|
|
|
|
}
|
|
|
|
|
*dst = '\0';
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
else if (enc == WE_BASE64)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[4];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos = 0;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
while (*src && (src != end))
|
|
|
|
|
{
|
|
|
|
|
c = *src++;
|
|
|
|
|
if (isupper(c))
|
|
|
|
|
{
|
|
|
|
|
c -= 'A';
|
|
|
|
|
}
|
|
|
|
|
else if (islower(c))
|
|
|
|
|
{
|
|
|
|
|
c = c - 'a' + 26;
|
|
|
|
|
}
|
|
|
|
|
else if (isdigit(c))
|
|
|
|
|
{
|
|
|
|
|
c = c - '0' + 52;
|
|
|
|
|
}
|
|
|
|
|
else if (c == '/')
|
|
|
|
|
{
|
|
|
|
|
c = 63;
|
|
|
|
|
}
|
|
|
|
|
else if (c == '+')
|
|
|
|
|
{
|
|
|
|
|
c = 62;
|
|
|
|
|
}
|
|
|
|
|
else if (c == '=')
|
|
|
|
|
{
|
|
|
|
|
c = -1;
|
|
|
|
|
}
|
|
|
|
|
else if (c == '-')
|
|
|
|
|
{
|
|
|
|
|
break; /* end */
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
c = -1; /* ignore */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c >= 0)
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = c;
|
|
|
|
|
if (pos == 4)
|
|
|
|
|
{
|
|
|
|
|
pos = 0;
|
|
|
|
|
decodebase64(dst, buf);
|
|
|
|
|
dst += 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos > 0)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger i;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
for (i = pos; i < 4; i++)
|
2013-04-14 08:00:23 +00:00
|
|
|
|
{
|
|
|
|
|
buf[i] = '\0';
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
pos--;
|
2013-04-14 08:00:23 +00:00
|
|
|
|
decodebase64(dst, buf);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
dst += pos;
|
|
|
|
|
*dst = '\0';
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Unsupported encoding type");
|
2010-06-09 15:03:37 +00:00
|
|
|
|
return dst;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-17 15:21:15 +00:00
|
|
|
|
static NSString *
|
|
|
|
|
selectCharacterSet(NSString *str, NSData **d)
|
|
|
|
|
{
|
|
|
|
|
if ([str length] == 0)
|
|
|
|
|
{
|
|
|
|
|
*d = [NSData data];
|
|
|
|
|
return @"us-ascii"; // Default character set.
|
|
|
|
|
}
|
|
|
|
|
if ((*d = [str dataUsingEncoding: NSASCIIStringEncoding]) != nil)
|
|
|
|
|
return @"us-ascii"; // Default character set.
|
|
|
|
|
if ((*d = [str dataUsingEncoding: NSISOLatin1StringEncoding]) != nil)
|
|
|
|
|
return @"iso-8859-1";
|
|
|
|
|
*d = [str dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
|
return @"utf-8"; // Catch-all character set.
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-19 11:29:49 +00:00
|
|
|
|
/**
|
|
|
|
|
* Encode a word in a header according to RFC2047 if necessary.
|
|
|
|
|
* For an ascii word, we just return the data.
|
|
|
|
|
*/
|
|
|
|
|
static NSData*
|
2014-03-11 09:46:54 +00:00
|
|
|
|
wordData(NSString *word, BOOL *encoded)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
|
|
|
|
NSData *d = nil;
|
|
|
|
|
NSString *charset;
|
|
|
|
|
|
|
|
|
|
charset = selectCharacterSet(word, &d);
|
|
|
|
|
if ([charset isEqualToString: @"us-ascii"] == YES)
|
|
|
|
|
{
|
2014-03-11 09:46:54 +00:00
|
|
|
|
*encoded = NO;
|
2002-06-19 11:29:49 +00:00
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
int len = [charset length];
|
2010-02-25 18:34:49 +00:00
|
|
|
|
char buf[len + 1];
|
2002-06-19 11:29:49 +00:00
|
|
|
|
NSMutableData *md;
|
|
|
|
|
|
2014-03-11 09:46:54 +00:00
|
|
|
|
*encoded = YES;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
[charset getCString: buf
|
2010-02-25 18:34:49 +00:00
|
|
|
|
maxLength: len + 1
|
|
|
|
|
encoding: NSISOLatin1StringEncoding];
|
2002-06-19 11:29:49 +00:00
|
|
|
|
md = [NSMutableData dataWithCapacity: [d length]*4/3 + len + 8];
|
2005-03-21 19:16:15 +00:00
|
|
|
|
d = [documentClass encodeBase64: d];
|
2002-06-19 11:29:49 +00:00
|
|
|
|
[md appendBytes: "=?" length: 2];
|
|
|
|
|
[md appendBytes: buf length: len];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
[md appendBytes: "?B?" length: 3];
|
2002-06-19 11:29:49 +00:00
|
|
|
|
[md appendData: d];
|
|
|
|
|
[md appendBytes: "?=" length: 2];
|
|
|
|
|
return md;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* Coding contexts are objects used by the parser to store the state of
|
|
|
|
|
* decoding incoming data while it is being incrementally parsed.<br />
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* The most rudimentary context ... this is used for decoding plain
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* text and binary data (ie data which is not really decoded at all)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* and all other decoding work is done by a subclass.
|
|
|
|
|
*/
|
|
|
|
|
@implementation GSMimeCodingContext
|
|
|
|
|
/**
|
|
|
|
|
* Returns the current value of the 'atEnd' flag.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) atEnd
|
|
|
|
|
{
|
|
|
|
|
return atEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Copying is implemented as a simple retain.
|
|
|
|
|
*/
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
return RETAIN(self);
|
|
|
|
|
}
|
|
|
|
|
|
2002-10-02 12:23:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Decode length bytes of data from sData and append the results to dData.<br />
|
2005-11-06 13:53:40 +00:00
|
|
|
|
* Return YES on success, NO if there is an error.
|
2002-10-02 12:23:36 +00:00
|
|
|
|
*/
|
|
|
|
|
- (BOOL) decodeData: (const void*)sData
|
2009-02-23 20:42:32 +00:00
|
|
|
|
length: (NSUInteger)length
|
2002-10-02 12:23:36 +00:00
|
|
|
|
intoData: (NSMutableData*)dData
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger size = [dData length];
|
2002-10-02 12:23:36 +00:00
|
|
|
|
|
|
|
|
|
[dData setLength: size + length];
|
|
|
|
|
memcpy([dData mutableBytes] + size, sData, length);
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* Sets the current value of the 'atEnd' flag.
|
|
|
|
|
*/
|
|
|
|
|
- (void) setAtEnd: (BOOL)flag
|
|
|
|
|
{
|
|
|
|
|
atEnd = flag;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface GSMimeBase64DecoderContext : GSMimeCodingContext
|
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
unsigned char buf[4];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
@implementation GSMimeBase64DecoderContext
|
2002-10-02 12:23:36 +00:00
|
|
|
|
- (BOOL) decodeData: (const void*)sData
|
2009-02-23 20:42:32 +00:00
|
|
|
|
length: (NSUInteger)length
|
2002-10-02 12:23:36 +00:00
|
|
|
|
intoData: (NSMutableData*)dData
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger size = [dData length];
|
2002-10-02 12:23:36 +00:00
|
|
|
|
unsigned char *src = (unsigned char*)sData;
|
|
|
|
|
unsigned char *end = src + length;
|
|
|
|
|
unsigned char *beg;
|
|
|
|
|
unsigned char *dst;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Expand destination data buffer to have capacity to handle info.
|
|
|
|
|
*/
|
|
|
|
|
[dData setLength: size + (3 * (end + 8 - src))/4];
|
|
|
|
|
dst = (unsigned char*)[dData mutableBytes];
|
|
|
|
|
beg = dst;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Now decode data into buffer, keeping count and temporary
|
|
|
|
|
* data in context.
|
|
|
|
|
*/
|
|
|
|
|
while (src < end)
|
|
|
|
|
{
|
|
|
|
|
int cc = *src++;
|
|
|
|
|
|
|
|
|
|
if (isupper(cc))
|
|
|
|
|
{
|
|
|
|
|
cc -= 'A';
|
|
|
|
|
}
|
|
|
|
|
else if (islower(cc))
|
|
|
|
|
{
|
|
|
|
|
cc = cc - 'a' + 26;
|
|
|
|
|
}
|
|
|
|
|
else if (isdigit(cc))
|
|
|
|
|
{
|
|
|
|
|
cc = cc - '0' + 52;
|
|
|
|
|
}
|
|
|
|
|
else if (cc == '+')
|
|
|
|
|
{
|
|
|
|
|
cc = 62;
|
|
|
|
|
}
|
|
|
|
|
else if (cc == '/')
|
|
|
|
|
{
|
|
|
|
|
cc = 63;
|
|
|
|
|
}
|
|
|
|
|
else if (cc == '=')
|
|
|
|
|
{
|
2010-06-10 09:28:15 +00:00
|
|
|
|
[self setAtEnd: YES];
|
2002-10-02 12:23:36 +00:00
|
|
|
|
cc = -1;
|
|
|
|
|
}
|
|
|
|
|
else if (cc == '-')
|
|
|
|
|
{
|
|
|
|
|
[self setAtEnd: YES];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cc = -1; /* ignore */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cc >= 0)
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = cc;
|
|
|
|
|
if (pos == 4)
|
|
|
|
|
{
|
|
|
|
|
pos = 0;
|
|
|
|
|
decodebase64(dst, buf);
|
|
|
|
|
dst += 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Odd characters at end of decoded data need to be added separately.
|
|
|
|
|
*/
|
|
|
|
|
if ([self atEnd] == YES && pos > 0)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger len = pos - 1;
|
2002-10-02 12:23:36 +00:00
|
|
|
|
|
|
|
|
|
while (pos < 4)
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = '\0';
|
|
|
|
|
}
|
|
|
|
|
pos = 0;
|
|
|
|
|
decodebase64(dst, buf);
|
|
|
|
|
size += len;
|
|
|
|
|
}
|
|
|
|
|
[dData setLength: size + dst - beg];
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface GSMimeQuotedDecoderContext : GSMimeCodingContext
|
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
unsigned char buf[4];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
@implementation GSMimeQuotedDecoderContext
|
2002-10-02 12:23:36 +00:00
|
|
|
|
- (BOOL) decodeData: (const void*)sData
|
2009-02-23 20:42:32 +00:00
|
|
|
|
length: (NSUInteger)length
|
2002-10-02 12:23:36 +00:00
|
|
|
|
intoData: (NSMutableData*)dData
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger size = [dData length];
|
2002-10-02 12:23:36 +00:00
|
|
|
|
unsigned char *src = (unsigned char*)sData;
|
|
|
|
|
unsigned char *end = src + length;
|
|
|
|
|
unsigned char *beg;
|
|
|
|
|
unsigned char *dst;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Expand destination data buffer to have capacity to handle info.
|
|
|
|
|
*/
|
|
|
|
|
[dData setLength: size + (end - src)];
|
|
|
|
|
dst = (unsigned char*)[dData mutableBytes];
|
|
|
|
|
beg = dst;
|
|
|
|
|
|
|
|
|
|
while (src < end)
|
|
|
|
|
{
|
|
|
|
|
if (pos > 0)
|
|
|
|
|
{
|
2011-05-22 06:22:05 +00:00
|
|
|
|
if (1 == pos && '\r' == *src)
|
|
|
|
|
{
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '\n')
|
2002-10-02 12:23:36 +00:00
|
|
|
|
{
|
|
|
|
|
pos = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = *src;
|
|
|
|
|
if (pos == 3)
|
|
|
|
|
{
|
2011-05-22 06:22:05 +00:00
|
|
|
|
BOOL ok = YES;
|
2002-10-02 12:23:36 +00:00
|
|
|
|
int c;
|
|
|
|
|
int val;
|
|
|
|
|
|
|
|
|
|
pos = 0;
|
|
|
|
|
c = buf[1];
|
2011-05-22 06:22:05 +00:00
|
|
|
|
if (isxdigit(c))
|
|
|
|
|
{
|
|
|
|
|
if (islower(c)) c = toupper(c);
|
|
|
|
|
val = isdigit(c) ? (c - '0') : (c - 55);
|
|
|
|
|
val *= 0x10;
|
|
|
|
|
c = buf[2];
|
|
|
|
|
if (isxdigit(c))
|
|
|
|
|
{
|
|
|
|
|
if (islower(c)) c = toupper(c);
|
|
|
|
|
val += isdigit(c) ? (c - '0') : (c - 55);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ok = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ok = NO;
|
|
|
|
|
}
|
|
|
|
|
if (YES == ok)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = val;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* A bad escape sequence is copied literally.
|
|
|
|
|
*/
|
|
|
|
|
*dst++ = '=';
|
|
|
|
|
*dst++ = buf[0];
|
|
|
|
|
*dst++ = buf[1];
|
|
|
|
|
}
|
2002-10-02 12:23:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '=')
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = '=';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*dst++ = *src;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
}
|
|
|
|
|
[dData setLength: size + dst - beg];
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface GSMimeChunkedDecoderContext : GSMimeCodingContext
|
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
unsigned char buf[8];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
enum {
|
|
|
|
|
ChunkSize, // Reading chunk size
|
|
|
|
|
ChunkExt, // Reading chunk extensions
|
|
|
|
|
ChunkEol1, // Reading end of line after size;ext
|
|
|
|
|
ChunkData, // Reading chunk data
|
|
|
|
|
ChunkEol2, // Reading end of line after data
|
|
|
|
|
ChunkFoot, // Reading chunk footer after newline
|
|
|
|
|
ChunkFootA // Reading chunk footer
|
|
|
|
|
} state;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger size; // Size of buffer required.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSMutableData *data;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
@implementation GSMimeChunkedDecoderContext
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
RELEASE(data);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
self = [super init];
|
|
|
|
|
if (self != nil)
|
|
|
|
|
{
|
|
|
|
|
data = [NSMutableData new];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2004-02-19 11:21:46 +00:00
|
|
|
|
/**
|
|
|
|
|
* Inefficient ... copies data into output object and only performs
|
|
|
|
|
* the actual decoding at the end.
|
|
|
|
|
*/
|
|
|
|
|
@interface GSMimeUUCodingContext : GSMimeCodingContext
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GSMimeUUCodingContext
|
|
|
|
|
- (BOOL) decodeData: (const void*)sData
|
2009-02-23 20:42:32 +00:00
|
|
|
|
length: (NSUInteger)length
|
2004-02-19 11:21:46 +00:00
|
|
|
|
intoData: (NSMutableData*)dData
|
|
|
|
|
{
|
|
|
|
|
[super decodeData: sData length: length intoData: dData];
|
|
|
|
|
|
|
|
|
|
if ([self atEnd] == YES)
|
|
|
|
|
{
|
2004-03-12 15:49:08 +00:00
|
|
|
|
NSMutableData *dec;
|
|
|
|
|
|
|
|
|
|
dec = [[NSMutableData alloc] initWithCapacity: [dData length]];
|
|
|
|
|
[dData uudecodeInto: dec name: 0 mode: 0];
|
|
|
|
|
[dData setData: dec];
|
|
|
|
|
RELEASE(dec);
|
2004-02-19 11:21:46 +00:00
|
|
|
|
}
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
@end
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface GSMimeParser (Private)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
- (void) _child;
|
|
|
|
|
- (BOOL) _decodeBody: (NSData*)d;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
- (NSString*) _decodeHeader;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
- (NSRange) _endOfHeaders: (NSData*)newData;
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This class provides support for parsing MIME messages
|
|
|
|
|
* into GSMimeDocument objects. Each parser object maintains
|
|
|
|
|
* an associated document into which data is stored.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* You supply the document to be parsed as one or more data
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* items passed to the -parse: method, and (if
|
|
|
|
|
* the method always returns YES, you give it
|
|
|
|
|
* a final nil argument to mark the end of the
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* document.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* On completion of parsing a valid document, the
|
2002-11-25 18:18:18 +00:00
|
|
|
|
* [GSMimeParser-mimeDocument] method returns the
|
2002-11-04 15:39:43 +00:00
|
|
|
|
* resulting parsed document.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
2009-03-06 09:01:17 +00:00
|
|
|
|
* <p>If you need to parse faulty documents (eg where a faulty mail client
|
|
|
|
|
* has produced an email which does not conform to the MIME standards), you
|
2009-08-25 11:13:00 +00:00
|
|
|
|
* should look at the -setBuggyQuotes: and -setDefaultCharset: methods, which
|
2009-03-06 09:01:17 +00:00
|
|
|
|
* are designed to cope with the most common faults.
|
|
|
|
|
* </p>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
@implementation GSMimeParser
|
|
|
|
|
|
2002-05-26 16:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to parse a single data item as a MIME message
|
|
|
|
|
* and return the resulting document.
|
|
|
|
|
*/
|
|
|
|
|
+ (GSMimeDocument*) documentFromData: (NSData*)mimeData
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *newDocument = nil;
|
|
|
|
|
GSMimeParser *parser = [GSMimeParser new];
|
|
|
|
|
|
|
|
|
|
if ([parser parse: mimeData] == YES)
|
|
|
|
|
{
|
|
|
|
|
[parser parse: nil];
|
|
|
|
|
}
|
|
|
|
|
if ([parser isComplete] == YES)
|
|
|
|
|
{
|
2002-05-27 08:52:09 +00:00
|
|
|
|
newDocument = [parser mimeDocument];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC(RETAIN(newDocument);)
|
2002-05-26 16:10:31 +00:00
|
|
|
|
}
|
|
|
|
|
RELEASE(parser);
|
|
|
|
|
return AUTORELEASE(newDocument);
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
2024-11-27 16:25:08 +00:00
|
|
|
|
NSMutableCharacterSet *m = [[NSMutableCharacterSet alloc] init];
|
|
|
|
|
|
|
|
|
|
[m formUnionWithCharacterSet:
|
|
|
|
|
[NSCharacterSet characterSetWithCharactersInString:
|
|
|
|
|
@".()<>@,;:[]\"\\"]];
|
|
|
|
|
[m formUnionWithCharacterSet:
|
|
|
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
|
[m formUnionWithCharacterSet:
|
|
|
|
|
[NSCharacterSet controlCharacterSet]];
|
|
|
|
|
[m formUnionWithCharacterSet:
|
|
|
|
|
[NSCharacterSet illegalCharacterSet]];
|
|
|
|
|
rfc822Specials = [m copy];
|
|
|
|
|
[[NSObject leakAt: &rfc822Specials] release];
|
|
|
|
|
[m formUnionWithCharacterSet:
|
|
|
|
|
[NSCharacterSet characterSetWithCharactersInString:
|
|
|
|
|
@"/?="]];
|
|
|
|
|
[m removeCharactersInString: @"."];
|
|
|
|
|
rfc2045Specials = [m copy];
|
|
|
|
|
[[NSObject leakAt: &rfc2045Specials] release];
|
|
|
|
|
[m release];
|
|
|
|
|
whitespace = RETAIN([NSCharacterSet whitespaceAndNewlineCharacterSet]);
|
|
|
|
|
[[NSObject leakAt: &whitespace] release];
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if (NSArrayClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSArrayClass = [NSArray class];
|
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
if (NSDataClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSDataClass = [NSData class];
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if (NSStringClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSStringClass = [NSString class];
|
|
|
|
|
}
|
2005-03-11 12:58:54 +00:00
|
|
|
|
if (documentClass == 0)
|
|
|
|
|
{
|
|
|
|
|
documentClass = [GSMimeDocument class];
|
|
|
|
|
}
|
2004-08-23 14:29:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* Create and return a parser.
|
|
|
|
|
*/
|
|
|
|
|
+ (GSMimeParser*) mimeParser
|
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([[self alloc] init]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return a coding context object to be used for decoding data
|
|
|
|
|
* according to the scheme specified in the header.
|
|
|
|
|
* <p>
|
|
|
|
|
* The default implementation supports the following transfer
|
|
|
|
|
* encodings specified in either a <code>transfer-encoding</code>
|
|
|
|
|
* of <code>content-transfer-encoding</code> header -
|
|
|
|
|
* </p>
|
|
|
|
|
* <list>
|
|
|
|
|
* <item>base64</item>
|
|
|
|
|
* <item>quoted-printable</item>
|
|
|
|
|
* <item>binary (no coding actually performed)</item>
|
|
|
|
|
* <item>7bit (no coding actually performed)</item>
|
|
|
|
|
* <item>8bit (no coding actually performed)</item>
|
|
|
|
|
* <item>chunked (for HTTP/1.1)</item>
|
2004-02-19 11:21:46 +00:00
|
|
|
|
* <item>x-uuencode</item>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </list>
|
2013-07-03 17:58:21 +00:00
|
|
|
|
* To add new coding schemes to the parser, you need to override
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* this method to return a new coding context for your scheme
|
|
|
|
|
* when the info argument indicates that this is appropriate.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (GSMimeCodingContext*) contextFor: (GSMimeHeader*)info
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *name;
|
|
|
|
|
NSString *value;
|
|
|
|
|
|
|
|
|
|
if (info == nil)
|
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
name = [info name];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([name isEqualToString: @"content-transfer-encoding"] == YES
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|| [name isEqualToString: @"transfer-encoding"] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
value = [[info value] lowercaseString];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([value length] == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad value for %@ header - assume binary encoding", name);
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if ([value isEqualToString: CteBase64] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeBase64DecoderContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([value isEqualToString: CteQuotedPrintable] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeQuotedDecoderContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([value isEqualToString: CteBinary] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([value isEqualToString: Cte7bit] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([value isEqualToString: Cte8bit] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
|
|
|
|
else if ([value isEqualToString: @"chunked"] == YES)
|
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeChunkedDecoderContext new]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([value isEqualToString: CteXuuencode] == YES)
|
2004-02-19 11:21:46 +00:00
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([GSMimeUUCodingContext new]);
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSLog(@"contextFor: - unknown header (%@) ... assumed binary encoding", name);
|
|
|
|
|
return AUTORELEASE([GSMimeCodingContext new]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the data accumulated in the parser. If the parser is
|
|
|
|
|
* still parsing headers, this will be the header data read so far.
|
|
|
|
|
* If the parse has parsed the body of the message, this will be
|
|
|
|
|
* the data of the body, with any transfer encoding removed.
|
|
|
|
|
*/
|
2012-10-30 14:05:12 +00:00
|
|
|
|
- (NSMutableData*) data
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
RELEASE(data);
|
|
|
|
|
RELEASE(child);
|
|
|
|
|
RELEASE(context);
|
|
|
|
|
RELEASE(boundary);
|
|
|
|
|
RELEASE(document);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* Decodes the raw data from the specified range in the source
|
|
|
|
|
* data object and appends it to the destination data object.
|
|
|
|
|
* The context object provides information about the content
|
|
|
|
|
* encoding type in use, and the state of the decoding operation.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* This method may be called repeatedly to incrementally decode
|
|
|
|
|
* information as it arrives on some communications channel.
|
|
|
|
|
* It should be called with a nil source data item (or with
|
|
|
|
|
* the atEnd flag of the context set to YES) in order to flush
|
|
|
|
|
* any information held in the context to the output data
|
|
|
|
|
* object.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* You may override this method in order to implement additional
|
|
|
|
|
* coding schemes, but usually it should be enough for you to
|
|
|
|
|
* implement a custom GSMimeCodingContext subclass fotr this method
|
|
|
|
|
* to use.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) decodeData: (NSData*)sData
|
|
|
|
|
fromRange: (NSRange)aRange
|
|
|
|
|
intoData: (NSMutableData*)dData
|
|
|
|
|
withContext: (GSMimeCodingContext*)con
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger len = [sData length];
|
2002-10-02 12:23:36 +00:00
|
|
|
|
BOOL result = YES;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
if (dData == nil || [con isKindOfClass: [GSMimeCodingContext class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] bad destination data for decode",
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
GS_RANGE_CHECK(aRange, len);
|
|
|
|
|
|
|
|
|
|
/*
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* Chunked decoding is relatively complex ... it makes sense to do it
|
|
|
|
|
* here, in order to make use of parser facilities, rather than having
|
|
|
|
|
* the decoding context do the work. In this case the context is used
|
|
|
|
|
* solely to store state information.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-10-02 12:23:36 +00:00
|
|
|
|
if ([con class] == [GSMimeChunkedDecoderContext class])
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
GSMimeChunkedDecoderContext *ctxt;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger size = [dData length];
|
2012-07-31 06:53:02 +00:00
|
|
|
|
unsigned char *buf;
|
|
|
|
|
unsigned char *beg;
|
|
|
|
|
unsigned char *dst;
|
|
|
|
|
const char *src;
|
|
|
|
|
const char *end;
|
|
|
|
|
const char *footers;
|
|
|
|
|
BOOL atEnd;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
ctxt = (GSMimeChunkedDecoderContext*)con;
|
|
|
|
|
|
2012-07-31 06:53:02 +00:00
|
|
|
|
/* Get pointers into source data buffer.
|
2002-10-02 12:23:36 +00:00
|
|
|
|
*/
|
|
|
|
|
src = (const char *)[sData bytes];
|
2003-07-28 16:44:24 +00:00
|
|
|
|
footers = src;
|
2002-10-02 12:23:36 +00:00
|
|
|
|
src += aRange.location;
|
|
|
|
|
end = src + aRange.length;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
beg = 0;
|
2012-07-31 06:53:02 +00:00
|
|
|
|
|
|
|
|
|
/* Make sure buffer is big enough, and set up output pointers.
|
|
|
|
|
* ctxt->size is the amount of data laready decoded, so if we
|
|
|
|
|
* add the size of the chunked source data we are guaranteed
|
|
|
|
|
* to have enough space for the unchunked data.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2012-07-31 06:53:02 +00:00
|
|
|
|
[dData setLength: ctxt->size + [sData length]];
|
|
|
|
|
buf = (unsigned char*)[dData mutableBytes];
|
|
|
|
|
dst = buf + size;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
beg = dst;
|
|
|
|
|
|
2012-07-31 06:53:02 +00:00
|
|
|
|
atEnd = [ctxt atEnd];
|
|
|
|
|
while (NO == atEnd && src < end)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
switch (ctxt->state)
|
|
|
|
|
{
|
|
|
|
|
case ChunkSize:
|
|
|
|
|
if (isxdigit(*src) && ctxt->pos < sizeof(ctxt->buf))
|
|
|
|
|
{
|
|
|
|
|
ctxt->buf[ctxt->pos++] = *src;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == ';')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkExt;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '\r')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkEol1;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '\n')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkData;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
if (ctxt->state != ChunkSize)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger val = 0;
|
|
|
|
|
NSUInteger index;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
for (index = 0; index < ctxt->pos; index++)
|
|
|
|
|
{
|
|
|
|
|
val *= 16;
|
|
|
|
|
if (isdigit(ctxt->buf[index]))
|
|
|
|
|
{
|
|
|
|
|
val += ctxt->buf[index] - '0';
|
|
|
|
|
}
|
|
|
|
|
else if (isupper(ctxt->buf[index]))
|
|
|
|
|
{
|
|
|
|
|
val += ctxt->buf[index] - 'A' + 10;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
val += ctxt->buf[index] - 'a' + 10;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ctxt->pos = val;
|
2012-07-31 06:53:02 +00:00
|
|
|
|
|
|
|
|
|
/* If we have read a chunk already, make sure that our
|
|
|
|
|
* destination size stored in context is updated before
|
|
|
|
|
* resetting the destination pointer for a new chunk.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
size += (dst - beg);
|
|
|
|
|
ctxt->size = size + val;
|
|
|
|
|
beg = dst;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkExt:
|
|
|
|
|
if (*src == '\r')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkEol1;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '\n')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkData;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkEol1:
|
|
|
|
|
if (*src == '\n')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkData;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkData:
|
|
|
|
|
/*
|
|
|
|
|
* If the pos is non-zero, we have a data chunk to read.
|
2002-09-17 10:26:54 +00:00
|
|
|
|
* otherwise, what we actually want is to read footers.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
if (ctxt->pos > 0)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = *src++;
|
|
|
|
|
if (--ctxt->pos == 0)
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkEol2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
footers = src; // Record start position.
|
|
|
|
|
ctxt->state = ChunkFoot;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkEol2:
|
|
|
|
|
if (*src == '\n')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkSize;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkFoot:
|
|
|
|
|
if (*src == '\r')
|
|
|
|
|
{
|
|
|
|
|
src++;
|
|
|
|
|
}
|
|
|
|
|
else if (*src == '\n')
|
|
|
|
|
{
|
2012-07-31 06:53:02 +00:00
|
|
|
|
atEnd = YES;
|
|
|
|
|
[ctxt setAtEnd: atEnd];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkFootA;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ChunkFootA:
|
|
|
|
|
if (*src == '\n')
|
|
|
|
|
{
|
|
|
|
|
ctxt->state = ChunkFootA;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ctxt->state == ChunkFoot || ctxt->state == ChunkFootA)
|
|
|
|
|
{
|
|
|
|
|
[ctxt->data appendBytes: footers length: src - footers];
|
2012-07-31 06:53:02 +00:00
|
|
|
|
if (YES == atEnd)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableData *old;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Pretend we are back parsing the original headers ...
|
|
|
|
|
*/
|
|
|
|
|
old = data;
|
|
|
|
|
data = ctxt->data;
|
|
|
|
|
bytes = (unsigned char*)[data mutableBytes];
|
|
|
|
|
dataEnd = [data length];
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.inBody = 0;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Duplicate the normal header parsing process for our footers.
|
|
|
|
|
*/
|
2002-11-26 14:26:00 +00:00
|
|
|
|
while (flags.inBody == 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 15:03:37 +00:00
|
|
|
|
NSString *header;
|
|
|
|
|
|
|
|
|
|
header = [self _decodeHeader];
|
|
|
|
|
if (header == nil)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2010-06-09 15:03:37 +00:00
|
|
|
|
if ([self parseHeader: header] == NO)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 15:03:37 +00:00
|
|
|
|
flags.hadErrors = 1;
|
|
|
|
|
break;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* restore original data.
|
|
|
|
|
*/
|
|
|
|
|
ctxt->data = data;
|
|
|
|
|
data = old;
|
|
|
|
|
bytes = (unsigned char*)[data mutableBytes];
|
|
|
|
|
dataEnd = [data length];
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.inBody = 1;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-07-31 06:53:02 +00:00
|
|
|
|
|
|
|
|
|
/** Correct the size of the output buffer (shrink back from the
|
|
|
|
|
* original allocation to the actual unchunked size).
|
2011-10-08 17:53:17 +00:00
|
|
|
|
*/
|
2002-03-06 15:50:14 +00:00
|
|
|
|
[dData setLength: size + dst - beg];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-10-02 12:23:36 +00:00
|
|
|
|
result = [con decodeData: [sData bytes] + aRange.location
|
|
|
|
|
length: aRange.length
|
|
|
|
|
intoData: dData];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A nil data item as input represents end of data.
|
|
|
|
|
*/
|
|
|
|
|
if (sData == nil)
|
|
|
|
|
{
|
|
|
|
|
[con setAtEnd: YES];
|
|
|
|
|
}
|
|
|
|
|
|
2002-10-02 12:23:36 +00:00
|
|
|
|
return result;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
2015-08-29 16:52:31 +00:00
|
|
|
|
NSString *desc;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2015-08-29 16:52:31 +00:00
|
|
|
|
desc = [NSMutableString stringWithFormat: @"%@ - %@",
|
|
|
|
|
[super description], document];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return desc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-05-27 08:28:28 +00:00
|
|
|
|
* <deprecated />
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* Returns the object into which raw mime data is being parsed.
|
|
|
|
|
*/
|
2002-05-27 08:28:28 +00:00
|
|
|
|
- (id) document
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return document;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 11:51:08 +00:00
|
|
|
|
/** If there was more data passed to the parser than actually needed to
|
|
|
|
|
* form the document, this method returns that excess data, othrwise it
|
|
|
|
|
* returns nil.
|
|
|
|
|
*/
|
|
|
|
|
- (NSData*) excess
|
|
|
|
|
{
|
|
|
|
|
if (flags.excessData == 1)
|
|
|
|
|
{
|
|
|
|
|
return boundary;
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 16:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
* This method may be called to tell the parser that it should not expect
|
|
|
|
|
* to parse any headers, and that the data it will receive is body data.<br />
|
|
|
|
|
* If the parse is already in the body, or is complete, this method has
|
|
|
|
|
* no effect.<br />
|
|
|
|
|
* This is for use when some other utility has been used to parse headers,
|
|
|
|
|
* and you have set the headers of the document owned by the parser
|
|
|
|
|
* accordingly. You can then use the GSMimeParser to read the body data
|
|
|
|
|
* into the document.
|
|
|
|
|
*/
|
|
|
|
|
- (void) expectNoHeaders
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.complete == 0)
|
2002-05-26 16:10:31 +00:00
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.inBody = 1;
|
2002-05-26 16:10:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-05-27 08:28:28 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
2002-05-27 15:35:54 +00:00
|
|
|
|
* Returns YES if the document parsing is known to be completed successfully.
|
|
|
|
|
* Returns NO if either more data is needed, or if the parser encountered an
|
|
|
|
|
* error.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
- (BOOL) isComplete
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.hadErrors == 1)
|
2002-05-27 15:35:54 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2002-11-26 14:26:00 +00:00
|
|
|
|
return (flags.complete == 1) ? YES : NO;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 08:28:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns YES if the parser is parsing an HTTP document rather than
|
|
|
|
|
* a true MIME document.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) isHttp
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
return (flags.isHttp == 1) ? YES : NO;
|
2002-05-27 08:28:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns YES if all the document headers have been parsed but
|
|
|
|
|
* the document body parsing may not yet be complete.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) isInBody
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
return (flags.inBody == 1) ? YES : NO;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns YES if parsing of the document headers has not yet
|
|
|
|
|
* been completed.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) isInHeaders
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.inBody == 1)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.complete == 1)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
self = [super init];
|
|
|
|
|
if (self != nil)
|
|
|
|
|
{
|
2005-03-11 12:58:54 +00:00
|
|
|
|
document = [[documentClass alloc] init];
|
2010-06-09 15:03:37 +00:00
|
|
|
|
data = [NSMutableData new];
|
2004-05-30 09:05:10 +00:00
|
|
|
|
_defaultEncoding = NSASCIIStringEncoding;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 08:28:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns the GSMimeDocument instance into which data is being parsed
|
|
|
|
|
* or has been parsed.
|
|
|
|
|
*/
|
|
|
|
|
- (GSMimeDocument*) mimeDocument
|
|
|
|
|
{
|
|
|
|
|
return document;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This method is called repeatedly to pass raw mime data into
|
|
|
|
|
* the parser. It returns <code>YES</code> as long as it wants
|
|
|
|
|
* more data to complete parsing of a document, and <code>NO</code>
|
|
|
|
|
* if parsing is complete, either due to having reached the end of
|
|
|
|
|
* a document or due to an error.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* Since it is not always possible to determine if the end of a
|
|
|
|
|
* MIME document has been reached from its content, the method
|
|
|
|
|
* may need to be called with a nil or empty argument after you have
|
|
|
|
|
* passed all the data to it ... this tells it that the data
|
|
|
|
|
* is complete.
|
|
|
|
|
* </p>
|
2002-05-27 14:03:10 +00:00
|
|
|
|
* <p>
|
2002-05-27 15:35:54 +00:00
|
|
|
|
* The parser attempts to be as flexible as possible and to continue
|
|
|
|
|
* parsing wherever it can. If an error occurs in parsing, the
|
|
|
|
|
* -isComplete method will always return NO, even after the -parse:
|
|
|
|
|
* method has been called with a nil argument.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
2002-05-27 14:03:10 +00:00
|
|
|
|
* A multipart document will be parsed to content consisting of an
|
|
|
|
|
* NSArray of GSMimeDocument instances representing each part.<br />
|
|
|
|
|
* Otherwise, a document will become content of type NSData, unless
|
|
|
|
|
* it is of content type <em>text</em>, in which case it will be an
|
|
|
|
|
* NSString.<br />
|
|
|
|
|
* If a document has no content type specified, it will be treated as
|
|
|
|
|
* <em>text</em>, unless it is identifiable as a <em>file</em>
|
2005-02-22 11:22:44 +00:00
|
|
|
|
* (eg. t has a content-disposition header containing a filename parameter).
|
2002-05-27 14:03:10 +00:00
|
|
|
|
* </p>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
- (BOOL) parse: (NSData*)d
|
|
|
|
|
{
|
2013-04-24 15:15:49 +00:00
|
|
|
|
if (1 == flags.complete || 1 == flags.hadErrors)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2013-04-24 15:15:49 +00:00
|
|
|
|
return NO; /* Already completely parsed or failed! */
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2008-02-20 09:22:43 +00:00
|
|
|
|
if ([d length] > 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2013-04-24 15:15:49 +00:00
|
|
|
|
if (0 == flags.inBody)
|
2008-02-20 09:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
if ([self parseHeaders: d remaining: &d] == YES)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([d length] > 0)
|
|
|
|
|
{
|
2013-04-24 15:15:49 +00:00
|
|
|
|
if (1 == flags.inBody)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* We can't just re-call -parse: ...
|
|
|
|
|
* that would lead to recursion.
|
|
|
|
|
*/
|
|
|
|
|
return [self _decodeBody: d];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return [self parse: d];
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-04-24 15:15:49 +00:00
|
|
|
|
if (1 == flags.complete)
|
2008-10-03 07:40:52 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return YES; /* Want more data for body */
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-04-24 15:15:49 +00:00
|
|
|
|
if (1 == flags.wantEndOfLine)
|
2003-11-25 13:19:41 +00:00
|
|
|
|
{
|
2006-06-15 14:05:22 +00:00
|
|
|
|
[self parse: [NSData dataWithBytes: "\r\n" length: 2]];
|
2003-11-25 13:19:41 +00:00
|
|
|
|
}
|
2013-04-24 15:15:49 +00:00
|
|
|
|
else if (1 == flags.inBody)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2006-06-15 14:05:22 +00:00
|
|
|
|
[self _decodeBody: d];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* If still parsing headers, add CR-LF sequences to terminate
|
|
|
|
|
* the headers.
|
|
|
|
|
*/
|
2006-06-15 14:05:22 +00:00
|
|
|
|
[self parse: [NSData dataWithBytes: "\r\n\r\n" length: 4]];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2003-11-25 13:19:41 +00:00
|
|
|
|
flags.wantEndOfLine = 0;
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1; /* Finished parsing */
|
2006-06-15 14:05:22 +00:00
|
|
|
|
return NO; /* Want no more data */
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-02-20 09:22:43 +00:00
|
|
|
|
- (BOOL) parseHeaders: (NSData*)d remaining: (NSData**)body
|
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
GSMimeHeader *info;
|
2008-02-20 09:22:43 +00:00
|
|
|
|
GSMimeHeader *hdr;
|
2010-06-09 15:03:37 +00:00
|
|
|
|
NSRange r;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger l = [d length];
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
|
|
|
|
if (flags.complete == 1 || flags.inBody == 1)
|
|
|
|
|
{
|
|
|
|
|
return NO; /* Headers already parsed! */
|
|
|
|
|
}
|
|
|
|
|
if (body != 0)
|
|
|
|
|
{
|
|
|
|
|
*body = nil;
|
|
|
|
|
}
|
|
|
|
|
if (l == 0)
|
|
|
|
|
{
|
2011-10-08 17:53:17 +00:00
|
|
|
|
/* Add an empty line to the end of the current headers to force
|
2010-06-09 15:03:37 +00:00
|
|
|
|
* completion of header parsing.
|
2008-02-20 09:22:43 +00:00
|
|
|
|
*/
|
2010-06-09 15:03:37 +00:00
|
|
|
|
[self parseHeaders: [NSData dataWithBytes: "\r\n\r\n" length: 4]
|
2010-06-11 12:00:02 +00:00
|
|
|
|
remaining: 0];
|
2008-02-20 09:22:43 +00:00
|
|
|
|
flags.wantEndOfLine = 0;
|
2011-08-15 06:16:51 +00:00
|
|
|
|
flags.excessData = 0;
|
2008-02-20 09:22:43 +00:00
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1; /* Finished parsing */
|
|
|
|
|
return NO; /* Want no more data */
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-03 06:46:41 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'",
|
2020-11-20 06:42:33 +00:00
|
|
|
|
(unsigned)l, (unsigned)l, (unsigned)l, (char*)[d bytes]);
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
2010-06-09 15:03:37 +00:00
|
|
|
|
r = [self _endOfHeaders: d];
|
|
|
|
|
if (r.location == NSNotFound)
|
|
|
|
|
{
|
2013-04-22 10:27:07 +00:00
|
|
|
|
[data appendBytes: [d bytes] length: [d length]];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
bytes = (unsigned char*)[data bytes];
|
|
|
|
|
dataEnd = [data length];
|
2012-07-20 05:27:26 +00:00
|
|
|
|
/* Fall through to parse the headers so far.
|
2012-07-11 12:57:31 +00:00
|
|
|
|
*/
|
2010-06-09 15:03:37 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSUInteger i = NSMaxRange(r);
|
2011-10-08 17:53:17 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
i -= [data length]; // Bytes to append to headers
|
2010-06-09 17:34:19 +00:00
|
|
|
|
[data appendBytes: [d bytes] length: i];
|
|
|
|
|
bytes = (unsigned char*)[data bytes];
|
|
|
|
|
dataEnd = [data length];
|
2010-06-11 12:00:02 +00:00
|
|
|
|
if (l > i)
|
|
|
|
|
{
|
2013-04-22 10:27:07 +00:00
|
|
|
|
/* NB. Take care ... the data object we create does not own or
|
|
|
|
|
* free its storage.
|
|
|
|
|
*/
|
2010-06-11 12:00:02 +00:00
|
|
|
|
d = [[[NSData alloc] initWithBytesNoCopy: (void*)([d bytes] + i)
|
|
|
|
|
length: l - i
|
|
|
|
|
freeWhenDone: NO] autorelease];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
2010-06-09 15:03:37 +00:00
|
|
|
|
if (body != 0)
|
|
|
|
|
{
|
|
|
|
|
*body = d;
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
|
|
|
|
while (flags.inBody == 0)
|
|
|
|
|
{
|
2010-06-09 15:03:37 +00:00
|
|
|
|
NSString *header;
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
2010-06-09 15:03:37 +00:00
|
|
|
|
header = [self _decodeHeader];
|
|
|
|
|
if (header == nil)
|
|
|
|
|
{
|
|
|
|
|
if (1 == flags.hadErrors)
|
|
|
|
|
{
|
|
|
|
|
return NO; /* Couldn't handle words. */
|
|
|
|
|
}
|
|
|
|
|
else if (0 == flags.inBody)
|
|
|
|
|
{
|
|
|
|
|
return YES; /* need more data */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ([self parseHeader: header] == NO)
|
|
|
|
|
{
|
|
|
|
|
flags.hadErrors = 1;
|
|
|
|
|
return NO; /* Header not parsed properly. */
|
|
|
|
|
}
|
2008-02-20 09:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* All headers have been parsed, so we empty our internal buffer
|
2010-06-09 15:03:37 +00:00
|
|
|
|
* (which we will now use to store decoded data)
|
2008-02-20 09:22:43 +00:00
|
|
|
|
*/
|
|
|
|
|
[data setLength: 0];
|
2010-06-09 15:03:37 +00:00
|
|
|
|
bytes = 0;
|
|
|
|
|
input = 0;
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We have finished parsing the headers, but we may have http
|
|
|
|
|
* continuation header(s), in which case, we must start parsing
|
|
|
|
|
* headers again.
|
|
|
|
|
*/
|
2010-09-28 13:23:53 +00:00
|
|
|
|
info = [document _lastHeaderNamed: @"http"];
|
2008-02-20 09:22:43 +00:00
|
|
|
|
if (info != nil && flags.isHttp == 1)
|
|
|
|
|
{
|
2009-01-30 20:33:14 +00:00
|
|
|
|
NSNumber *num;
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
2009-01-30 20:33:14 +00:00
|
|
|
|
num = [info objectForKey: NSHTTPPropertyStatusCodeKey];
|
|
|
|
|
if (num != nil)
|
2008-02-20 09:22:43 +00:00
|
|
|
|
{
|
2009-01-30 20:33:14 +00:00
|
|
|
|
int v = [num intValue];
|
2008-02-20 09:22:43 +00:00
|
|
|
|
|
|
|
|
|
if (v >= 100 && v < 200)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This is an intermediary response ... so we have
|
|
|
|
|
* to restart the parsing operation!
|
|
|
|
|
*/
|
2013-07-03 06:46:41 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@", @"Parsed http continuation");
|
2008-02-20 09:22:43 +00:00
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
if ([d length] == 0)
|
|
|
|
|
{
|
|
|
|
|
/* We need more data, so we have to return YES
|
|
|
|
|
* to ask our caller to provide it.
|
|
|
|
|
*/
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return [self parseHeaders: d remaining: body];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If there is a zero content length, all parsing is complete,
|
|
|
|
|
* not just header parsing.
|
|
|
|
|
*/
|
2009-09-15 15:28:35 +00:00
|
|
|
|
if (flags.headersOnly == 1
|
|
|
|
|
|| ((hdr = [document headerNamed: @"content-length"]) != nil
|
|
|
|
|
&& [[hdr value] intValue] == 0))
|
2008-02-20 09:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
[document setContent: @""];
|
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1;
|
2009-09-15 15:28:35 +00:00
|
|
|
|
/* If we have more data after the headers ... it's excess and
|
|
|
|
|
* should become available as excess data.
|
|
|
|
|
*/
|
|
|
|
|
if ([d length] > 0)
|
|
|
|
|
{
|
2013-04-22 10:27:07 +00:00
|
|
|
|
/* NB. We must copy the bytes from 'd' as that object doesn't
|
|
|
|
|
* own its storage.
|
|
|
|
|
*/
|
|
|
|
|
RELEASE(boundary);
|
|
|
|
|
boundary = [[NSData alloc] initWithBytes: [d bytes]
|
|
|
|
|
length: [d length]];
|
2009-09-15 15:28:35 +00:00
|
|
|
|
flags.excessData = 1;
|
|
|
|
|
}
|
2008-02-20 09:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO; // No more data needed
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This method is called to parse a header line <em>for the
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* current document</em>, split its contents into a GSMimeHeader
|
2002-05-26 16:10:31 +00:00
|
|
|
|
* object, and add that information to the document.<br />
|
|
|
|
|
* The method is normally used internally by the -parse: method,
|
|
|
|
|
* but you may also call it to parse an entire header line and
|
|
|
|
|
* add it to the document (this may be useful in conjunction
|
|
|
|
|
* with the -expectNoHeaders method, to parse a document body data
|
|
|
|
|
* into a document where the headers are available from a
|
|
|
|
|
* separate source).
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
2002-05-26 16:10:31 +00:00
|
|
|
|
* <example>
|
|
|
|
|
* GSMimeParser *parser = [GSMimeParser mimeParser];
|
|
|
|
|
*
|
|
|
|
|
* [parser parseHeader: @"content-type: text/plain"];
|
|
|
|
|
* [parser expectNoHeaders];
|
|
|
|
|
* [parser parse: bodyData];
|
|
|
|
|
* [parser parse: nil];
|
|
|
|
|
* </example>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* <p>
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* The standard implementation of this method scans the header
|
|
|
|
|
* name and then calls -scanHeaderBody:into: to complete the
|
|
|
|
|
* parsing of the header.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* This method also performs consistency checks on headers scanned
|
|
|
|
|
* so it is recommended that it is not overridden, but that
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* subclasses override -scanHeaderBody:into: to
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* implement custom scanning.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* As a special case, for HTTP support, this method also parses
|
|
|
|
|
* lines in the format of HTTP responses as if they were headers
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* named <code>http</code>. The resulting header object contains
|
|
|
|
|
* additional object values -
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>HttpMajorVersion</term>
|
|
|
|
|
* <desc>The first part of the version number</desc>
|
|
|
|
|
* <term>HttpMinorVersion</term>
|
|
|
|
|
* <desc>The second part of the version number</desc>
|
|
|
|
|
* <term>NSHTTPPropertyServerHTTPVersionKey</term>
|
|
|
|
|
* <desc>The full HTTP protocol version number</desc>
|
|
|
|
|
* <term>NSHTTPPropertyStatusCodeKey</term>
|
2009-01-30 20:33:14 +00:00
|
|
|
|
* <desc>The HTTP status code (numeric)</desc>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* <term>NSHTTPPropertyStatusReasonKey</term>
|
|
|
|
|
* <desc>The text message (if any) after the status code</desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) parseHeader: (NSString*)aHeader
|
|
|
|
|
{
|
|
|
|
|
NSScanner *scanner = [NSScanner scannerWithString: aHeader];
|
|
|
|
|
NSString *name;
|
|
|
|
|
NSString *value;
|
2002-05-26 15:24:05 +00:00
|
|
|
|
GSMimeHeader *info;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2002-05-30 05:57:27 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Parse header - '%@'", aHeader);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Special case - permit web response status line to act like a header.
|
|
|
|
|
*/
|
2007-06-01 16:56:40 +00:00
|
|
|
|
if ([scanner scanString: @"HTTP/" intoString: &name] == YES)
|
|
|
|
|
{
|
|
|
|
|
name = @"HTTP";
|
|
|
|
|
}
|
|
|
|
|
else
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
if ([scanner scanUpToString: @":" intoString: &name] == NO)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Not a valid header (%@)", [scanner string]);
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* Position scanner after colon and any white space.
|
|
|
|
|
*/
|
|
|
|
|
if ([scanner scanString: @":" intoString: 0] == NO)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"No colon terminating name in header (%@)", [scanner string]);
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* Set the header name.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2024-11-27 16:25:08 +00:00
|
|
|
|
info = [GSMimeHeader headerWithName: name
|
|
|
|
|
value: nil
|
|
|
|
|
parameters: nil];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
name = [info name];
|
2005-02-22 11:22:44 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Break header fields out into info dictionary.
|
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
if ([self scanHeaderBody: scanner into: info] == NO)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check validity of broken-out header fields.
|
|
|
|
|
*/
|
|
|
|
|
if ([name isEqualToString: @"mime-version"] == YES)
|
|
|
|
|
{
|
|
|
|
|
int majv = 0;
|
|
|
|
|
int minv = 0;
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
value = [info value];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([value length] == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Missing value for mime-version header");
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2010-02-25 09:05:58 +00:00
|
|
|
|
if (sscanf([value UTF8String], "%d.%d", &majv, &minv) != 2)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-05-27 09:31:49 +00:00
|
|
|
|
NSLog(@"Bad value for mime-version header (%@)", value);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
[document deleteHeaderNamed: name]; // Should be unique
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
else if ([name isEqualToString: CteContentType] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-11-26 10:15:35 +00:00
|
|
|
|
NSString *tmp = [info parameterForKey: @"boundary"];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSString *type;
|
|
|
|
|
NSString *subtype;
|
|
|
|
|
|
|
|
|
|
DESTROY(boundary);
|
2002-11-26 10:15:35 +00:00
|
|
|
|
if (tmp != nil)
|
|
|
|
|
{
|
2010-02-25 18:34:49 +00:00
|
|
|
|
NSUInteger l = [tmp length];
|
2009-02-11 17:33:31 +00:00
|
|
|
|
unsigned char *b;
|
2002-11-26 10:15:35 +00:00
|
|
|
|
|
2010-02-25 08:19:52 +00:00
|
|
|
|
b = NSZoneMalloc(NSDefaultMallocZone(), l + 3);
|
2002-11-26 10:15:35 +00:00
|
|
|
|
b[0] = '-';
|
|
|
|
|
b[1] = '-';
|
2010-02-25 08:19:52 +00:00
|
|
|
|
[tmp getCString: (char*)&b[2]
|
2010-02-25 18:34:49 +00:00
|
|
|
|
maxLength: l + 1
|
|
|
|
|
encoding: NSISOLatin1StringEncoding];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
boundary = [[NSData alloc] initWithBytesNoCopy: b length: l + 2];
|
2002-11-26 10:15:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
type = [info objectForKey: @"Type"];
|
|
|
|
|
if ([type length] == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Missing Mime content-type");
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2002-11-28 13:24:32 +00:00
|
|
|
|
subtype = [info objectForKey: @"Subtype"];
|
2011-10-08 17:53:17 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([type isEqualToString: @"text"] == YES)
|
|
|
|
|
{
|
|
|
|
|
if (subtype == nil)
|
2002-06-18 10:41:29 +00:00
|
|
|
|
{
|
|
|
|
|
subtype = @"plain";
|
2011-03-09 10:24:18 +00:00
|
|
|
|
[info setObject: subtype forKey: @"Subtype"];
|
2002-06-18 10:41:29 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([type isEqualToString: @"multipart"] == YES)
|
|
|
|
|
{
|
2002-06-18 10:41:29 +00:00
|
|
|
|
if (subtype == nil)
|
|
|
|
|
{
|
|
|
|
|
subtype = @"mixed";
|
2011-03-09 10:24:18 +00:00
|
|
|
|
[info setObject: subtype forKey: @"Subtype"];
|
2002-06-18 10:41:29 +00:00
|
|
|
|
}
|
2002-11-26 10:15:35 +00:00
|
|
|
|
if (boundary == nil)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
NSLog(@"multipart message without boundary");
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-06-18 10:41:29 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (subtype == nil)
|
|
|
|
|
{
|
|
|
|
|
subtype = @"octet-stream";
|
2011-03-09 10:24:18 +00:00
|
|
|
|
[info setObject: subtype forKey: @"Subtype"];
|
2002-06-18 10:41:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
[document deleteHeaderNamed: name]; // Should be unique
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
[document addHeader: info];
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
return NO;
|
|
|
|
|
NS_ENDHANDLER
|
2002-05-30 05:57:27 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Header parsed - %@", info);
|
|
|
|
|
|
2002-05-27 14:03:10 +00:00
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This method is called to parse a header line and split its
|
2006-06-15 14:05:22 +00:00
|
|
|
|
* contents into the supplied [GSMimeHeader] instance.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
2006-06-15 14:05:22 +00:00
|
|
|
|
* On entry, the header (info) is already partially filled,
|
|
|
|
|
* the name is a lowercase representation of the
|
|
|
|
|
* header name. The the scanner must be set to a scan location
|
|
|
|
|
* immediately after the colon in the original header string
|
|
|
|
|
* (ie to the header value string).
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* If the header is parsed successfully, the method should
|
|
|
|
|
* return YES, otherwise NO.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
2006-06-15 14:05:22 +00:00
|
|
|
|
* You would not normally call this method directly yourself,
|
|
|
|
|
* but may override it to support parsing of new headers.<br />
|
|
|
|
|
* If you do call this yourself, you need to be aware that it
|
|
|
|
|
* may change the state of the document in the parser.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* You should be aware of the parsing that the standard
|
|
|
|
|
* implementation performs, and that <em>needs</em> to be
|
|
|
|
|
* done for certain headers in order to permit the parser to
|
|
|
|
|
* work generally -
|
|
|
|
|
* </p>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>content-disposition</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>Value</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* The content disposition (excluding parameters) as a
|
|
|
|
|
* lowercase string.
|
|
|
|
|
* </desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
* </desc>
|
|
|
|
|
* <term>content-type</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* <deflist>
|
2002-11-28 13:24:32 +00:00
|
|
|
|
* <term>Subtype</term>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* <desc>The MIME subtype lowercase</desc>
|
|
|
|
|
* <term>Type</term>
|
|
|
|
|
* <desc>The MIME type lowercase</desc>
|
|
|
|
|
* <term>value</term>
|
|
|
|
|
* <desc>The full MIME type (xxx/yyy) in lowercase</desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
* </desc>
|
|
|
|
|
* <term>content-transfer-encoding</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>Value</term>
|
|
|
|
|
* <desc>The transfer encoding type in lowercase</desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
* </desc>
|
|
|
|
|
* <term>http</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>HttpVersion</term>
|
|
|
|
|
* <desc>The HTTP protocol version number</desc>
|
|
|
|
|
* <term>HttpMajorVersion</term>
|
|
|
|
|
* <desc>The first component of the version number</desc>
|
|
|
|
|
* <term>HttpMinorVersion</term>
|
|
|
|
|
* <desc>The second component of the version number</desc>
|
|
|
|
|
* <term>HttpStatus</term>
|
|
|
|
|
* <desc>The response status value (numeric code)</desc>
|
|
|
|
|
* <term>Value</term>
|
|
|
|
|
* <desc>The text message (if any)</desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
* </desc>
|
|
|
|
|
* <term>transfer-encoding</term>
|
|
|
|
|
* <desc>
|
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>Value</term>
|
|
|
|
|
* <desc>The transfer encoding type in lowercase</desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
* </desc>
|
|
|
|
|
* </deflist>
|
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (BOOL) scanHeaderBody: (NSScanner*)scanner
|
|
|
|
|
into: (GSMimeHeader*)info
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
NSString *name = [info name];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSString *value = nil;
|
|
|
|
|
|
2002-05-30 05:57:27 +00:00
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Now see if we are interested in any of it.
|
|
|
|
|
*/
|
|
|
|
|
if ([name isEqualToString: @"http"] == YES)
|
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
int loc = [scanner scanLocation];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
int major;
|
|
|
|
|
int minor;
|
|
|
|
|
int status;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger count;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSArray *hdrs;
|
|
|
|
|
|
|
|
|
|
if ([scanner scanInt: &major] == NO || major < 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Bad value for http major version in %@", [scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if ([scanner scanString: @"." intoString: 0] == NO)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Bad format for http version in %@", [scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if ([scanner scanInt: &minor] == NO || minor < 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Bad value for http minor version in %@", [scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if ([scanner scanInt: &status] == NO || status < 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Bad value for http status in %@", [scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
[info setObject: [NSStringClass stringWithFormat: @"%d", minor]
|
2002-03-06 15:50:14 +00:00
|
|
|
|
forKey: @"HttpMinorVersion"];
|
2005-03-21 19:51:52 +00:00
|
|
|
|
[info setObject: [NSStringClass stringWithFormat: @"%d.%d", major, minor]
|
2002-03-06 15:50:14 +00:00
|
|
|
|
forKey: @"HttpVersion"];
|
2005-03-21 19:51:52 +00:00
|
|
|
|
[info setObject: [NSStringClass stringWithFormat: @"%d", major]
|
2002-03-06 15:50:14 +00:00
|
|
|
|
forKey: NSHTTPPropertyServerHTTPVersionKey];
|
2009-01-30 20:33:14 +00:00
|
|
|
|
[info setObject: [NSNumber numberWithInt: status]
|
2002-03-06 15:50:14 +00:00
|
|
|
|
forKey: NSHTTPPropertyStatusCodeKey];
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
value = [[scanner string] substringFromIndex: [scanner scanLocation]];
|
|
|
|
|
[info setObject: value
|
|
|
|
|
forKey: NSHTTPPropertyStatusReasonKey];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
value = [[scanner string] substringFromIndex: loc];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
2005-11-06 13:53:40 +00:00
|
|
|
|
* Get rid of preceding headers in case this is a continuation.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
hdrs = [document allHeaders];
|
|
|
|
|
for (count = 0; count < [hdrs count]; count++)
|
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
GSMimeHeader *h = [hdrs objectAtIndex: count];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
[document deleteHeader: h];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2002-05-27 08:28:28 +00:00
|
|
|
|
/*
|
|
|
|
|
* Mark to say we are parsing HTTP content
|
|
|
|
|
*/
|
|
|
|
|
[self setIsHttp];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([name isEqualToString: @"content-transfer-encoding"] == YES
|
|
|
|
|
|| [name isEqualToString: @"transfer-encoding"] == YES)
|
|
|
|
|
{
|
|
|
|
|
value = [self scanToken: scanner];
|
|
|
|
|
if ([value length] == 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Bad value for content-transfer-encoding header in %@",
|
|
|
|
|
[scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
value = [value lowercaseString];
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
else if ([name isEqualToString: CteContentType] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *type;
|
|
|
|
|
NSString *subtype = nil;
|
|
|
|
|
|
2002-05-30 05:57:27 +00:00
|
|
|
|
type = [self scanName: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([type length] == 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Invalid Mime content-type in %@", [scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
type = [type lowercaseString];
|
|
|
|
|
[info setObject: type forKey: @"Type"];
|
|
|
|
|
if ([scanner scanString: @"/" intoString: 0] == YES)
|
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
subtype = [self scanName: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([subtype length] == 0)
|
|
|
|
|
{
|
2009-07-31 04:32:46 +00:00
|
|
|
|
NSLog(@"Invalid Mime content-type (subtype) in %@",
|
|
|
|
|
[scanner string]);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
subtype = [subtype lowercaseString];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
[info setObject: subtype forKey: @"Subtype"];
|
2005-03-21 19:51:52 +00:00
|
|
|
|
value = [NSStringClass stringWithFormat: @"%@/%@", type, subtype];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
value = type;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[self _scanHeaderParameters: scanner into: info];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([name isEqualToString: @"content-disposition"] == YES)
|
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
value = [self scanName: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
value = [value lowercaseString];
|
|
|
|
|
/*
|
|
|
|
|
* Concatenate slash separated parts of field.
|
|
|
|
|
*/
|
|
|
|
|
while ([scanner scanString: @"/" intoString: 0] == YES)
|
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
NSString *sub = [self scanName: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
if ([sub length] > 0)
|
|
|
|
|
{
|
|
|
|
|
sub = [sub lowercaseString];
|
2005-03-21 19:51:52 +00:00
|
|
|
|
value = [NSStringClass stringWithFormat: @"%@/%@", value, sub];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Expect anything else to be 'name=value' parameters.
|
|
|
|
|
*/
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[self _scanHeaderParameters: scanner into: info];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2002-05-27 09:31:49 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int loc;
|
|
|
|
|
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
loc = [scanner scanLocation];
|
|
|
|
|
value = [[scanner string] substringFromIndex: loc];
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
if (value != nil)
|
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
[info setValue: value];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2005-02-22 11:22:44 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-30 05:57:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* A convenience method to use a scanner (that is set up to scan a
|
|
|
|
|
* header line) to scan a name - a simple word.
|
|
|
|
|
* <list>
|
|
|
|
|
* <item>Leading whitespace is ignored.</item>
|
|
|
|
|
* </list>
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) scanName: (NSScanner*)scanner
|
|
|
|
|
{
|
|
|
|
|
NSString *value;
|
|
|
|
|
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Scan value terminated by any MIME special character.
|
|
|
|
|
*/
|
|
|
|
|
if ([scanner scanUpToCharactersFromSet: rfc2045Specials
|
|
|
|
|
intoString: &value] == NO)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* A convenience method to scan past any whitespace in the scanner
|
|
|
|
|
* in preparation for scanning something more interesting that
|
|
|
|
|
* comes after it. Returns YES if any space was read, NO otherwise.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) scanPastSpace: (NSScanner*)scanner
|
|
|
|
|
{
|
|
|
|
|
NSCharacterSet *skip;
|
|
|
|
|
BOOL scanned;
|
|
|
|
|
|
|
|
|
|
skip = RETAIN([scanner charactersToBeSkipped]);
|
|
|
|
|
[scanner setCharactersToBeSkipped: nil];
|
2002-05-29 16:44:19 +00:00
|
|
|
|
scanned = [scanner scanCharactersFromSet: whitespace intoString: 0];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
[scanner setCharactersToBeSkipped: skip];
|
|
|
|
|
RELEASE(skip);
|
|
|
|
|
return scanned;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A convenience method to use a scanner (that is set up to scan a
|
|
|
|
|
* header line) to scan in a special character that terminated a
|
|
|
|
|
* token previously scanned. If the token was terminated by
|
|
|
|
|
* whitespace and no other special character, the string returned
|
|
|
|
|
* will contain a single space character.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) scanSpecial: (NSScanner*)scanner
|
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSCharacterSet *specials;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger location;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
unichar c;
|
|
|
|
|
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.isHttp == 1)
|
2002-05-28 11:30:15 +00:00
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
specials = rfc822Specials;
|
2002-05-28 11:30:15 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-05-29 16:56:58 +00:00
|
|
|
|
specials = rfc2045Specials;
|
2002-05-28 11:30:15 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Now return token delimiter (may be whitespace)
|
|
|
|
|
*/
|
|
|
|
|
location = [scanner scanLocation];
|
|
|
|
|
c = [[scanner string] characterAtIndex: location];
|
|
|
|
|
|
|
|
|
|
if ([specials characterIsMember: c] == YES)
|
|
|
|
|
{
|
|
|
|
|
[scanner setScanLocation: location + 1];
|
2005-03-21 19:51:52 +00:00
|
|
|
|
return [NSStringClass stringWithCharacters: &c length: 1];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return @" ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A convenience method to use a scanner (that is set up to scan a
|
|
|
|
|
* header line) to scan a header token - either a quoted string or
|
|
|
|
|
* a simple word.
|
|
|
|
|
* <list>
|
|
|
|
|
* <item>Leading whitespace is ignored.</item>
|
|
|
|
|
* <item>Backslash escapes in quoted text are converted</item>
|
|
|
|
|
* </list>
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) scanToken: (NSScanner*)scanner
|
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
[self scanPastSpace: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if ([scanner scanString: @"\"" intoString: 0] == YES) // Quoted
|
|
|
|
|
{
|
|
|
|
|
NSString *string = [scanner string];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger length = [string length];
|
|
|
|
|
NSUInteger start = [scanner scanLocation];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSRange r = NSMakeRange(start, length - start);
|
|
|
|
|
BOOL done = NO;
|
|
|
|
|
|
|
|
|
|
while (done == NO)
|
|
|
|
|
{
|
|
|
|
|
r = [string rangeOfString: @"\""
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
if (r.length == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Parsing header value - found unterminated quoted string");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
if ([string characterAtIndex: r.location - 1] == '\\')
|
|
|
|
|
{
|
2002-05-27 08:28:28 +00:00
|
|
|
|
int p;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Count number of escape ('\') characters ... if it's odd
|
|
|
|
|
* then the quote has been escaped and is not a closing
|
|
|
|
|
* quote.
|
|
|
|
|
*/
|
|
|
|
|
p = r.location;
|
|
|
|
|
while (p > 0 && [string characterAtIndex: p - 1] == '\\')
|
|
|
|
|
{
|
|
|
|
|
p--;
|
|
|
|
|
}
|
|
|
|
|
p = r.location - p;
|
|
|
|
|
if (p % 2 == 1)
|
|
|
|
|
{
|
|
|
|
|
r.location++;
|
|
|
|
|
r.length = length - r.location;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
done = YES;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
done = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-05-27 14:03:10 +00:00
|
|
|
|
[scanner setScanLocation: r.location + 1];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
length = r.location - start;
|
|
|
|
|
if (length == 0)
|
|
|
|
|
{
|
2011-10-08 17:53:17 +00:00
|
|
|
|
return @"";
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unichar buf[length];
|
|
|
|
|
unichar *src = buf;
|
|
|
|
|
unichar *dst = buf;
|
|
|
|
|
|
|
|
|
|
[string getCharacters: buf range: NSMakeRange(start, length)];
|
|
|
|
|
while (src < &buf[length])
|
|
|
|
|
{
|
|
|
|
|
if (*src == '\\')
|
|
|
|
|
{
|
|
|
|
|
src++;
|
2002-11-26 17:02:58 +00:00
|
|
|
|
if (flags.buggyQuotes == 1 && *src != '\\' && *src != '"')
|
|
|
|
|
{
|
|
|
|
|
*dst++ = '\\'; // Buggy use of escape in quotes.
|
|
|
|
|
}
|
2002-11-26 14:26:00 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*dst++ = *src++;
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
return [NSStringClass stringWithCharacters: buf length: dst - buf];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // Token
|
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSCharacterSet *specials;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSString *value;
|
|
|
|
|
|
2002-11-26 14:26:00 +00:00
|
|
|
|
if (flags.isHttp == 1)
|
2002-05-28 11:30:15 +00:00
|
|
|
|
{
|
2002-05-30 05:57:27 +00:00
|
|
|
|
specials = rfc822Specials;
|
2002-05-28 11:30:15 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-05-29 16:56:58 +00:00
|
|
|
|
specials = rfc2045Specials;
|
2002-05-28 11:30:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Move past white space.
|
|
|
|
|
*/
|
2002-05-29 16:48:10 +00:00
|
|
|
|
[self scanPastSpace: scanner];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Scan value terminated by any special character.
|
|
|
|
|
*/
|
|
|
|
|
if ([scanner scanUpToCharactersFromSet: specials
|
|
|
|
|
intoString: &value] == NO)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-11-26 14:26:00 +00:00
|
|
|
|
/**
|
|
|
|
|
* Method to inform the parser that the data it is parsing is likely to
|
|
|
|
|
* contain fields with buggy use of backslash quotes ... and it should
|
|
|
|
|
* try to be tolerant of them and treat them as is they were escaped
|
|
|
|
|
* backslashes. This is for use with things like microsoft internet
|
|
|
|
|
* explorer, which puts the backslashes used as file path separators
|
|
|
|
|
* in parameters without quoting them.
|
|
|
|
|
*/
|
|
|
|
|
- (void) setBuggyQuotes: (BOOL)flag
|
|
|
|
|
{
|
2002-11-26 17:02:58 +00:00
|
|
|
|
if (flag)
|
2002-11-26 14:26:00 +00:00
|
|
|
|
{
|
|
|
|
|
flags.buggyQuotes = 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
flags.buggyQuotes = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-03-06 09:01:17 +00:00
|
|
|
|
/** This is a method to inform the parser that body parts with no content-type
|
2004-05-30 09:05:10 +00:00
|
|
|
|
* header (which are treated as text/plain) should use the specified
|
2009-03-06 09:01:17 +00:00
|
|
|
|
* characterset rather than the default (us-ascii).<br />
|
|
|
|
|
* This also controls the parsing of headers ... in a legal MIME document
|
2013-09-08 20:18:57 +00:00
|
|
|
|
* these must consist solely of us-ascii characters, but setting a different
|
2009-03-06 09:01:17 +00:00
|
|
|
|
* default characterset (such as latin1) will permit many illegal header
|
|
|
|
|
* lines (produced by faulty mail clients) to be parsed.<br />
|
|
|
|
|
* HTTP requests use headers in the latin1 characterset, so this is the
|
|
|
|
|
* header line characterset used most commonly by faulty clients.
|
2004-05-30 09:05:10 +00:00
|
|
|
|
*/
|
|
|
|
|
- (void) setDefaultCharset: (NSString*)aName
|
|
|
|
|
{
|
2005-03-11 12:58:54 +00:00
|
|
|
|
_defaultEncoding = [documentClass encodingFromCharset: aName];
|
2005-03-22 09:16:22 +00:00
|
|
|
|
if (_defaultEncoding == 0)
|
|
|
|
|
{
|
|
|
|
|
_defaultEncoding = NSASCIIStringEncoding;
|
|
|
|
|
}
|
2004-05-30 09:05:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 15:28:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* Method to inform the parser that only the headers should be parsed
|
|
|
|
|
* and any remaining data be treated as excess
|
|
|
|
|
*/
|
|
|
|
|
- (void) setHeadersOnly
|
|
|
|
|
{
|
|
|
|
|
flags.headersOnly = 1;
|
|
|
|
|
}
|
2005-03-11 09:12:53 +00:00
|
|
|
|
|
2002-05-27 08:28:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Method to inform the parser that the data it is parsing is an HTTP
|
|
|
|
|
* document rather than true MIME. This method is called internally
|
|
|
|
|
* if the parser detects an HTTP response line at the start of the
|
|
|
|
|
* headers it is parsing.
|
|
|
|
|
*/
|
|
|
|
|
- (void) setIsHttp
|
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.isHttp = 1;
|
2002-05-27 08:28:28 +00:00
|
|
|
|
}
|
2007-05-14 16:55:16 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GSMimeParser (Private)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2004-05-30 09:05:10 +00:00
|
|
|
|
/*
|
|
|
|
|
* Make a new child to parse a subsidiary document
|
|
|
|
|
*/
|
|
|
|
|
- (void) _child
|
|
|
|
|
{
|
|
|
|
|
DESTROY(child);
|
|
|
|
|
child = [GSMimeParser new];
|
2013-09-09 09:40:03 +00:00
|
|
|
|
if (1 == flags.buggyQuotes)
|
2004-05-30 09:05:10 +00:00
|
|
|
|
{
|
|
|
|
|
[child setBuggyQuotes: YES];
|
|
|
|
|
}
|
2013-09-09 09:40:03 +00:00
|
|
|
|
if (1 == flags.isHttp)
|
|
|
|
|
{
|
|
|
|
|
[child setIsHttp];
|
|
|
|
|
}
|
2004-05-30 09:05:10 +00:00
|
|
|
|
/*
|
|
|
|
|
* Tell child parser the default encoding to use.
|
|
|
|
|
*/
|
|
|
|
|
child->_defaultEncoding = _defaultEncoding;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
/*
|
|
|
|
|
* Return YES if more data is needed, NO if the body has been completely
|
|
|
|
|
* parsed.
|
|
|
|
|
*/
|
|
|
|
|
- (BOOL) _decodeBody: (NSData*)d
|
2010-06-09 15:03:37 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSUInteger l = [d length];
|
|
|
|
|
BOOL needsMore = YES;
|
2010-06-09 15:03:37 +00:00
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
rawBodyLength += l;
|
2010-06-09 15:03:37 +00:00
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (context == nil)
|
2010-06-09 15:03:37 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
|
|
|
|
expect = 0;
|
|
|
|
|
/*
|
|
|
|
|
* Check for expected content length.
|
2010-06-09 15:03:37 +00:00
|
|
|
|
*/
|
2010-06-09 17:34:19 +00:00
|
|
|
|
hdr = [document headerNamed: @"content-length"];
|
|
|
|
|
if (hdr != nil)
|
|
|
|
|
{
|
|
|
|
|
expect = [[hdr value] intValue];
|
|
|
|
|
}
|
2010-06-09 15:03:37 +00:00
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
/*
|
|
|
|
|
* Set up context for decoding data.
|
|
|
|
|
*/
|
|
|
|
|
hdr = [document headerNamed: @"transfer-encoding"];
|
|
|
|
|
if (hdr == nil)
|
2010-06-09 15:03:37 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
hdr = [document headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
}
|
|
|
|
|
else if ([[[hdr value] lowercaseString] isEqualToString: @"chunked"])
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Chunked transfer encoding overrides any content length spec.
|
2010-06-09 15:03:37 +00:00
|
|
|
|
*/
|
2010-06-09 17:34:19 +00:00
|
|
|
|
expect = 0;
|
2010-06-09 15:03:37 +00:00
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
context = [self contextFor: hdr];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([context retain];)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Parse body expects %u bytes", expect);
|
2010-06-09 15:03:37 +00:00
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2013-07-03 06:46:41 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'",
|
2020-11-20 06:42:33 +00:00
|
|
|
|
(unsigned)l, (unsigned)l, (unsigned)l, (char*)[d bytes]);
|
2010-06-09 17:34:19 +00:00
|
|
|
|
// NSDebugMLLog(@"GSMime", @"Boundary - '%*.*s'", [boundary length], [boundary length], [boundary bytes]);
|
|
|
|
|
|
|
|
|
|
if ([context atEnd] == YES)
|
2010-06-09 15:03:37 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1;
|
|
|
|
|
if ([d length] > 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Additional data (%*.*s) ignored after parse complete",
|
2020-11-20 06:42:33 +00:00
|
|
|
|
(unsigned)[d length], (unsigned)[d length], (char*)[d bytes]);
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
|
|
|
|
needsMore = NO; /* Nothing more to do */
|
2010-06-09 15:03:37 +00:00
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
else if (boundary == nil)
|
2010-06-09 15:03:37 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
GSMimeHeader *typeInfo;
|
|
|
|
|
NSString *type;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
typeInfo = [document headerNamed: CteContentType];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
type = [typeInfo objectForKey: @"Type"];
|
|
|
|
|
if ([type isEqualToString: @"multipart"] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSLog(@"multipart decode attempt without boundary");
|
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1;
|
|
|
|
|
needsMore = NO;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSUInteger dLength = [d length];
|
|
|
|
|
|
|
|
|
|
if (expect > 0 && rawBodyLength > expect)
|
2004-05-26 13:45:37 +00:00
|
|
|
|
{
|
|
|
|
|
NSData *excess;
|
|
|
|
|
|
|
|
|
|
dLength -= (rawBodyLength - expect);
|
|
|
|
|
rawBodyLength = expect;
|
|
|
|
|
excess = [d subdataWithRange:
|
2004-05-26 16:55:10 +00:00
|
|
|
|
NSMakeRange(dLength, [d length] - dLength)];
|
2009-09-15 11:51:08 +00:00
|
|
|
|
ASSIGN(boundary, excess);
|
|
|
|
|
flags.excessData = 1;
|
2004-05-26 13:45:37 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
[self decodeData: d
|
2004-05-26 13:45:37 +00:00
|
|
|
|
fromRange: NSMakeRange(0, dLength)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
intoData: data
|
|
|
|
|
withContext: context];
|
|
|
|
|
|
|
|
|
|
if ([context atEnd] == YES
|
|
|
|
|
|| (expect > 0 && rawBodyLength >= expect))
|
|
|
|
|
{
|
2005-05-07 08:54:57 +00:00
|
|
|
|
NSString *subtype = [typeInfo objectForKey: @"Subtype"];
|
|
|
|
|
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.inBody = 0;
|
|
|
|
|
flags.complete = 1;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2013-07-03 06:46:41 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@", @"Parse body complete");
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
2002-05-27 14:03:10 +00:00
|
|
|
|
* If no content type is supplied, we assume text ... unless
|
|
|
|
|
* we have something that's known to be a file.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-27 14:03:10 +00:00
|
|
|
|
if (type == nil)
|
|
|
|
|
{
|
|
|
|
|
if ([document contentFile] != nil)
|
|
|
|
|
{
|
|
|
|
|
type = @"application";
|
2005-05-07 08:54:57 +00:00
|
|
|
|
subtype= @"octet-stream";
|
2002-05-27 14:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
type = @"text";
|
2005-05-07 08:54:57 +00:00
|
|
|
|
subtype= @"plain";
|
2002-05-27 14:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-27 13:35:45 +00:00
|
|
|
|
/* We assume any text data is best treated as a string
|
|
|
|
|
* unless it's some format we will probably be parsing.
|
|
|
|
|
*/
|
2005-05-07 08:54:57 +00:00
|
|
|
|
if ([type isEqualToString: @"text"] == YES
|
2015-08-27 13:35:45 +00:00
|
|
|
|
&& [subtype isEqualToString: @"xml"] == NO
|
|
|
|
|
&& [subtype isEqualToString: @"json"] == NO)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2005-05-07 08:54:57 +00:00
|
|
|
|
NSStringEncoding stringEncoding = _defaultEncoding;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
NSString *string;
|
|
|
|
|
|
2004-05-30 09:05:10 +00:00
|
|
|
|
if (typeInfo == nil)
|
|
|
|
|
{
|
2016-06-07 18:12:13 +00:00
|
|
|
|
typeInfo = [document setHeader: @"Content-Type"
|
|
|
|
|
value: @"text/plain"
|
|
|
|
|
parameters: nil];
|
2005-05-07 08:54:57 +00:00
|
|
|
|
[typeInfo setObject: type forKey: @"Type"];
|
|
|
|
|
[typeInfo setObject: subtype forKey: @"Subtype"];
|
2004-05-30 09:05:10 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSString *charset;
|
|
|
|
|
|
|
|
|
|
charset = [typeInfo parameterForKey: @"charset"];
|
2005-05-07 08:54:57 +00:00
|
|
|
|
if (charset != nil)
|
2005-05-04 17:19:11 +00:00
|
|
|
|
{
|
2005-05-07 08:54:57 +00:00
|
|
|
|
stringEncoding
|
|
|
|
|
= [documentClass encodingFromCharset: charset];
|
2005-05-04 17:19:11 +00:00
|
|
|
|
}
|
2005-05-07 08:54:57 +00:00
|
|
|
|
}
|
2005-05-04 17:19:11 +00:00
|
|
|
|
|
2005-05-07 08:54:57 +00:00
|
|
|
|
/*
|
|
|
|
|
* Ensure that the charset reflects the encoding used.
|
|
|
|
|
*/
|
|
|
|
|
if (stringEncoding != NSASCIIStringEncoding)
|
|
|
|
|
{
|
|
|
|
|
NSString *charset;
|
|
|
|
|
|
|
|
|
|
charset = [documentClass charsetFromEncoding:
|
|
|
|
|
stringEncoding];
|
|
|
|
|
[typeInfo setParameter: charset
|
|
|
|
|
forKey: @"charset"];
|
2004-05-30 09:05:10 +00:00
|
|
|
|
}
|
2005-05-07 08:54:57 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Assume that content type is best represented as NSString.
|
|
|
|
|
*/
|
2005-03-21 19:51:52 +00:00
|
|
|
|
string = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
string = [string initWithData: data
|
|
|
|
|
encoding: stringEncoding];
|
2005-05-04 17:19:11 +00:00
|
|
|
|
if (string == nil)
|
|
|
|
|
{
|
|
|
|
|
[document setContent: data]; // Can't make string
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[document setContent: string];
|
|
|
|
|
RELEASE(string);
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Assume that any non-text content type is best
|
|
|
|
|
* represented as NSData.
|
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
[document setContent: data];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2006-05-23 09:05:50 +00:00
|
|
|
|
needsMore = NO;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSUInteger bLength;
|
|
|
|
|
const unsigned char *bBytes;
|
|
|
|
|
unsigned char bInit;
|
|
|
|
|
const unsigned char *buf;
|
|
|
|
|
NSUInteger len;
|
|
|
|
|
BOOL done = NO;
|
|
|
|
|
BOOL endedFinalPart = NO;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
bLength = [boundary length];
|
|
|
|
|
bBytes = (const unsigned char*)[boundary bytes];
|
|
|
|
|
bInit = bBytes[0];
|
|
|
|
|
|
|
|
|
|
/* If we already have buffered data, append the new information
|
|
|
|
|
* so we have a single buffer to scan.
|
|
|
|
|
*/
|
|
|
|
|
if ([data length] > 0)
|
|
|
|
|
{
|
|
|
|
|
[data appendData: d];
|
|
|
|
|
bytes = (unsigned char*)[data mutableBytes];
|
|
|
|
|
dataEnd = [data length];
|
|
|
|
|
d = data;
|
|
|
|
|
}
|
|
|
|
|
buf = (const unsigned char*)[d bytes];
|
|
|
|
|
len = [d length];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
while (done == NO)
|
|
|
|
|
{
|
2011-08-15 08:59:52 +00:00
|
|
|
|
BOOL found = NO;
|
|
|
|
|
NSUInteger eol = len;
|
2003-11-25 13:19:41 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
2010-06-09 17:34:19 +00:00
|
|
|
|
* Search data for the next boundary.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2010-06-09 17:34:19 +00:00
|
|
|
|
while (len - lineStart >= bLength)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (buf[lineStart] == bInit
|
|
|
|
|
&& memcmp(&buf[lineStart], bBytes, bLength) == 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (lineStart == 0 || buf[lineStart-1] == '\r'
|
|
|
|
|
|| buf[lineStart-1] == '\n')
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
lineEnd = lineStart + bLength;
|
2003-11-25 13:19:41 +00:00
|
|
|
|
eol = lineEnd;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (lineEnd + 2 <= len && buf[lineEnd] == '-'
|
|
|
|
|
&& buf[lineEnd+1] == '-')
|
2002-11-01 18:20:34 +00:00
|
|
|
|
{
|
2012-12-12 09:30:19 +00:00
|
|
|
|
/* The final boundary (shown by the trailng '--').
|
|
|
|
|
* Any data after this should be ignored.
|
|
|
|
|
* NB. careful reading of section 7.2.1 of RFC1341
|
|
|
|
|
* reveals that the final boundary does NOT include
|
|
|
|
|
* a trailing CRLF (but that excess data after the
|
|
|
|
|
* final boundary is to be ignored).
|
|
|
|
|
*/
|
2003-11-25 13:19:41 +00:00
|
|
|
|
eol += 2;
|
|
|
|
|
flags.wantEndOfLine = 0;
|
2012-12-12 09:30:19 +00:00
|
|
|
|
endedFinalPart = YES;
|
2003-11-25 13:19:41 +00:00
|
|
|
|
found = YES;
|
2002-11-01 18:20:34 +00:00
|
|
|
|
}
|
2012-12-12 09:30:19 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Ignore space/tab characters after boundary marker
|
|
|
|
|
* and before crlf. Strictly this is wrong ... but
|
|
|
|
|
* at least one mailer generates bogus whitespace.
|
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
while (eol < len && isWSP(buf[eol]))
|
2012-12-12 09:30:19 +00:00
|
|
|
|
{
|
|
|
|
|
eol++;
|
|
|
|
|
}
|
|
|
|
|
if (eol < len && buf[eol] == '\r')
|
|
|
|
|
{
|
|
|
|
|
eol++;
|
|
|
|
|
}
|
|
|
|
|
if (eol < len && buf[eol] == '\n')
|
|
|
|
|
{
|
|
|
|
|
eol++;
|
|
|
|
|
flags.wantEndOfLine = 0;
|
|
|
|
|
found = YES;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
flags.wantEndOfLine = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
lineStart++;
|
|
|
|
|
}
|
2003-11-25 13:19:41 +00:00
|
|
|
|
if (found == NO)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
/* Need more data ... so, if we have none buffered we must
|
|
|
|
|
* buffer any unused data, otherwise we can copy data within
|
|
|
|
|
* the buffer.
|
|
|
|
|
*/
|
|
|
|
|
if ([data length] == 0)
|
|
|
|
|
{
|
|
|
|
|
[data appendBytes: buf + sectionStart
|
|
|
|
|
length: len - sectionStart];
|
|
|
|
|
sectionStart = lineStart = 0;
|
|
|
|
|
bytes = (unsigned char*)[data mutableBytes];
|
|
|
|
|
dataEnd = [data length];
|
|
|
|
|
}
|
|
|
|
|
else if (sectionStart > 0)
|
|
|
|
|
{
|
|
|
|
|
len -= sectionStart;
|
|
|
|
|
memcpy(bytes, buf + sectionStart, len);
|
|
|
|
|
sectionStart = lineStart = 0;
|
|
|
|
|
[data setLength: len];
|
|
|
|
|
dataEnd = len;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
done = YES; /* Needs more data. */
|
2005-02-22 11:22:44 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
else if (child == nil)
|
|
|
|
|
{
|
2004-05-30 09:05:10 +00:00
|
|
|
|
NSString *cset;
|
2011-10-08 17:53:17 +00:00
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/*
|
|
|
|
|
* Found boundary at the start of the first section.
|
|
|
|
|
* Set sectionStart to point immediately after boundary.
|
|
|
|
|
*/
|
|
|
|
|
lineStart += bLength;
|
|
|
|
|
sectionStart = lineStart;
|
2004-05-30 09:05:10 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If we have an explicit character set for the multipart
|
|
|
|
|
* document, we set it as the default characterset inherited
|
|
|
|
|
* by any child documents.
|
|
|
|
|
*/
|
2017-06-30 06:37:05 +00:00
|
|
|
|
cset = [[document headerNamed: CteContentType]
|
2004-05-30 09:05:10 +00:00
|
|
|
|
parameterForKey: @"charset"];
|
|
|
|
|
if (cset != nil)
|
2002-11-26 17:02:58 +00:00
|
|
|
|
{
|
2004-05-30 09:05:10 +00:00
|
|
|
|
[self setDefaultCharset: cset];
|
2002-11-26 17:02:58 +00:00
|
|
|
|
}
|
2004-05-30 09:05:10 +00:00
|
|
|
|
|
|
|
|
|
[self _child];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSData *childBody;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Found boundary at the end of a section.
|
|
|
|
|
* Skip past line terminator for boundary at start of section
|
|
|
|
|
* or past marker for end of multipart document.
|
|
|
|
|
*/
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (buf[sectionStart] == '-' && sectionStart < len
|
|
|
|
|
&& buf[sectionStart+1] == '-')
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
sectionStart += 2;
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (buf[sectionStart] == '\r')
|
2002-05-27 15:35:54 +00:00
|
|
|
|
{
|
|
|
|
|
sectionStart++;
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (buf[sectionStart] == '\n')
|
2002-05-27 15:35:54 +00:00
|
|
|
|
{
|
|
|
|
|
sectionStart++;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create data object for this section and pass it to the
|
2002-05-27 08:52:09 +00:00
|
|
|
|
* child parser to deal with. NB. As lineStart points to
|
|
|
|
|
* the start of the end boundary, we need to step back to
|
|
|
|
|
* before the end of line introducing it in order to have
|
|
|
|
|
* the correct length of body data for the child document.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-27 08:52:09 +00:00
|
|
|
|
pos = lineStart;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (pos > 0 && buf[pos-1] == '\n')
|
2002-05-27 08:52:09 +00:00
|
|
|
|
{
|
|
|
|
|
pos--;
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (pos > 0 && buf[pos-1] == '\r')
|
2002-05-27 08:52:09 +00:00
|
|
|
|
{
|
|
|
|
|
pos--;
|
|
|
|
|
}
|
2010-06-11 10:49:21 +00:00
|
|
|
|
/* Since we know the child can't modify it, and we know
|
|
|
|
|
* that we aren't going to change the buffer while the
|
|
|
|
|
* child is using it, we can safely pass a data object
|
|
|
|
|
* which simply references the memory in our own buffer.
|
|
|
|
|
*/
|
|
|
|
|
childBody = [[NSData alloc]
|
|
|
|
|
initWithBytesNoCopy: (void*)(buf + sectionStart)
|
|
|
|
|
length: pos - sectionStart
|
|
|
|
|
freeWhenDone: NO];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if ([child parse: childBody] == YES)
|
2002-05-26 15:24:05 +00:00
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* The parser wants more data, so pass a nil data item
|
|
|
|
|
* to tell it that it has had all there is.
|
|
|
|
|
*/
|
|
|
|
|
[child parse: nil];
|
|
|
|
|
}
|
2010-06-11 10:49:21 +00:00
|
|
|
|
[childBody release];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
if ([child isComplete] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *doc;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Store the document produced by the child, and
|
|
|
|
|
* create a new parser for the next section.
|
|
|
|
|
*/
|
2002-05-27 08:52:09 +00:00
|
|
|
|
doc = [child mimeDocument];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
if (doc != nil)
|
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
[document addContent: doc];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2004-05-30 09:05:10 +00:00
|
|
|
|
[self _child];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Section failed to decode properly!
|
|
|
|
|
*/
|
|
|
|
|
NSLog(@"Failed to decode section of multipart");
|
2004-05-30 09:05:10 +00:00
|
|
|
|
[self _child];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update parser data.
|
|
|
|
|
*/
|
|
|
|
|
lineStart += bLength;
|
|
|
|
|
sectionStart = lineStart;
|
2002-11-01 18:20:34 +00:00
|
|
|
|
if (endedFinalPart == YES)
|
|
|
|
|
{
|
2011-08-15 08:59:52 +00:00
|
|
|
|
if (eol < len)
|
|
|
|
|
{
|
|
|
|
|
NSData *excess;
|
|
|
|
|
|
|
|
|
|
excess = [[NSData alloc] initWithBytes: buf + eol
|
|
|
|
|
length: len - eol];
|
|
|
|
|
ASSIGN(boundary, excess);
|
|
|
|
|
flags.excessData = 1;
|
|
|
|
|
[excess release];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
lineStart = sectionStart = 0;
|
|
|
|
|
[data setLength: 0];
|
2002-11-01 18:20:34 +00:00
|
|
|
|
done = YES;
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*
|
2002-11-01 18:20:34 +00:00
|
|
|
|
* Check to see if we have reached content length or ended multipart
|
|
|
|
|
* document.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-11-01 18:20:34 +00:00
|
|
|
|
if (endedFinalPart == YES || (expect > 0 && rawBodyLength >= expect))
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-11-26 14:26:00 +00:00
|
|
|
|
flags.complete = 1;
|
|
|
|
|
flags.inBody = 0;
|
2006-05-23 09:05:50 +00:00
|
|
|
|
needsMore = NO;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2006-05-23 09:05:50 +00:00
|
|
|
|
return needsMore;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-06-09 17:34:19 +00:00
|
|
|
|
static const unsigned char *
|
|
|
|
|
unfold(const unsigned char *src, const unsigned char *end, BOOL *folded)
|
|
|
|
|
{
|
|
|
|
|
BOOL startOfLine = YES;
|
|
|
|
|
|
|
|
|
|
*folded = NO;
|
|
|
|
|
|
|
|
|
|
if (src >= end)
|
|
|
|
|
{
|
|
|
|
|
/* Not enough data to tell whether this is a header end or
|
|
|
|
|
* just a folded header ... need to get more input.
|
|
|
|
|
*/
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 19:27:28 +00:00
|
|
|
|
while (src < end && isspace(*src))
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
|
|
|
|
if (*src == '\r' || *src == '\n')
|
|
|
|
|
{
|
|
|
|
|
if (YES == startOfLine)
|
|
|
|
|
{
|
|
|
|
|
return src; // Pointer to line after headers
|
|
|
|
|
}
|
|
|
|
|
if (*src == '\r')
|
|
|
|
|
{
|
|
|
|
|
if (src + 1 >= end)
|
|
|
|
|
{
|
|
|
|
|
return 0; // Need more data (linefeed expected)
|
|
|
|
|
}
|
|
|
|
|
if (src[1] == '\n')
|
|
|
|
|
{
|
|
|
|
|
src++; // Step past carriage return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Step after end of line and look for fold (leading whitespace)
|
|
|
|
|
* or blank line (end of headers), or new data.
|
|
|
|
|
*/
|
|
|
|
|
src++;
|
|
|
|
|
startOfLine = YES;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
startOfLine = NO;
|
|
|
|
|
}
|
|
|
|
|
if (src >= end)
|
|
|
|
|
{
|
|
|
|
|
return 0; // Need more data
|
|
|
|
|
}
|
|
|
|
|
if (NO == startOfLine)
|
|
|
|
|
{
|
|
|
|
|
*folded = YES;
|
|
|
|
|
}
|
|
|
|
|
return src; // Pointer to first non-space data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This method takes the raw data of an unfolded header line, and handles
|
|
|
|
|
* RFC2047 word encoding in the header by creating a
|
|
|
|
|
* string containing the decoded words.
|
|
|
|
|
* Strictly speaking, the header should be plain ASCII data with escapes
|
|
|
|
|
* for non-ascii characters, but for the sake of fault tolerance, we also
|
|
|
|
|
* attempt to use the default encoding currently set for the document,
|
|
|
|
|
* and if that fails we try UTF8. Only if none of these works do we
|
|
|
|
|
* assume that the header is corrupt/unparsable.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) _decodeHeader
|
|
|
|
|
{
|
|
|
|
|
NSStringEncoding enc;
|
|
|
|
|
WE encoding;
|
|
|
|
|
unsigned char c;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
NSMutableString *hdr = nil;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
NSString *s;
|
|
|
|
|
const unsigned char *beg = &bytes[input];
|
|
|
|
|
const unsigned char *end = &bytes[dataEnd];
|
|
|
|
|
const unsigned char *src = beg;
|
|
|
|
|
|
|
|
|
|
while (src < end)
|
|
|
|
|
{
|
|
|
|
|
if (src[0] == '\n'
|
|
|
|
|
|| (src[0] == '\r' && src+1 < end && src[1] == '\n')
|
|
|
|
|
|| (src[0] == '=' && src+1 < end && src[1] == '?'))
|
|
|
|
|
{
|
|
|
|
|
/* Append any accumulated text to the header.
|
|
|
|
|
*/
|
|
|
|
|
if (src > beg)
|
|
|
|
|
{
|
2013-09-19 04:37:34 +00:00
|
|
|
|
s = nil;
|
2013-09-08 21:02:48 +00:00
|
|
|
|
if (1 == flags.isHttp)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2013-09-09 06:50:42 +00:00
|
|
|
|
/* Old web code tends to use latin1 (and RFCs say we
|
|
|
|
|
* should use latin1 for headers). However newer systems
|
2013-09-09 08:22:51 +00:00
|
|
|
|
* tend to use utf-8. We try any explicitly set encoding,
|
|
|
|
|
* then the modern utf-8, and finally fall back to latin1.
|
2013-09-09 06:50:42 +00:00
|
|
|
|
*/
|
2013-09-09 08:22:51 +00:00
|
|
|
|
if (NSUTF8StringEncoding != _defaultEncoding)
|
2013-09-09 06:50:42 +00:00
|
|
|
|
{
|
2013-09-09 08:22:51 +00:00
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
s = [s initWithBytes: beg
|
|
|
|
|
length: src - beg
|
|
|
|
|
encoding: _defaultEncoding];
|
|
|
|
|
}
|
|
|
|
|
if (nil == s)
|
|
|
|
|
{
|
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2013-09-09 06:50:42 +00:00
|
|
|
|
s = [s initWithBytes: beg
|
|
|
|
|
length: src - beg
|
|
|
|
|
encoding: NSUTF8StringEncoding];
|
|
|
|
|
}
|
2013-09-09 08:22:51 +00:00
|
|
|
|
if (nil == s)
|
2013-09-09 06:50:42 +00:00
|
|
|
|
{
|
2013-09-09 08:22:51 +00:00
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2013-09-09 06:50:42 +00:00
|
|
|
|
s = [s initWithBytes: beg
|
|
|
|
|
length: src - beg
|
|
|
|
|
encoding: NSISOLatin1StringEncoding];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-09-19 04:37:34 +00:00
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
s = [s initWithBytes: beg
|
|
|
|
|
length: src - beg
|
|
|
|
|
encoding: NSASCIIStringEncoding];
|
2013-09-09 08:22:51 +00:00
|
|
|
|
if (nil == s && _defaultEncoding != NSASCIIStringEncoding)
|
|
|
|
|
{
|
|
|
|
|
/* The parser has been explicitly set to accept an
|
|
|
|
|
* alternative coding ... Try the encoding we were
|
|
|
|
|
* given.
|
2013-09-08 20:52:12 +00:00
|
|
|
|
*/
|
2013-09-09 08:22:51 +00:00
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
s = [s initWithBytes: beg
|
|
|
|
|
length: src - beg
|
|
|
|
|
encoding: _defaultEncoding];
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-07-11 12:57:31 +00:00
|
|
|
|
if (nil == s)
|
|
|
|
|
{
|
2013-05-01 10:11:07 +00:00
|
|
|
|
NSLog(@"Bad header ... illegal characters in %@",
|
|
|
|
|
[NSData dataWithBytes: beg length: src - beg]);
|
2012-07-11 12:57:31 +00:00
|
|
|
|
flags.hadErrors = 1;
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (nil == hdr)
|
|
|
|
|
{
|
|
|
|
|
hdr = AUTORELEASE([s mutableCopy]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[hdr appendString: s];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
RELEASE(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ('=' == src[0])
|
|
|
|
|
{
|
|
|
|
|
const unsigned char *tmp;
|
|
|
|
|
|
|
|
|
|
src += 2;
|
|
|
|
|
tmp = src;
|
2016-01-21 19:27:28 +00:00
|
|
|
|
while (tmp < end && *tmp != '?' && !isspace(*tmp))
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
|
|
|
|
tmp++;
|
|
|
|
|
}
|
|
|
|
|
if (tmp >= end) return nil;
|
|
|
|
|
if (*tmp != '?')
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad encoded word - character set terminator missing");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
s = [s initWithBytes: src
|
|
|
|
|
length: tmp - src
|
|
|
|
|
encoding: NSUTF8StringEncoding];
|
|
|
|
|
enc = [documentClass encodingFromCharset: s];
|
|
|
|
|
RELEASE(s);
|
|
|
|
|
|
|
|
|
|
src = tmp + 1;
|
|
|
|
|
if (src >= end) return nil;
|
2019-02-14 11:19:33 +00:00
|
|
|
|
c = toupper(*src);
|
|
|
|
|
if (c == 'B')
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
|
|
|
|
encoding = WE_BASE64;
|
|
|
|
|
}
|
2019-02-14 11:19:33 +00:00
|
|
|
|
else if (c == 'Q')
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
|
|
|
|
encoding = WE_QUOTED;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad encoded word - content type unknown");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
if (src >= end) return nil;
|
|
|
|
|
if (*src != '?')
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad encoded word - content type terminator missing");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
src++;
|
|
|
|
|
if (src >= end) return nil;
|
|
|
|
|
tmp = src;
|
2016-01-21 19:27:28 +00:00
|
|
|
|
while (tmp < end && *tmp != '?' && !isspace(*tmp))
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
|
|
|
|
tmp++;
|
|
|
|
|
}
|
|
|
|
|
if (tmp+1 >= end) return nil;
|
|
|
|
|
if (tmp[0] != '?' || tmp[1] != '=')
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad encoded word - data terminator missing");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2014-02-03 08:25:33 +00:00
|
|
|
|
/* If we are expecting to have white space after an encoded
|
|
|
|
|
* word, we must get rid of it between words.
|
|
|
|
|
*/
|
|
|
|
|
if (1 == flags.encodedWord && expect > 0)
|
|
|
|
|
{
|
|
|
|
|
[hdr deleteCharactersInRange:
|
|
|
|
|
NSMakeRange([hdr length] - expect, expect)];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
/* If the data part is not empty, decode it and append to header.
|
|
|
|
|
*/
|
|
|
|
|
if (tmp > src)
|
|
|
|
|
{
|
2024-11-15 12:20:05 +00:00
|
|
|
|
unsigned char buf[tmp - src + 1];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
unsigned char *ptr;
|
|
|
|
|
|
|
|
|
|
ptr = decodeWord(buf, src, tmp, encoding);
|
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
s = [s initWithBytes: buf
|
|
|
|
|
length: ptr - buf
|
|
|
|
|
encoding: enc];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (nil == hdr)
|
|
|
|
|
{
|
|
|
|
|
hdr = AUTORELEASE([s mutableCopy]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[hdr appendString: s];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
RELEASE(s);
|
|
|
|
|
}
|
|
|
|
|
/* Point past end to continue parsing.
|
|
|
|
|
*/
|
|
|
|
|
src = tmp + 2;
|
|
|
|
|
beg = src;
|
2014-02-03 08:25:33 +00:00
|
|
|
|
flags.encodedWord = 1; // We just parsed an encoded word
|
|
|
|
|
expect = 0; // No space expected after word yet
|
2010-06-09 17:34:19 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BOOL folded;
|
|
|
|
|
|
|
|
|
|
if (src[0] == '\r')
|
|
|
|
|
src++;
|
|
|
|
|
src++;
|
|
|
|
|
if ([hdr length] == 0)
|
|
|
|
|
{
|
|
|
|
|
/* Nothing in this header ... it's the empty line
|
|
|
|
|
* between headers and body.
|
|
|
|
|
*/
|
|
|
|
|
flags.inBody = 1;
|
2014-02-03 08:25:33 +00:00
|
|
|
|
flags.encodedWord = 0;
|
|
|
|
|
expect = 0;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
input = src - bytes;
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
src = unfold(src, end, &folded);
|
|
|
|
|
if (src == 0)
|
|
|
|
|
{
|
|
|
|
|
return nil; // need more data
|
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (nil == hdr)
|
|
|
|
|
{
|
|
|
|
|
hdr = [NSMutableString stringWithCapacity: 1];
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
if (NO == folded)
|
|
|
|
|
{
|
|
|
|
|
/* End of line ... return this header.
|
|
|
|
|
*/
|
|
|
|
|
input = src - bytes;
|
2014-02-03 08:25:33 +00:00
|
|
|
|
flags.encodedWord = 0;
|
|
|
|
|
expect = 0;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
/* Folded line ... add space at fold and continue parsing.
|
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (YES == oldStyleFolding)
|
|
|
|
|
{
|
|
|
|
|
/* Old style ... any fold is at a space.
|
|
|
|
|
*/
|
|
|
|
|
[hdr appendString: @" "];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Modern style ... exact whitespace character is
|
|
|
|
|
* preserved.
|
|
|
|
|
*/
|
|
|
|
|
if (' ' == src[-1])
|
|
|
|
|
{
|
|
|
|
|
[hdr appendString: @" "];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[hdr appendString: @"\t"];
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-03 10:23:41 +00:00
|
|
|
|
if (1 == flags.encodedWord)
|
|
|
|
|
{
|
|
|
|
|
/* NB Space is ignored between encoded words;
|
|
|
|
|
* count expected space but don't reset flag.
|
|
|
|
|
*/
|
|
|
|
|
expect++;
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
beg = src;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-03 08:25:33 +00:00
|
|
|
|
else if (1 == flags.encodedWord)
|
|
|
|
|
{
|
2016-01-21 19:27:28 +00:00
|
|
|
|
if (isspace(src[0]))
|
2014-02-03 08:25:33 +00:00
|
|
|
|
{
|
|
|
|
|
expect++; // Count expected space after word
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
flags.encodedWord = 0; // No longer in encoded word
|
|
|
|
|
expect = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
src++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Need more data.
|
|
|
|
|
*/
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Scan the provided data for an empty line (a CRLF immediately followed
|
|
|
|
|
* by another CRLF). Return the range of the empty line or a zero length
|
|
|
|
|
* range at index NSNotFound.<br />
|
|
|
|
|
* Permits a bare LF as a line terminator for maximum compatibility.<br />
|
|
|
|
|
* Also checks for an empty line overlapping the existing data and the
|
2011-08-15 06:16:51 +00:00
|
|
|
|
* new data.<br />
|
|
|
|
|
* Also, handles the special case of an empty line and no further headers.
|
2010-06-09 17:34:19 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSRange) _endOfHeaders: (NSData*)newData
|
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
unsigned int ol = [data length];
|
|
|
|
|
unsigned int nl = [newData length];
|
|
|
|
|
unsigned int len = ol + nl;
|
|
|
|
|
unsigned int pos = ol;
|
|
|
|
|
const unsigned char *op = (const unsigned char*)[data bytes];
|
|
|
|
|
const unsigned char *np = (const unsigned char*)[newData bytes];
|
|
|
|
|
char c;
|
|
|
|
|
|
|
|
|
|
#define C(X) ((X) < ol ? op[(X)] : np[(X)-ol])
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if (ol > 0)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
/* Find the start of any trailing CRLF or LF sequence we have already
|
|
|
|
|
* checked.
|
|
|
|
|
*/
|
|
|
|
|
while (pos > 0)
|
|
|
|
|
{
|
|
|
|
|
c = C(pos - 1);
|
|
|
|
|
if (c != '\r' && c != '\n')
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
pos--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for a document with no headers
|
|
|
|
|
*/
|
|
|
|
|
if (0 == pos)
|
|
|
|
|
{
|
|
|
|
|
if (len < 1)
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(NSNotFound, 0);
|
|
|
|
|
}
|
|
|
|
|
c = C(0);
|
|
|
|
|
if ('\n' == c)
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(0, 1); // no headers ... just an LF.
|
|
|
|
|
}
|
|
|
|
|
if (len < 2)
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(NSNotFound, 0);
|
|
|
|
|
}
|
|
|
|
|
if ('\r' == c && '\n' == C(1))
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(0, 2); // no headers ... just a CRLF.
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
/* Now check for pairs of line ends overlapping the old and new data
|
|
|
|
|
*/
|
|
|
|
|
if (pos < ol)
|
|
|
|
|
{
|
|
|
|
|
if (pos + 2 >= len)
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(NSNotFound, 0);
|
|
|
|
|
}
|
|
|
|
|
c = C(pos);
|
|
|
|
|
if ('\n' == c)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
char c1 = C(pos + 1);
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if ('\n' == c1)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
return NSMakeRange(pos, 2); // LFLF
|
|
|
|
|
}
|
|
|
|
|
if ('\r' == c1 && pos + 3 <= len && '\n' == C(pos + 2))
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(pos, 3); // LFCRLF
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
}
|
|
|
|
|
else if ('\r' == c)
|
|
|
|
|
{
|
|
|
|
|
char c1 = C(pos + 1);
|
|
|
|
|
|
|
|
|
|
if ('\n' == c1 && pos + 3 <= len)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
char c2 = C(pos + 2);
|
|
|
|
|
|
|
|
|
|
if ('\n' == c2)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
return NSMakeRange(pos, 3); // CRLFLF
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if ('\r' == c2 && pos + 4 <= len && '\n' == C(pos + 3))
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
return NSMakeRange(pos, 4); // CRLFCRLF
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
}
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
/* Now check for end of headers in new data.
|
|
|
|
|
*/
|
|
|
|
|
pos = 0;
|
|
|
|
|
while (pos + 2 <= nl)
|
|
|
|
|
{
|
|
|
|
|
c = np[pos];
|
|
|
|
|
if ('\n' == c)
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
char c1 = np[pos + 1];
|
2010-06-09 17:34:19 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if ('\n' == c1)
|
2011-08-14 18:38:13 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
return NSMakeRange(pos + ol, 2); // LFLF
|
|
|
|
|
}
|
|
|
|
|
if ('\r' == c1 && pos + 3 <= nl && '\n' == np[pos + 2])
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(pos + ol, 3); // LFCRLF
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ('\r' == c)
|
|
|
|
|
{
|
|
|
|
|
char c1 = np[pos + 1];
|
2011-08-14 20:49:16 +00:00
|
|
|
|
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if ('\n' == c1 && pos + 3 <= nl)
|
|
|
|
|
{
|
|
|
|
|
char c2 = np[pos + 2];
|
|
|
|
|
|
|
|
|
|
if ('\n' == c2)
|
|
|
|
|
{
|
|
|
|
|
return NSMakeRange(pos + ol, 3); // CRLFLF
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
if ('\r' == c2 && pos + 4 <= nl && '\n' == np[pos + 3])
|
2010-06-09 17:34:19 +00:00
|
|
|
|
{
|
2011-08-15 06:16:51 +00:00
|
|
|
|
return NSMakeRange(pos + ol, 4); // CRLFCRLF
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
pos++;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-08-15 06:16:51 +00:00
|
|
|
|
pos++;
|
2010-06-09 17:34:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSMakeRange(NSNotFound, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info
|
|
|
|
|
{
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
while ([scanner scanString: @";" intoString: 0] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSString *paramName;
|
|
|
|
|
|
2002-05-30 05:57:27 +00:00
|
|
|
|
paramName = [self scanName: scanner];
|
2002-05-28 11:30:15 +00:00
|
|
|
|
if ([paramName length] == 0)
|
|
|
|
|
{
|
2009-11-10 09:14:57 +00:00
|
|
|
|
NSLog(@"Invalid Mime %@ field (parameter name) at %@",
|
|
|
|
|
[info name], [scanner string]);
|
2002-05-28 11:30:15 +00:00
|
|
|
|
return NO;
|
|
|
|
|
}
|
2002-05-30 05:57:27 +00:00
|
|
|
|
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
if ([scanner scanString: @"=" intoString: 0] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSString *paramValue;
|
|
|
|
|
|
|
|
|
|
paramValue = [self scanToken: scanner];
|
|
|
|
|
[self scanPastSpace: scanner];
|
|
|
|
|
if (paramValue == nil)
|
|
|
|
|
{
|
|
|
|
|
paramValue = @"";
|
|
|
|
|
}
|
|
|
|
|
[info setParameter: paramValue forKey: paramName];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Ignoring Mime %@ field parameter (%@)",
|
|
|
|
|
[info name], paramName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
2010-01-11 12:38:37 +00:00
|
|
|
|
@interface _GSMutableInsensitiveDictionary : NSMutableDictionary
|
|
|
|
|
@end
|
|
|
|
|
|
2002-05-26 05:45:36 +00:00
|
|
|
|
@implementation GSMimeHeader
|
|
|
|
|
|
|
|
|
|
static NSCharacterSet *nonToken = nil;
|
|
|
|
|
static NSCharacterSet *tokenSet = nil;
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
+ (void) _defaultsChanged: (NSNotification*)n
|
|
|
|
|
{
|
|
|
|
|
oldStyleFolding = [[NSUserDefaults standardUserDefaults]
|
|
|
|
|
boolForKey: @"GSMimeOldStyleFolding"];
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
+ (GSMimeHeader*) headerWithName: (NSString*)n
|
|
|
|
|
value: (NSString*)v
|
|
|
|
|
parameters: (NSDictionary*)p
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *h;
|
|
|
|
|
|
|
|
|
|
h = [[self alloc] initWithName: n value: v parameters: p];
|
|
|
|
|
return AUTORELEASE(h);
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 05:45:36 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
|
|
|
|
if (nonToken == nil)
|
|
|
|
|
{
|
|
|
|
|
NSMutableCharacterSet *ms;
|
|
|
|
|
|
|
|
|
|
ms = [NSMutableCharacterSet new];
|
|
|
|
|
[ms addCharactersInRange: NSMakeRange(33, 126-32)];
|
|
|
|
|
[ms removeCharactersInString: @"()<>@,;:\\\"/[]?="];
|
|
|
|
|
tokenSet = [ms copy];
|
2013-08-22 15:44:54 +00:00
|
|
|
|
[[NSObject leakAt: &tokenSet] release];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
RELEASE(ms);
|
|
|
|
|
nonToken = RETAIN([tokenSet invertedSet]);
|
2013-08-22 15:44:54 +00:00
|
|
|
|
[[NSObject leakAt: &nonToken] release];
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if (NSArrayClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSArrayClass = [NSArray class];
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if (NSStringClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSStringClass = [NSString class];
|
|
|
|
|
}
|
2005-03-21 19:16:15 +00:00
|
|
|
|
if (documentClass == 0)
|
|
|
|
|
{
|
|
|
|
|
documentClass = [GSMimeDocument class];
|
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
|
|
|
|
selector: @selector(_defaultsChanged:)
|
|
|
|
|
name: NSUserDefaultsDidChangeNotification
|
|
|
|
|
object: nil];
|
|
|
|
|
[self _defaultsChanged: nil];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-11-25 18:00:55 +00:00
|
|
|
|
* Makes the value into a quoted string if necessary (ie if it contains
|
|
|
|
|
* any special / non-token characters). If flag is YES then the value
|
|
|
|
|
* is made into a quoted string even if it does not contain special characters.
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
2002-11-25 18:00:55 +00:00
|
|
|
|
+ (NSString*) makeQuoted: (NSString*)v always: (BOOL)flag
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
|
|
|
|
NSRange r;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos = 0;
|
|
|
|
|
NSUInteger l = [v length];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
|
|
|
|
|
r = [v rangeOfCharacterFromSet: nonToken
|
|
|
|
|
options: NSLiteralSearch
|
2011-02-11 11:09:55 +00:00
|
|
|
|
range: NSMakeRange(0, l)];
|
2002-11-25 18:00:55 +00:00
|
|
|
|
if (flag == YES || r.length > 0)
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableString *m = [NSMutableString new];
|
|
|
|
|
|
|
|
|
|
[m appendString: @"\""];
|
|
|
|
|
while (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
unichar c;
|
|
|
|
|
|
|
|
|
|
if (r.location > pos)
|
|
|
|
|
{
|
|
|
|
|
[m appendString:
|
2002-12-03 02:50:07 +00:00
|
|
|
|
[v substringWithRange: NSMakeRange(pos, r.location - pos)]];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
pos = r.location + 1;
|
|
|
|
|
c = [v characterAtIndex: r.location];
|
|
|
|
|
if (c < 128)
|
|
|
|
|
{
|
2002-07-03 11:42:02 +00:00
|
|
|
|
if (c == '\\' || c == '"')
|
|
|
|
|
{
|
|
|
|
|
[m appendFormat: @"\\%c", c];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[m appendFormat: @"%c", c];
|
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"NON ASCII characters not yet implemented");
|
|
|
|
|
}
|
|
|
|
|
r = [v rangeOfCharacterFromSet: nonToken
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(pos, l - pos)];
|
|
|
|
|
}
|
2002-11-25 18:00:55 +00:00
|
|
|
|
if (l > pos)
|
|
|
|
|
{
|
|
|
|
|
[m appendString:
|
2002-12-03 02:50:07 +00:00
|
|
|
|
[v substringWithRange: NSMakeRange(pos, l - pos)]];
|
2002-11-25 18:00:55 +00:00
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
[m appendString: @"\""];
|
|
|
|
|
v = AUTORELEASE(m);
|
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2007-05-14 16:55:16 +00:00
|
|
|
|
* Convert the supplied string to a standardized token by removing
|
|
|
|
|
* all illegal characters. If preserve is NO then the result is
|
|
|
|
|
* converted to lowercase.<br />
|
|
|
|
|
* Returns an autoreleased (and possibly modified) copy of the original.
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
2007-05-14 16:55:16 +00:00
|
|
|
|
+ (NSString*) makeToken: (NSString*)t preservingCase: (BOOL)preserve
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
NSMutableString *m = nil;
|
|
|
|
|
NSRange r;
|
2002-05-26 05:45:36 +00:00
|
|
|
|
|
|
|
|
|
r = [t rangeOfCharacterFromSet: nonToken];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
m = [t mutableCopy];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
while (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
[m deleteCharactersInRange: r];
|
|
|
|
|
r = [m rangeOfCharacterFromSet: nonToken];
|
|
|
|
|
}
|
2007-05-14 16:55:16 +00:00
|
|
|
|
t = m;
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
2007-05-15 12:37:13 +00:00
|
|
|
|
if (preserve == NO)
|
2007-05-14 16:55:16 +00:00
|
|
|
|
{
|
|
|
|
|
t = [t lowercaseString];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
t = AUTORELEASE([t copy]);
|
|
|
|
|
}
|
|
|
|
|
TEST_RELEASE(m);
|
2002-05-26 05:45:36 +00:00
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
2007-05-14 16:55:16 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convert the supplied string to a standardized token by making it
|
|
|
|
|
* lowercase and removing all illegal characters.
|
|
|
|
|
*/
|
|
|
|
|
+ (NSString*) makeToken: (NSString*)t
|
|
|
|
|
{
|
|
|
|
|
return [self makeToken: t preservingCase: NO];
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 11:32:31 +00:00
|
|
|
|
/* Used to put headers in the HTTP requests we generate.
|
|
|
|
|
* If masked is non-NULL then the data object it points to is modified to
|
|
|
|
|
* make it a version of the request with authenticiation hidden (for debug
|
|
|
|
|
* logging). If the receiver is an authentication header and masked is not
|
|
|
|
|
* NULL but points to a nil object, an autoreleased mutable data object is
|
|
|
|
|
* created to hold the debug information.
|
|
|
|
|
*/
|
|
|
|
|
- (void) addToBuffer: (NSMutableData*)buf
|
|
|
|
|
masking: (NSMutableData**)masked
|
|
|
|
|
{
|
|
|
|
|
NSUInteger pos = [buf length];
|
|
|
|
|
BOOL maskThis = NO;
|
|
|
|
|
|
|
|
|
|
if (masked)
|
|
|
|
|
{
|
|
|
|
|
NSString *n = [self name];
|
|
|
|
|
|
|
|
|
|
if ([n isEqualToString: @"authorization"])
|
|
|
|
|
{
|
|
|
|
|
NSUInteger len = [*masked length];
|
|
|
|
|
|
|
|
|
|
maskThis = YES;
|
|
|
|
|
if (0 == len)
|
|
|
|
|
{
|
|
|
|
|
*masked = AUTORELEASE([buf mutableCopy]);
|
|
|
|
|
}
|
|
|
|
|
else if (len < pos)
|
|
|
|
|
{
|
|
|
|
|
[*masked appendBytes: [buf bytes] + len
|
|
|
|
|
length: pos - len];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-02 15:40:44 +00:00
|
|
|
|
[self rawMimeDataPreservingCase: YES foldedAt: 0 to: buf];
|
2022-05-20 11:32:31 +00:00
|
|
|
|
if (masked && *masked)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger len = [buf length];
|
|
|
|
|
const uint8_t *from = [buf bytes];
|
|
|
|
|
|
|
|
|
|
if (maskThis)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *to;
|
|
|
|
|
uint8_t c;
|
|
|
|
|
|
|
|
|
|
[*masked setLength: len];
|
|
|
|
|
to = [*masked mutableBytes];
|
|
|
|
|
memcpy(to + pos, from + pos, 14); // Authorization:
|
|
|
|
|
pos += 14;
|
|
|
|
|
|
|
|
|
|
/* Show spaces before scheme
|
|
|
|
|
*/
|
|
|
|
|
while (pos < len && isspace((c = from[pos])))
|
|
|
|
|
{
|
|
|
|
|
to[pos++] = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Show authorisation scheme
|
|
|
|
|
*/
|
|
|
|
|
while (pos < len && ((c = from[pos]) == '-' || isalnum(c)))
|
|
|
|
|
{
|
|
|
|
|
to[pos++] = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Show spaces after scheme
|
|
|
|
|
*/
|
|
|
|
|
while (pos < len && isspace((c = from[pos])))
|
|
|
|
|
{
|
|
|
|
|
to[pos++] = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mask out everything apart from line wrapping/termination.
|
|
|
|
|
*/
|
|
|
|
|
while (pos < len)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = from[pos];
|
|
|
|
|
|
|
|
|
|
if (c != '\n' && c != '\r' && c != '\t')
|
|
|
|
|
{
|
|
|
|
|
c = '*';
|
|
|
|
|
}
|
|
|
|
|
to[pos++] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[*masked appendBytes: from + pos
|
|
|
|
|
length: len - pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 16:10:31 +00:00
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
GSMimeHeader *c;
|
2002-05-26 16:10:31 +00:00
|
|
|
|
NSEnumerator *e;
|
|
|
|
|
NSString *k;
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
c = [[self class] allocWithZone: z];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
c = [c initWithName: [self namePreservingCase: YES]
|
2002-05-26 16:10:31 +00:00
|
|
|
|
value: [self value]
|
2007-05-14 16:55:16 +00:00
|
|
|
|
parameters: [self parametersPreservingCase: YES]];
|
2002-05-26 16:10:31 +00:00
|
|
|
|
e = [objects keyEnumerator];
|
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[c setObject: [self objectForKey: k] forKey: k];
|
|
|
|
|
}
|
|
|
|
|
return c;
|
2015-08-29 15:05:15 +00:00
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
RELEASE(name);
|
2016-08-03 09:24:53 +00:00
|
|
|
|
RELEASE(lower);
|
2002-05-26 15:24:05 +00:00
|
|
|
|
RELEASE(value);
|
2007-05-14 16:55:16 +00:00
|
|
|
|
TEST_RELEASE(objects);
|
|
|
|
|
TEST_RELEASE(params);
|
2002-05-26 15:24:05 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
2022-05-20 11:32:31 +00:00
|
|
|
|
NSString *desc = [super description];
|
|
|
|
|
NSString *n = [self name];
|
|
|
|
|
NSString *v = [self value];
|
2015-08-29 16:52:31 +00:00
|
|
|
|
NSDictionary *p = [self parameters];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
|
2022-05-20 11:32:31 +00:00
|
|
|
|
if ([n isEqualToString: @"authorization"])
|
2015-08-29 16:52:31 +00:00
|
|
|
|
{
|
2022-05-20 11:32:31 +00:00
|
|
|
|
NSRange r = [v rangeOfCharacterFromSet: whitespace];
|
|
|
|
|
NSString *scheme;
|
|
|
|
|
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
scheme = [v substringToIndex: r.location];
|
|
|
|
|
v = [v substringFromIndex: NSMaxRange(r)];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
scheme = v;
|
|
|
|
|
v = @"";
|
|
|
|
|
}
|
|
|
|
|
if ([p count] > 0)
|
|
|
|
|
{
|
|
|
|
|
if ([scheme caseInsensitiveCompare: @"digest"] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
NSMutableDictionary *m = AUTORELEASE([p mutableCopy]);
|
|
|
|
|
|
|
|
|
|
/* For a digest, it's good for debug to be able to see the
|
|
|
|
|
* digest parameters. Only the 'response' is sensitive.
|
|
|
|
|
*/
|
|
|
|
|
[m setObject: @"masked" forKey: @"response"];
|
|
|
|
|
v = [v stringByTrimmingSpaces];
|
|
|
|
|
if ([v length] > 0)
|
|
|
|
|
{
|
|
|
|
|
v = @"value-masked";
|
|
|
|
|
}
|
|
|
|
|
desc = [NSString stringWithFormat: @"%@ %@: %@ params: %@",
|
|
|
|
|
desc, n, v, m];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
desc = [NSString stringWithFormat: @"%@ %@: value-masked",
|
|
|
|
|
desc, n];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
desc = [NSString stringWithFormat: @"%@ %@: value-masked",
|
|
|
|
|
desc, n];
|
|
|
|
|
}
|
2015-08-29 16:52:31 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-05-20 11:32:31 +00:00
|
|
|
|
if ([p count] > 0)
|
|
|
|
|
{
|
|
|
|
|
desc = [NSString stringWithFormat: @"%@ %@: %@ params: %@",
|
|
|
|
|
desc, n, v, p];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
desc = [NSString stringWithFormat: @"%@ %@: %@",
|
|
|
|
|
desc, n, v];
|
|
|
|
|
}
|
2015-08-29 16:52:31 +00:00
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
return desc;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
- (NSUInteger) estimatedSize
|
|
|
|
|
{
|
|
|
|
|
return ([name length] + [value length] + [params count] * 20) + 6;
|
|
|
|
|
}
|
|
|
|
|
|
2007-05-15 08:36:23 +00:00
|
|
|
|
/** Returns the full value of the header including any parameters and
|
|
|
|
|
* preserving case. This is an unfolded (long) line with no escape
|
|
|
|
|
* sequences (ie contains a unicode string not necessarily plain ASCII).<br />
|
|
|
|
|
* If you just want the plain value excluding any parameters, use the
|
|
|
|
|
* -value method instead.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) fullValue
|
|
|
|
|
{
|
|
|
|
|
if ([params count] > 0)
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *m;
|
|
|
|
|
NSEnumerator *e;
|
|
|
|
|
NSString *k;
|
|
|
|
|
|
2016-03-10 16:50:41 +00:00
|
|
|
|
m = AUTORELEASE([value mutableCopy]);
|
2007-05-15 08:36:23 +00:00
|
|
|
|
e = [params keyEnumerator];
|
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
NSString *v;
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
v = [[self class] makeQuoted: [params objectForKey: k] always: NO];
|
2007-05-15 08:36:23 +00:00
|
|
|
|
[m appendString: @"; "];
|
|
|
|
|
[m appendString: k];
|
|
|
|
|
[m appendString: @"="];
|
|
|
|
|
[m appendString: v];
|
|
|
|
|
}
|
2016-03-10 16:50:41 +00:00
|
|
|
|
if (YES == [m makeImmutable])
|
|
|
|
|
{
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
return AUTORELEASE([m copy]);
|
2007-05-15 08:36:23 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-10 09:28:15 +00:00
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return [lower hash];
|
2010-06-10 09:28:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
return [self initWithName: @"unknown" value: @"none" parameters: nil];
|
|
|
|
|
}
|
|
|
|
|
|
2002-10-02 12:23:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method calling -initWithName:value:parameters: with the
|
|
|
|
|
* supplied argument and nil parameters.
|
|
|
|
|
*/
|
|
|
|
|
- (id) initWithName: (NSString*)n
|
|
|
|
|
value: (NSString*)v
|
|
|
|
|
{
|
|
|
|
|
return [self initWithName: n value: v parameters: nil];
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
/**
|
|
|
|
|
* <init />
|
|
|
|
|
* Initialise a GSMimeHeader supplying a name, a value and a dictionary
|
|
|
|
|
* of any parameters occurring after the value.
|
|
|
|
|
*/
|
|
|
|
|
- (id) initWithName: (NSString*)n
|
|
|
|
|
value: (NSString*)v
|
|
|
|
|
parameters: (NSDictionary*)p
|
|
|
|
|
{
|
2024-11-27 16:25:08 +00:00
|
|
|
|
n = [[self class] makeToken: n preservingCase: YES];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if ([n length] == 0)
|
|
|
|
|
{
|
|
|
|
|
n = @"unknown";
|
|
|
|
|
}
|
|
|
|
|
ASSIGN(name, n);
|
2017-06-30 06:37:05 +00:00
|
|
|
|
if ([CteContentType caseInsensitiveCompare: name] == NSOrderedSame)
|
2016-08-03 09:24:53 +00:00
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
n = CteContentType;
|
2016-08-03 09:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([@"content-transfer-encoding" caseInsensitiveCompare: name]
|
|
|
|
|
== NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
n = @"content-transfer-encoding";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
n = [name lowercaseString];
|
|
|
|
|
}
|
|
|
|
|
ASSIGN(lower, n);
|
|
|
|
|
|
|
|
|
|
if (nil != v)
|
|
|
|
|
{
|
|
|
|
|
[self setValue: v];
|
|
|
|
|
}
|
|
|
|
|
if (nil != p)
|
|
|
|
|
{
|
|
|
|
|
[self setParameters: p];
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-10 09:28:15 +00:00
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
|
|
|
{
|
|
|
|
|
if (other == self)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
2024-11-27 16:25:08 +00:00
|
|
|
|
if (NO == [other isKindOfClass: [self class]])
|
2010-06-10 09:28:15 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [[self name] isEqual: [other name]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [[self value] isEqual: [other value]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [[self parameters] isEqual: [other parameters]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 05:45:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns the name of this header ... a lowercase string.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) name
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return lower;
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the name of this header as originally set (without conversion
|
|
|
|
|
* to lowercase) if preserve is YES, but as a lowercase string if preserve
|
|
|
|
|
* is NO.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) namePreservingCase: (BOOL)preserve
|
|
|
|
|
{
|
|
|
|
|
if (preserve == YES)
|
|
|
|
|
{
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return lower;
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return extra information specific to a particular header type.
|
|
|
|
|
*/
|
|
|
|
|
- (id) objectForKey: (NSString*)k
|
|
|
|
|
{
|
|
|
|
|
return [objects objectForKey: k];
|
|
|
|
|
}
|
|
|
|
|
|
2002-10-02 12:23:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns a dictionary of all the additional objects for the header.
|
|
|
|
|
*/
|
2002-05-26 16:10:31 +00:00
|
|
|
|
- (NSDictionary*) objects
|
|
|
|
|
{
|
|
|
|
|
return AUTORELEASE([objects copy]);
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return the named parameter value.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) parameterForKey: (NSString*)k
|
|
|
|
|
{
|
|
|
|
|
NSString *p = [params objectForKey: k];
|
|
|
|
|
|
|
|
|
|
if (p == nil)
|
|
|
|
|
{
|
2024-11-27 16:25:08 +00:00
|
|
|
|
k = [[self class] makeToken: k];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
p = [params objectForKey: k];
|
|
|
|
|
}
|
2007-05-14 16:55:16 +00:00
|
|
|
|
return p;
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 05:45:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns the parameters of this header ... a dictionary whose keys
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* are all lowercase strings, and whose values are strings which may
|
2002-05-26 05:45:36 +00:00
|
|
|
|
* contain mixed case.
|
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (NSDictionary*) parameters
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
return [self parametersPreservingCase: NO];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the parameters of this header ... a dictionary whose keys
|
|
|
|
|
* are strings preserving the case originally used to set the values
|
|
|
|
|
* or all lowercase depending on the preserve argument.
|
|
|
|
|
*/
|
|
|
|
|
- (NSDictionary*) parametersPreservingCase: (BOOL)preserve
|
|
|
|
|
{
|
|
|
|
|
NSMutableDictionary *m;
|
|
|
|
|
NSEnumerator *e;
|
|
|
|
|
NSString *k;
|
|
|
|
|
|
|
|
|
|
m = [NSMutableDictionary dictionaryWithCapacity: [params count]];
|
2007-05-16 08:02:05 +00:00
|
|
|
|
e = [params keyEnumerator];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (preserve == YES)
|
|
|
|
|
{
|
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[m setObject: [params objectForKey: k] forKey: k];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[m setObject: [params objectForKey: k] forKey: [k lowercaseString]];
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-10 16:50:41 +00:00
|
|
|
|
if (YES == [m makeImmutable])
|
|
|
|
|
{
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
return AUTORELEASE([m copy]);
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
/* Given a byte buffer and a minimum position in the buffer,
|
|
|
|
|
* return the first white space found before the starting position.
|
|
|
|
|
* If no white space is found, return NSNotFound.
|
|
|
|
|
*/
|
|
|
|
|
static NSUInteger
|
|
|
|
|
lastWhiteSpace(const uint8_t *ptr, NSUInteger minimum, NSUInteger from)
|
|
|
|
|
{
|
|
|
|
|
while (from-- > minimum)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = ptr[from];
|
|
|
|
|
|
|
|
|
|
if (' ' == c || '\t' == c)
|
|
|
|
|
{
|
|
|
|
|
return from;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NSNotFound;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 22:20:53 +00:00
|
|
|
|
static char* _charsToEncode = "()<>@,;:_\"/[]?.=";
|
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
static NSUInteger
|
|
|
|
|
quotableLength(const uint8_t *ptr, NSUInteger size, NSUInteger max,
|
2019-02-15 14:13:14 +00:00
|
|
|
|
NSUInteger *quotedLength, BOOL utf8)
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
|
|
|
|
NSUInteger encoded;
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
|
|
|
|
|
for (encoded = index = 0; index < size; index++)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = ptr[index];
|
|
|
|
|
|
2016-01-21 22:20:53 +00:00
|
|
|
|
if (c < 32 || c >= 127 || strchr(_charsToEncode, c))
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
2019-02-15 14:13:14 +00:00
|
|
|
|
if (encoded + 3 > max)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
encoded += 3;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (encoded >= max)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
encoded++;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
}
|
2019-02-15 14:13:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (YES == utf8 && index < size)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = ptr[index];
|
|
|
|
|
|
|
|
|
|
/* We are breaking up a utf-8 string, so we must make sure
|
|
|
|
|
* we don't break inside a character.
|
|
|
|
|
*/
|
|
|
|
|
if ((c & 0xc0) == 0x80)
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
2019-02-15 14:13:14 +00:00
|
|
|
|
/* The next byte is a continuation byte, so we must be
|
|
|
|
|
* inside a utf-8 codepoint and need to step back out
|
|
|
|
|
* of it.
|
|
|
|
|
*/
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
encoded -= 3;
|
|
|
|
|
c = ptr[--index];
|
|
|
|
|
}
|
|
|
|
|
while ((c & 0xc0) == 0x80);
|
2016-01-21 12:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*quotedLength = encoded;
|
|
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
quotedWord(const uint8_t *ptr, NSUInteger size, uint8_t *buffer)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger encoded = 0;
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
|
|
|
|
|
for (index = 0; index < size; index++)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = ptr[index];
|
|
|
|
|
|
|
|
|
|
if (' ' == c)
|
|
|
|
|
{
|
|
|
|
|
buffer[encoded++] = '_';
|
|
|
|
|
}
|
2016-01-21 22:20:53 +00:00
|
|
|
|
else if (c < 32 || c >= 127 || strchr(_charsToEncode, c))
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
|
|
|
|
buffer[encoded++] = '=';
|
|
|
|
|
buffer[encoded++] = hex[c>>4];
|
|
|
|
|
buffer[encoded++] = hex[c&15];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
buffer[encoded++] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-02-25 08:19:52 +00:00
|
|
|
|
static NSUInteger
|
|
|
|
|
appendBytes(NSMutableData *m, NSUInteger offset, NSUInteger fold,
|
|
|
|
|
const char *bytes, NSUInteger size)
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2012-11-27 16:13:25 +00:00
|
|
|
|
if (fold > 0 && offset + size > fold && size + 8 <= fold)
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
|
|
|
|
/* This would take the line beyond the folding limit,
|
|
|
|
|
* so we fold at this point.
|
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (YES == oldStyleFolding)
|
2008-02-16 09:30:06 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
NSUInteger len = [m length];
|
|
|
|
|
|
|
|
|
|
/* If we already have space at the end of the line,
|
|
|
|
|
* we remove it because the wrapping counts as a space.
|
|
|
|
|
*/
|
2016-01-21 19:27:28 +00:00
|
|
|
|
if (len > 0 && isspace(((const uint8_t *)[m bytes])[len - 1]))
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
|
|
|
|
[m setLength: --len];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Folding results in a follow-on line starting with white space
|
|
|
|
|
*/
|
|
|
|
|
[m appendBytes: "\r\n\t" length: 3];
|
|
|
|
|
offset = 8;
|
2016-01-21 19:27:28 +00:00
|
|
|
|
if (size > 0 && isspace(bytes[0]))
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
|
|
|
|
/* The folding counts as a space character,
|
|
|
|
|
* so we refrain from writing the next character
|
|
|
|
|
* if it is also a space.
|
|
|
|
|
*/
|
|
|
|
|
size--;
|
|
|
|
|
bytes++;
|
|
|
|
|
}
|
2008-02-16 09:30:06 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
else
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
uint8_t wsp;
|
|
|
|
|
uint8_t buf[3];
|
|
|
|
|
|
2016-02-18 15:52:00 +00:00
|
|
|
|
/* Modern folding preserves exact whitespace characters.
|
2007-11-28 13:09:58 +00:00
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (size > 0 && isWSP(bytes[0]))
|
|
|
|
|
{
|
|
|
|
|
/* Next char is whitespace, so we fold before it.
|
|
|
|
|
*/
|
|
|
|
|
wsp = bytes[0];
|
|
|
|
|
bytes++;
|
|
|
|
|
size--;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSUInteger len = [m length];
|
|
|
|
|
|
|
|
|
|
/* We are expecting white space to be present after the
|
|
|
|
|
* last word (because we didn't find it before the next
|
|
|
|
|
* one). If it's there, we need to step back so we have
|
|
|
|
|
* it after the CRLF.
|
|
|
|
|
*/
|
|
|
|
|
wsp = ' ';
|
|
|
|
|
if (len > 0)
|
|
|
|
|
{
|
|
|
|
|
const uint8_t *ptr = [m bytes];
|
|
|
|
|
|
|
|
|
|
len--;
|
|
|
|
|
if (isWSP(ptr[len]))
|
|
|
|
|
{
|
|
|
|
|
wsp = ptr[len];
|
|
|
|
|
[m setLength: len];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now we append the CRLF and first whitespace character on
|
|
|
|
|
* the new line, and record the current character position.
|
|
|
|
|
*/
|
|
|
|
|
buf[0] = '\r';
|
|
|
|
|
buf[1] = '\n';
|
|
|
|
|
buf[2] = wsp;
|
|
|
|
|
[m appendBytes: buf length: 3];
|
|
|
|
|
if ('\t' == wsp)
|
|
|
|
|
{
|
|
|
|
|
offset = 8;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
offset = 1;
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (size > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Append the supplied byte data and update the offset
|
|
|
|
|
* on the current line.
|
|
|
|
|
*/
|
|
|
|
|
[m appendBytes: bytes length: size];
|
|
|
|
|
offset += size;
|
|
|
|
|
}
|
|
|
|
|
return offset;
|
|
|
|
|
}
|
|
|
|
|
|
2010-02-25 08:19:52 +00:00
|
|
|
|
static NSUInteger
|
|
|
|
|
appendString(NSMutableData *m, NSUInteger offset, NSUInteger fold,
|
2007-11-28 13:09:58 +00:00
|
|
|
|
NSString *str, BOOL *ok)
|
|
|
|
|
{
|
2014-03-11 09:46:54 +00:00
|
|
|
|
NSUInteger pos = 0;
|
|
|
|
|
BOOL hadEncodedWord = NO;
|
2007-11-28 13:09:58 +00:00
|
|
|
|
|
|
|
|
|
*ok = YES;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
|
|
|
|
if (YES == oldStyleFolding)
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2016-02-25 11:47:49 +00:00
|
|
|
|
NSUInteger size = [str length];
|
|
|
|
|
BOOL needSpace = NO;
|
2007-11-28 13:09:58 +00:00
|
|
|
|
|
2016-01-21 12:49:15 +00:00
|
|
|
|
while (pos < size)
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
NSRange r = NSMakeRange(pos, size - pos);
|
|
|
|
|
NSString *s = nil;
|
|
|
|
|
NSData *d = nil;
|
|
|
|
|
BOOL e = NO;
|
|
|
|
|
|
|
|
|
|
r = [str rangeOfCharacterFromSet: whitespace
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
if (r.length > 0 && r.location == pos)
|
2008-02-16 09:30:06 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
/* Found space at the start of the string, so we reduce
|
|
|
|
|
* it to a single space in the output, or omit it entirely
|
|
|
|
|
* if the string contains nothing more but space.
|
|
|
|
|
*/
|
2008-02-16 09:30:06 +00:00
|
|
|
|
pos++;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
while (pos < size
|
|
|
|
|
&& [whitespace characterIsMember: [str characterAtIndex: pos]])
|
|
|
|
|
{
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
if (pos < size)
|
|
|
|
|
{
|
|
|
|
|
needSpace = YES; // We need a space before the next word.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (r.length == 0)
|
|
|
|
|
{
|
|
|
|
|
/* No more space found ... we must output the remaining string
|
|
|
|
|
* without folding it.
|
|
|
|
|
*/
|
|
|
|
|
s = [str substringWithRange: NSMakeRange(pos, size - pos)];
|
|
|
|
|
pos = size;
|
|
|
|
|
d = wordData(s, &e);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Output the substring up to the next space.
|
|
|
|
|
*/
|
|
|
|
|
s = [str substringWithRange: NSMakeRange(pos, r.location - pos)];
|
|
|
|
|
pos = r.location;
|
|
|
|
|
d = wordData(s, &e);
|
2008-02-16 09:30:06 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (nil != d)
|
2008-02-16 09:30:06 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
/* We have a 'word' to output ... do that after dealing with any
|
|
|
|
|
* space needed between the last word and the new one.
|
|
|
|
|
*/
|
|
|
|
|
if (YES == needSpace)
|
|
|
|
|
{
|
|
|
|
|
if (YES == e && YES == hadEncodedWord)
|
|
|
|
|
{
|
|
|
|
|
/* We can't have space between two encoded words, so
|
|
|
|
|
* we incorporate the space at the start of the next
|
|
|
|
|
* encoded word.
|
|
|
|
|
*/
|
|
|
|
|
s = [@" " stringByAppendingString: s];
|
|
|
|
|
d = wordData(s, &e);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Add the needed space before the next word.
|
|
|
|
|
*/
|
|
|
|
|
offset = appendBytes(m, offset, fold, " ", 1);
|
|
|
|
|
if (fold > 0 && offset > fold)
|
|
|
|
|
{
|
|
|
|
|
*ok = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
needSpace = NO;
|
|
|
|
|
}
|
|
|
|
|
hadEncodedWord = e;
|
|
|
|
|
offset = appendBytes(m, offset, fold, [d bytes], [d length]);
|
|
|
|
|
if (fold > 0 && offset > fold)
|
|
|
|
|
{
|
|
|
|
|
*ok = NO;
|
|
|
|
|
}
|
2008-02-16 09:30:06 +00:00
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
return offset;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSData *d;
|
|
|
|
|
NSString *cset = selectCharacterSet(str, &d);
|
|
|
|
|
const uint8_t *ptr = (const uint8_t*)[d bytes];
|
|
|
|
|
NSUInteger len = [d length];
|
2019-02-15 14:13:14 +00:00
|
|
|
|
BOOL utf8 = NO;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
2019-02-15 14:13:14 +00:00
|
|
|
|
if ([cset isEqualToString: @"utf-8"])
|
|
|
|
|
{
|
|
|
|
|
utf8 = YES;
|
|
|
|
|
}
|
|
|
|
|
else if ([cset isEqualToString: @"us-ascii"])
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (0 == fold)
|
|
|
|
|
{
|
|
|
|
|
/* Simple ... no folding to do so we can just add the ascii.
|
|
|
|
|
*/
|
|
|
|
|
[m appendBytes: ptr + pos length: len - pos];
|
|
|
|
|
offset += (len - pos);
|
|
|
|
|
pos = len;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while (pos < len)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger next;
|
|
|
|
|
|
|
|
|
|
/* Find the longest string we can fit on the current line,
|
|
|
|
|
* either the whole string or by breaking at whitespace.
|
|
|
|
|
*/
|
|
|
|
|
if (offset + len - pos <= fold)
|
|
|
|
|
{
|
|
|
|
|
next = len;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next = lastWhiteSpace(ptr, pos, pos + fold - offset);
|
|
|
|
|
if (NSNotFound == next)
|
|
|
|
|
{
|
|
|
|
|
/* The header text has no whitespace usable as
|
|
|
|
|
* a folding point before the end of the line.
|
|
|
|
|
* break out and use encoded words.
|
|
|
|
|
*/
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Add the string to the output and adjust position.
|
|
|
|
|
*/
|
|
|
|
|
[m appendBytes: ptr + pos length: next - pos];
|
|
|
|
|
offset += next - pos;
|
2016-08-28 09:53:38 +00:00
|
|
|
|
pos = next;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (pos < len)
|
|
|
|
|
{
|
|
|
|
|
/* We have more text to output, so fold the line.
|
|
|
|
|
*/
|
|
|
|
|
[m appendBytes: "\r\n" length: 2];
|
2016-05-13 11:08:46 +00:00
|
|
|
|
if (isspace(ptr[pos]))
|
|
|
|
|
{
|
|
|
|
|
[m appendBytes: ptr + pos length: 1];
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[m appendBytes: " " length: 1];
|
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
offset = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
|
|
|
|
/* We get here to use encoded words, either because the text to be
|
|
|
|
|
* added contains non-ascii characters, or because it contains some
|
|
|
|
|
* non-foldable sequence too long to fit in the given line limit.
|
|
|
|
|
*/
|
|
|
|
|
if (pos < len)
|
2007-11-28 13:09:58 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
NSUInteger csetLength;
|
|
|
|
|
NSUInteger overhead;
|
|
|
|
|
|
|
|
|
|
/* The overhead is the number of bytes needed to wrap an
|
2016-02-25 11:55:58 +00:00
|
|
|
|
* encoded word in the format =?csetname?B?encodedtext?=
|
2008-02-16 09:30:06 +00:00
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
csetLength = [cset length];
|
|
|
|
|
overhead = csetLength + 7;
|
|
|
|
|
|
|
|
|
|
/* RFC2047 says that any header line containing an encoded word
|
|
|
|
|
* is limited to 76 characters, so we temporarily adjust the
|
|
|
|
|
* fold if necessary.
|
2014-03-11 09:46:54 +00:00
|
|
|
|
*/
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (0 == fold || fold > 76)
|
|
|
|
|
{
|
|
|
|
|
fold = 76;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (pos < len)
|
2014-03-11 09:46:54 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
uint8_t *buffer;
|
|
|
|
|
NSUInteger existingLength;
|
|
|
|
|
NSUInteger quotedLength;
|
2019-02-15 14:13:14 +00:00
|
|
|
|
NSUInteger byteLength;
|
2019-02-14 11:19:33 +00:00
|
|
|
|
uint8_t style = 'Q';
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
|
|
|
|
/* Calculate the number of encoded characters we can
|
|
|
|
|
* fit on the current line. If there's no room, we
|
|
|
|
|
* fold the line and recalculate.
|
|
|
|
|
* With base64 encoding, the minimum space used for an
|
|
|
|
|
* encoded character (because it works in triplets) is
|
|
|
|
|
* 4 bytes, while for quoted characters it's 3 bytes
|
|
|
|
|
* (the '=' followed by two hexadecimal digits).
|
|
|
|
|
* We therefore check that we have at least space for
|
|
|
|
|
* four characters left on the line.
|
|
|
|
|
*/
|
|
|
|
|
if (offset + overhead + 4 > fold)
|
2014-03-11 09:46:54 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
[m appendBytes: "\r\n " length: 3];
|
|
|
|
|
offset = 1;
|
2014-03-11 09:46:54 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
2019-02-15 14:13:14 +00:00
|
|
|
|
byteLength = quotableLength(ptr + pos, len - pos,
|
|
|
|
|
fold - offset - overhead, "edLength, utf8);
|
|
|
|
|
if (quotedLength > (byteLength * 4) / 3)
|
2014-03-11 09:46:54 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
/* Using base64 is more compact than using quoted
|
|
|
|
|
* text, so lets do that.
|
2014-03-11 09:46:54 +00:00
|
|
|
|
*/
|
2019-02-14 11:19:33 +00:00
|
|
|
|
style = 'B';
|
2019-02-15 14:13:14 +00:00
|
|
|
|
byteLength = ((fold - offset - overhead) / 4) * 3;
|
|
|
|
|
if (byteLength >= len - pos)
|
2014-03-11 09:46:54 +00:00
|
|
|
|
{
|
2016-01-21 12:49:15 +00:00
|
|
|
|
/* If we have less text than we can fit,
|
|
|
|
|
* just encode all of it.
|
|
|
|
|
*/
|
2019-02-15 14:13:14 +00:00
|
|
|
|
byteLength = len - pos;
|
2014-03-11 09:46:54 +00:00
|
|
|
|
}
|
2019-02-15 14:13:14 +00:00
|
|
|
|
else if (YES == utf8
|
|
|
|
|
&& (ptr[pos + byteLength] % 0xc0) == 0x80)
|
|
|
|
|
{
|
|
|
|
|
/* The byte after the end of the data we propose
|
|
|
|
|
* to encode is a utf8 continuation byte
|
|
|
|
|
* so step back to the character boundary.
|
|
|
|
|
*/
|
|
|
|
|
do {
|
|
|
|
|
byteLength--;
|
|
|
|
|
} while ((ptr[pos + byteLength] % 0xc0) == 0x80);
|
|
|
|
|
}
|
|
|
|
|
quotedLength = 4 * ((byteLength + 2) / 3);
|
2014-03-11 09:46:54 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
|
2016-02-25 11:55:58 +00:00
|
|
|
|
/* make sure we have enough space in the output buffer.
|
2016-01-21 12:49:15 +00:00
|
|
|
|
*/
|
|
|
|
|
existingLength = [m length];
|
|
|
|
|
[m setLength: existingLength + quotedLength + overhead];
|
|
|
|
|
buffer = (uint8_t*)[m mutableBytes] + existingLength;
|
|
|
|
|
|
|
|
|
|
memcpy(buffer, "=?", 2);
|
|
|
|
|
buffer += 2;
|
|
|
|
|
[cset getCString: (char*)buffer
|
|
|
|
|
maxLength: csetLength + 1
|
|
|
|
|
encoding: NSASCIIStringEncoding];
|
|
|
|
|
buffer += csetLength;
|
|
|
|
|
*buffer++ = '?';
|
|
|
|
|
*buffer++ = style;
|
|
|
|
|
*buffer++ = '?';
|
2019-02-14 11:19:33 +00:00
|
|
|
|
if ('Q' == style)
|
2016-01-21 12:49:15 +00:00
|
|
|
|
{
|
2019-02-15 14:13:14 +00:00
|
|
|
|
quotedWord(ptr + pos, byteLength, buffer);
|
2016-01-21 12:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-02-15 14:13:14 +00:00
|
|
|
|
GSPrivateEncodeBase64(ptr + pos, byteLength, buffer);
|
2016-01-21 12:49:15 +00:00
|
|
|
|
}
|
|
|
|
|
buffer[quotedLength] = '?';
|
|
|
|
|
buffer[quotedLength + 1] = '=';
|
|
|
|
|
offset += quotedLength + overhead;
|
2019-02-15 14:13:14 +00:00
|
|
|
|
pos += byteLength;
|
2014-03-11 09:46:54 +00:00
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
return offset;
|
2007-11-28 13:09:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-15 14:13:14 +00:00
|
|
|
|
/* For testing
|
|
|
|
|
+ (NSUInteger) appendString: (NSString*)str
|
|
|
|
|
to: (NSMutableData*)m
|
|
|
|
|
at: (NSUInteger)offset
|
|
|
|
|
fold: (NSUInteger)fold
|
|
|
|
|
ok: (BOOL*)ok
|
|
|
|
|
{
|
|
|
|
|
return appendString(m, offset, fold, str, ok);
|
|
|
|
|
}
|
|
|
|
|
*/
|
2007-11-28 13:09:58 +00:00
|
|
|
|
|
2012-11-27 16:13:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns the full text of the header, built from its component parts,
|
|
|
|
|
* and including a terminating CR-LF
|
|
|
|
|
*/
|
|
|
|
|
- (NSMutableData*) rawMimeData
|
|
|
|
|
{
|
|
|
|
|
return [self rawMimeDataPreservingCase: NO];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableData*) rawMimeDataPreservingCase: (BOOL)preserve
|
|
|
|
|
{
|
|
|
|
|
// 78 is what the RFCs say we should limit length to.
|
|
|
|
|
return [self rawMimeDataPreservingCase: NO foldedAt: 78];
|
|
|
|
|
}
|
|
|
|
|
|
2007-05-14 16:55:16 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns the full text of the header, built from its component parts,
|
|
|
|
|
* and including a terminating CR-LF.<br />
|
|
|
|
|
* If preserve is YES then we attempt to build the text using the same
|
|
|
|
|
* case as it was originally parsed/set from, otherwise we use common
|
2007-11-28 13:09:58 +00:00
|
|
|
|
* conventions of capitalising the header names and using lowercase
|
2012-11-27 16:13:25 +00:00
|
|
|
|
* parameter names.<br />
|
|
|
|
|
* If fold is greater than zero, lines with more than the specified
|
|
|
|
|
* number of characters are considered 'long' and are folded into
|
|
|
|
|
* multiple lines.
|
2007-05-14 16:55:16 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSMutableData*) rawMimeDataPreservingCase: (BOOL)preserve
|
2012-11-27 16:13:25 +00:00
|
|
|
|
foldedAt: (NSUInteger)fold
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableData *md = [NSMutableData dataWithCapacity: 128];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|
|
|
|
|
[self rawMimeDataPreservingCase: preserve foldedAt: fold to: md];
|
|
|
|
|
return md;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) rawMimeDataPreservingCase: (BOOL)preserve
|
|
|
|
|
foldedAt: (NSUInteger)fold
|
|
|
|
|
to: (NSMutableData*)md
|
|
|
|
|
{
|
2002-06-19 11:29:49 +00:00
|
|
|
|
NSEnumerator *e = [params keyEnumerator];
|
|
|
|
|
NSString *k;
|
2007-11-28 13:09:58 +00:00
|
|
|
|
NSString *n;
|
|
|
|
|
NSData *d;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger offset = 0;
|
2002-06-19 11:29:49 +00:00
|
|
|
|
BOOL conv = YES;
|
2007-11-28 13:09:58 +00:00
|
|
|
|
BOOL ok = YES;
|
2002-06-19 11:29:49 +00:00
|
|
|
|
|
2007-11-28 13:09:58 +00:00
|
|
|
|
n = [self namePreservingCase: preserve];
|
|
|
|
|
d = [n dataUsingEncoding: NSASCIIStringEncoding];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (preserve == YES)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
/* Protect the user ... MIME-Version *must* have the correct case.
|
|
|
|
|
*/
|
|
|
|
|
if ([n caseInsensitiveCompare: @"MIME-Version"] == NSOrderedSame)
|
|
|
|
|
{
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendBytes(md, offset, fold, "MIME-Version", 12);
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendBytes(md, offset, fold, [d bytes], [d length]);
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
2002-06-21 17:06:50 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2012-11-27 16:13:25 +00:00
|
|
|
|
NSUInteger l = [d length];
|
|
|
|
|
char buf[l];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger i = 0;
|
2007-05-14 16:55:16 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Capitalise the header name. However, the version header is a special
|
|
|
|
|
* case - it is defined as being literally 'MIME-Version'
|
|
|
|
|
*/
|
|
|
|
|
memcpy(buf, [d bytes], l);
|
2007-11-28 13:09:58 +00:00
|
|
|
|
if (l == 12 && strncasecmp(buf, "mime-version", 12) == 0)
|
2007-05-14 16:55:16 +00:00
|
|
|
|
{
|
|
|
|
|
memcpy(buf, "MIME-Version", 12);
|
|
|
|
|
}
|
|
|
|
|
else
|
2002-06-21 17:06:50 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
while (i < l)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (conv == YES)
|
2002-06-21 17:06:50 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (islower(buf[i]))
|
|
|
|
|
{
|
|
|
|
|
buf[i] = toupper(buf[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (buf[i++] == '-')
|
|
|
|
|
{
|
|
|
|
|
conv = YES;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
conv = NO;
|
2002-06-21 17:06:50 +00:00
|
|
|
|
}
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendBytes(md, offset, fold, buf, l);
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
2012-11-27 16:41:35 +00:00
|
|
|
|
if (fold > 0 && offset > fold)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
2012-01-09 08:28:27 +00:00
|
|
|
|
NSLog(@"Name '%@' too long for folding at %"PRIuPTR" in header",
|
|
|
|
|
n, fold);
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
|
|
|
|
|
offset = appendBytes(md, offset, fold, ":", 1);
|
|
|
|
|
offset = appendBytes(md, offset, fold, " ", 1);
|
|
|
|
|
offset = appendString(md, offset, fold, value, &ok);
|
|
|
|
|
if (ok == NO)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
2008-09-18 19:55:56 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime",
|
2013-07-03 06:46:41 +00:00
|
|
|
|
@"Value for '%@' too long for folding at %"PRIuPTR" in header",
|
|
|
|
|
n, fold);
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
2016-01-29 15:23:24 +00:00
|
|
|
|
NSString *v;
|
|
|
|
|
NSUInteger kLength;
|
|
|
|
|
NSUInteger vLength;
|
2002-11-25 18:00:55 +00:00
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
v = [[self class] makeQuoted: [params objectForKey: k] always: NO];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (preserve == NO)
|
|
|
|
|
{
|
|
|
|
|
k = [k lowercaseString];
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendBytes(md, offset, fold, ";", 1);
|
2016-01-23 15:30:05 +00:00
|
|
|
|
|
2016-01-29 15:23:24 +00:00
|
|
|
|
kLength = [k length];
|
|
|
|
|
vLength = [v length];
|
|
|
|
|
|
|
|
|
|
/* Crude heuristic ...
|
|
|
|
|
* if the length of the key=value will definitely be
|
2016-01-23 15:30:05 +00:00
|
|
|
|
* too long to fit on a line, fold right now.
|
2016-02-18 15:52:00 +00:00
|
|
|
|
* Since we are producing a key=value pair in a structured
|
|
|
|
|
* field, we use a tab to fold to maximise the chances of a
|
|
|
|
|
* parser understanding it.
|
2016-01-23 15:30:05 +00:00
|
|
|
|
*/
|
2016-01-29 15:23:24 +00:00
|
|
|
|
if (fold > 0 && offset + kLength + vLength + 1 >= fold)
|
2016-01-23 15:30:05 +00:00
|
|
|
|
{
|
2016-02-18 15:52:00 +00:00
|
|
|
|
[md appendBytes: "\r\n\t" length: 3];
|
2016-01-23 15:30:05 +00:00
|
|
|
|
offset = 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
offset = appendBytes(md, offset, fold, " ", 1);
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendString(md, offset, fold, k, &ok);
|
|
|
|
|
if (ok == NO)
|
|
|
|
|
{
|
2008-09-18 19:55:56 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime",
|
2013-07-03 06:46:41 +00:00
|
|
|
|
@"Parameter name '%@' in '%@' too long for folding at %"PRIuPTR,
|
2007-11-28 13:09:58 +00:00
|
|
|
|
k, n, fold);
|
|
|
|
|
}
|
|
|
|
|
offset = appendBytes(md, offset, fold, "=", 1);
|
2016-01-23 15:30:05 +00:00
|
|
|
|
|
|
|
|
|
/* Crude heuristic ... if the length of the value will definitely be
|
|
|
|
|
* too long to fit on a line, fold right now.
|
2016-02-18 15:52:00 +00:00
|
|
|
|
* Since we are producing a key=value pair in a structured
|
|
|
|
|
* field, we use a tab to fold to maximise the chances of a
|
|
|
|
|
* parser understanding it.
|
2016-01-23 15:30:05 +00:00
|
|
|
|
*/
|
2016-01-29 15:59:56 +00:00
|
|
|
|
if (fold > 0 && offset + vLength > fold)
|
2016-01-23 15:30:05 +00:00
|
|
|
|
{
|
2016-02-18 15:52:00 +00:00
|
|
|
|
[md appendBytes: "\r\n\t" length: 3];
|
2016-01-23 15:30:05 +00:00
|
|
|
|
offset = 1;
|
|
|
|
|
}
|
2007-11-28 13:09:58 +00:00
|
|
|
|
offset = appendString(md, offset, fold, v, &ok);
|
|
|
|
|
if (ok == NO)
|
|
|
|
|
{
|
2008-09-18 19:55:56 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime",
|
2013-07-03 06:46:41 +00:00
|
|
|
|
@"Parameter value for '%@' in '%@' "
|
|
|
|
|
@"too long for folding at %"PRIuPTR,
|
2007-11-28 13:09:58 +00:00
|
|
|
|
k, n, fold);
|
|
|
|
|
}
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* Method to store specific information for particular types of
|
2002-11-28 13:44:07 +00:00
|
|
|
|
* header. This is used for non-standard parts of headers.<br />
|
|
|
|
|
* Setting a nil value for o will remove any existing value set
|
|
|
|
|
* using the k as its key.
|
2002-05-26 15:24:05 +00:00
|
|
|
|
*/
|
|
|
|
|
- (void) setObject: (id)o forKey: (NSString*)k
|
|
|
|
|
{
|
2002-11-28 13:44:07 +00:00
|
|
|
|
if (o == nil)
|
|
|
|
|
{
|
|
|
|
|
[objects removeObjectForKey: k];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (objects == nil)
|
|
|
|
|
{
|
|
|
|
|
objects = [NSMutableDictionary new];
|
|
|
|
|
}
|
2002-11-28 13:44:07 +00:00
|
|
|
|
[objects setObject: o forKey: k];
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets a parameter of this header ... converts name to lowercase and
|
|
|
|
|
* removes illegal characters.<br />
|
2002-05-26 05:45:36 +00:00
|
|
|
|
* If a nil parameter name is supplied, removes any parameter with the
|
|
|
|
|
* specified key.
|
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (void) setParameter: (NSString*)v forKey: (NSString*)k
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
2024-11-27 16:25:08 +00:00
|
|
|
|
k = [[self class] makeToken: k preservingCase: YES];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
[params removeObjectForKey: k];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (params == nil)
|
|
|
|
|
{
|
2010-01-11 12:38:37 +00:00
|
|
|
|
params = [_GSMutableInsensitiveDictionary new];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
[params setObject: v forKey: k];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* Sets all parameters of this header ... converts names to lowercase
|
|
|
|
|
* and removes illegal characters from them.
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (void) setParameters: (NSDictionary*)d
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
NSMutableDictionary *m = nil;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger c = [d count];
|
2002-05-26 05:45:36 +00:00
|
|
|
|
|
2007-05-14 16:55:16 +00:00
|
|
|
|
if (c > 0)
|
2002-05-26 05:45:36 +00:00
|
|
|
|
{
|
2007-05-14 16:55:16 +00:00
|
|
|
|
NSEnumerator *e = [d keyEnumerator];
|
|
|
|
|
NSString *k;
|
|
|
|
|
|
2010-01-11 12:38:37 +00:00
|
|
|
|
m = [[_GSMutableInsensitiveDictionary alloc] initWithCapacity: c];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
|
|
|
{
|
2016-01-23 15:30:05 +00:00
|
|
|
|
NSString *v = [d objectForKey: k];
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
k = [[self class] makeToken: k preservingCase: YES];
|
2016-01-23 15:30:05 +00:00
|
|
|
|
[m setObject: v forKey: k];
|
2007-05-14 16:55:16 +00:00
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
DESTROY(params);
|
|
|
|
|
params = m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-08-03 09:24:53 +00:00
|
|
|
|
* Sets the value of this header (without changing parameters).<br />
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* If given a nil argument, set an empty string value.
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
|
|
|
|
- (void) setValue: (NSString*)s
|
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
if (s == nil)
|
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
ASSIGNCOPY(value, s);
|
2017-06-30 06:37:05 +00:00
|
|
|
|
if (CteContentType == lower)
|
2016-08-03 09:24:53 +00:00
|
|
|
|
{
|
|
|
|
|
NSArray *a;
|
|
|
|
|
|
|
|
|
|
a = [[value lowercaseString] componentsSeparatedByString: @"/"];
|
2023-02-09 11:03:21 +00:00
|
|
|
|
if ([a count] == 2)
|
|
|
|
|
{
|
|
|
|
|
[self setObject: [a objectAtIndex: 0] forKey: @"Type"];
|
|
|
|
|
[self setObject: [a objectAtIndex: 1] forKey: @"Subtype"];
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
}
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the full text of the header, built from its component parts,
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* and including a terminating CR-LF
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSString*) text
|
|
|
|
|
{
|
2005-03-21 19:51:52 +00:00
|
|
|
|
NSString *s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2002-06-19 11:29:49 +00:00
|
|
|
|
s = [s initWithData: [self rawMimeData] encoding: NSASCIIStringEncoding];
|
|
|
|
|
return AUTORELEASE(s);
|
2002-05-26 05:45:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2007-05-15 08:36:23 +00:00
|
|
|
|
* Returns the value of this header (excluding any parameters).<br />
|
2022-05-20 11:32:31 +00:00
|
|
|
|
* Use the -fullValue method if you want parameter included.
|
2002-05-26 05:45:36 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSString*) value
|
|
|
|
|
{
|
|
|
|
|
return value;
|
|
|
|
|
}
|
2015-07-15 14:14:21 +00:00
|
|
|
|
|
2015-07-16 08:44:15 +00:00
|
|
|
|
- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude
|
2015-07-15 14:14:21 +00:00
|
|
|
|
{
|
2015-07-16 08:44:15 +00:00
|
|
|
|
NSUInteger size = [super sizeInBytesExcluding: exclude];
|
2015-07-15 14:14:21 +00:00
|
|
|
|
|
|
|
|
|
if (size > 0)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
size += 2 * [name sizeInBytesExcluding: exclude];
|
2015-07-16 08:44:15 +00:00
|
|
|
|
size += [value sizeInBytesExcluding: exclude];
|
|
|
|
|
size += [objects sizeInBytesExcluding: exclude];
|
|
|
|
|
size += [params sizeInBytesExcluding: exclude];
|
2015-07-15 14:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 05:45:36 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This class is intended to provide a wrapper for MIME messages
|
|
|
|
|
* permitting easy access to the contents of a message and
|
|
|
|
|
* providing a basis for parsing an unparsing messages that
|
|
|
|
|
* have arrived via email or as a web document.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* The class keeps track of all the document headers, and provides
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* methods for modifying and examining the headers that apply to a
|
|
|
|
|
* document.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
@implementation GSMimeDocument
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/* Examine xml data/string to find out the characterset encoding specified
|
2009-07-28 18:48:37 +00:00
|
|
|
|
*/
|
2016-08-03 09:24:53 +00:00
|
|
|
|
+ (NSString*) charsetForXml: (id)xml
|
|
|
|
|
{
|
|
|
|
|
NSUInteger length = [xml length];
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
|
|
|
|
if (length < 4)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return nil; // Not long enough to determine an encoding
|
2009-07-28 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if ([xml isKindOfClass: [NSData class]])
|
2009-07-28 18:48:37 +00:00
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
const unsigned char *ptr = (const unsigned char*)[xml bytes];
|
|
|
|
|
const unsigned char *end = ptr + length;
|
|
|
|
|
NSUInteger offset = 0;
|
|
|
|
|
NSUInteger size = 1;
|
|
|
|
|
unsigned char quote = 0;
|
|
|
|
|
unsigned char buffer[30];
|
|
|
|
|
NSUInteger buflen = 0;
|
|
|
|
|
BOOL found = NO;
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/*
|
|
|
|
|
* Determine encoding using byte-order-mark if present
|
|
|
|
|
*/
|
|
|
|
|
if ((ptr[0] == 0xFE && ptr[1] == 0xFF)
|
|
|
|
|
|| (ptr[0] == 0xFF && ptr[1] == 0xFE))
|
|
|
|
|
{
|
|
|
|
|
return @"utf-16";
|
|
|
|
|
}
|
|
|
|
|
if (ptr[0] == 0xEF && ptr[1] == 0xBB && ptr[2] == 0xBF)
|
|
|
|
|
{
|
|
|
|
|
return @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
if ((ptr[0] == 0x00 && ptr[1] == 0x00)
|
|
|
|
|
&& ((ptr[2] == 0xFE && ptr[3] == 0xFF)
|
|
|
|
|
|| (ptr[2] == 0xFF && ptr[3] == 0xFE)))
|
|
|
|
|
{
|
|
|
|
|
return @"ucs-4";
|
|
|
|
|
}
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/*
|
|
|
|
|
* Look for nul bytes to determine whether this is a four byte
|
|
|
|
|
* encoding or a two byte encoding (or the default).
|
|
|
|
|
*/
|
|
|
|
|
if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 3;
|
|
|
|
|
size = 4;
|
|
|
|
|
}
|
|
|
|
|
else if (ptr[0] == 0 && ptr[1] == 0 && ptr[3] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 2;
|
|
|
|
|
size = 4;
|
|
|
|
|
}
|
|
|
|
|
else if (ptr[0] == 0 && ptr[2] == 0 && ptr[3] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 1;
|
|
|
|
|
size = 4;
|
|
|
|
|
}
|
|
|
|
|
else if (ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 0;
|
|
|
|
|
size = 4;
|
|
|
|
|
}
|
|
|
|
|
else if (ptr[0] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 1;
|
|
|
|
|
size = 2;
|
|
|
|
|
}
|
|
|
|
|
else if (ptr[1] == 0)
|
|
|
|
|
{
|
|
|
|
|
offset = 0;
|
|
|
|
|
size = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Now look for the xml encoding declaration ...
|
|
|
|
|
*/
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
// Tolerate leading whitespace
|
|
|
|
|
while (ptr + size <= end && isspace(ptr[offset])) ptr += size;
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (ptr + (size * 20) >= end
|
|
|
|
|
|| ptr[offset] != '<' || ptr[offset+size] != '?')
|
|
|
|
|
{
|
|
|
|
|
if (size == 1)
|
|
|
|
|
{
|
|
|
|
|
return @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
else if (size == 2)
|
|
|
|
|
{
|
|
|
|
|
return @"utf-16";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return @"ucs-4";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ptr += size * 5; // Step past '<?xml' prefix
|
|
|
|
|
|
|
|
|
|
while (ptr + size <= end)
|
|
|
|
|
{
|
|
|
|
|
unsigned char c = ptr[offset];
|
|
|
|
|
|
|
|
|
|
ptr += size;
|
|
|
|
|
if (quote == 0)
|
|
|
|
|
{
|
|
|
|
|
if (c == '\'' || c == '"')
|
|
|
|
|
{
|
|
|
|
|
buflen = 0;
|
|
|
|
|
quote = c;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (isspace(c) || c == '=')
|
|
|
|
|
{
|
|
|
|
|
if (buflen == 8)
|
|
|
|
|
{
|
|
|
|
|
buffer[8] = '\0';
|
|
|
|
|
if (strcasecmp((char*)buffer, "encoding") == 0)
|
|
|
|
|
{
|
|
|
|
|
found = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
buflen = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (buflen == sizeof(buffer)) buflen = 0;
|
|
|
|
|
buffer[buflen++] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (c == quote)
|
|
|
|
|
{
|
|
|
|
|
if (found == YES)
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
tmp = [[NSString alloc] initWithBytes: buffer
|
|
|
|
|
length: buflen
|
|
|
|
|
encoding: NSASCIIStringEncoding];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([tmp autorelease];)
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return [tmp lowercaseString];
|
|
|
|
|
}
|
|
|
|
|
buflen = 0;
|
|
|
|
|
quote = 0; // End of quoted section
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (buflen == sizeof(buffer)) buflen = 0;
|
|
|
|
|
buffer[buflen++] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ([xml isKindOfClass: [NSString class]])
|
2009-07-28 18:48:37 +00:00
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
NSUInteger index = 0;
|
|
|
|
|
NSRange search;
|
|
|
|
|
NSRange r;
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/* Skip past any leading white space
|
|
|
|
|
*/
|
|
|
|
|
while (index < length && isspace([xml characterAtIndex: index]))
|
|
|
|
|
{
|
|
|
|
|
index++;
|
|
|
|
|
}
|
2009-07-28 18:48:37 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/* Check for start of xml declaration after any leading space.
|
|
|
|
|
*/
|
|
|
|
|
search = NSMakeRange(index, length - index);
|
|
|
|
|
if (search.length > 5
|
|
|
|
|
&& [xml compare: @"<?xml"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(index, 5)] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
/* And find end of declaration.
|
|
|
|
|
*/
|
|
|
|
|
r = [xml rangeOfString: @">"];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Shorten the search range to the xml declaration end,
|
|
|
|
|
* then look for the 'encoding' keyword.
|
|
|
|
|
*/
|
|
|
|
|
search = NSMakeRange(index, NSMaxRange(r) - index);
|
|
|
|
|
r = [xml rangeOfString: @"encoding"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: search];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Shorten the search range to after 'encoding' and
|
|
|
|
|
* find the quote before the charset.
|
|
|
|
|
*/
|
|
|
|
|
index = NSMaxRange(r);
|
|
|
|
|
search = NSMakeRange(index, NSMaxRange(search) - index);
|
|
|
|
|
r = [xml rangeOfString: @"\""
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: search];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Narrow the search range to begin at the start of
|
|
|
|
|
* the charset and find the quote after the charset.
|
|
|
|
|
*/
|
|
|
|
|
index = NSMaxRange(r);
|
|
|
|
|
search = NSMakeRange(index, NSMaxRange(search) - index);
|
|
|
|
|
r = [xml rangeOfString: @"\""
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: search];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Extract the charset and return it.
|
|
|
|
|
*/
|
2016-09-19 15:56:51 +00:00
|
|
|
|
r = NSMakeRange(index, r.location - index);
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return [xml substringWithRange: r];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-28 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-19 11:29:49 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return the MIME characterset name corresponding to the
|
2005-03-22 09:16:22 +00:00
|
|
|
|
* specified string encoding.<br />
|
2005-03-22 09:45:58 +00:00
|
|
|
|
* As a special case, returns "us-ascii" if enc is zero.<br />
|
2005-03-22 10:00:55 +00:00
|
|
|
|
* Returns nil if enc cannot be mapped to a charset.<br />
|
2005-03-22 09:16:22 +00:00
|
|
|
|
* NB. The correspondence between charsets and encodings is not
|
|
|
|
|
* a direct one to one mapping, so successive calls to +encodingFromCharset:
|
|
|
|
|
* and +charsetFromEncoding: may not produce the original input.
|
2002-06-19 11:29:49 +00:00
|
|
|
|
*/
|
|
|
|
|
+ (NSString*) charsetFromEncoding: (NSStringEncoding)enc
|
|
|
|
|
{
|
2005-03-22 09:45:58 +00:00
|
|
|
|
NSString *charset = @"us-ascii";
|
2005-03-21 19:16:15 +00:00
|
|
|
|
|
2005-03-22 09:16:22 +00:00
|
|
|
|
if (enc != 0)
|
2005-03-21 19:16:15 +00:00
|
|
|
|
{
|
2005-03-22 09:16:22 +00:00
|
|
|
|
charset = (NSString*)NSMapGet(encodings, (void*)enc);
|
2005-03-21 19:16:15 +00:00
|
|
|
|
}
|
|
|
|
|
return charset;
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-06-12 15:00:02 +00:00
|
|
|
|
+ (NSData*) decodeBase64: (NSData*)source
|
|
|
|
|
{
|
2002-06-14 09:48:15 +00:00
|
|
|
|
int length;
|
2011-11-07 09:01:26 +00:00
|
|
|
|
int declen;
|
2005-07-08 11:48:37 +00:00
|
|
|
|
const unsigned char *src;
|
|
|
|
|
const unsigned char *end;
|
2002-06-12 15:00:02 +00:00
|
|
|
|
unsigned char *result;
|
|
|
|
|
unsigned char *dst;
|
|
|
|
|
unsigned char buf[4];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger pos = 0;
|
2010-05-29 06:42:38 +00:00
|
|
|
|
int pad = 0;
|
2002-06-12 15:00:02 +00:00
|
|
|
|
|
2002-06-14 09:48:15 +00:00
|
|
|
|
if (source == nil)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
length = [source length];
|
2002-06-12 15:00:02 +00:00
|
|
|
|
if (length == 0)
|
|
|
|
|
{
|
|
|
|
|
return [NSData data];
|
|
|
|
|
}
|
2002-06-14 09:48:15 +00:00
|
|
|
|
declen = ((length + 3) * 3)/4;
|
2005-07-08 11:48:37 +00:00
|
|
|
|
src = (const unsigned char*)[source bytes];
|
2002-06-14 09:48:15 +00:00
|
|
|
|
end = &src[length];
|
|
|
|
|
|
2002-06-12 15:00:02 +00:00
|
|
|
|
result = (unsigned char*)NSZoneMalloc(NSDefaultMallocZone(), declen);
|
|
|
|
|
dst = result;
|
|
|
|
|
|
2004-02-24 14:14:26 +00:00
|
|
|
|
while ((src != end) && *src != '\0')
|
2002-06-12 15:00:02 +00:00
|
|
|
|
{
|
|
|
|
|
int c = *src++;
|
|
|
|
|
|
|
|
|
|
if (isupper(c))
|
|
|
|
|
{
|
|
|
|
|
c -= 'A';
|
|
|
|
|
}
|
|
|
|
|
else if (islower(c))
|
|
|
|
|
{
|
|
|
|
|
c = c - 'a' + 26;
|
|
|
|
|
}
|
|
|
|
|
else if (isdigit(c))
|
|
|
|
|
{
|
|
|
|
|
c = c - '0' + 52;
|
|
|
|
|
}
|
|
|
|
|
else if (c == '/')
|
|
|
|
|
{
|
|
|
|
|
c = 63;
|
|
|
|
|
}
|
2011-11-07 08:15:00 +00:00
|
|
|
|
else if (c == '_')
|
|
|
|
|
{
|
|
|
|
|
c = 63; /* RFC 4648 permits '_' in URLs and filenames */
|
|
|
|
|
}
|
2002-06-12 15:00:02 +00:00
|
|
|
|
else if (c == '+')
|
|
|
|
|
{
|
|
|
|
|
c = 62;
|
|
|
|
|
}
|
2011-11-07 08:15:00 +00:00
|
|
|
|
else if (c == '-')
|
|
|
|
|
{
|
|
|
|
|
c = 62; /* RFC 4648 permits '-' in URLs and filenames */
|
|
|
|
|
}
|
2002-06-12 15:00:02 +00:00
|
|
|
|
else if (c == '=')
|
|
|
|
|
{
|
|
|
|
|
c = -1;
|
2010-05-29 06:42:38 +00:00
|
|
|
|
pad++;
|
2002-06-12 15:00:02 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-11-07 09:01:26 +00:00
|
|
|
|
c = -1; /* Ignore ... non-standard but more tolerant. */
|
|
|
|
|
length--; /* Don't count this as part of the length. */
|
2002-06-12 15:00:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c >= 0)
|
|
|
|
|
{
|
|
|
|
|
buf[pos++] = c;
|
|
|
|
|
if (pos == 4)
|
|
|
|
|
{
|
|
|
|
|
pos = 0;
|
|
|
|
|
decodebase64(dst, buf);
|
|
|
|
|
dst += 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-07 09:01:26 +00:00
|
|
|
|
/* If number of bytes is not a multiple of four, treat it as if the missing
|
2011-11-07 08:15:00 +00:00
|
|
|
|
* bytes were the '=' characters normally used for padding.
|
|
|
|
|
* This is not allowed by the basic standards, but permitted in some
|
|
|
|
|
* variants of 6ase64 encoding, so we should tolerate it.
|
|
|
|
|
*/
|
|
|
|
|
if (length % 4 > 0)
|
|
|
|
|
{
|
|
|
|
|
pad += (4 - length % 4);
|
|
|
|
|
}
|
2002-06-12 15:00:02 +00:00
|
|
|
|
if (pos > 0)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger i;
|
2010-05-29 06:42:38 +00:00
|
|
|
|
unsigned char tail[3];
|
2002-06-12 15:00:02 +00:00
|
|
|
|
|
|
|
|
|
for (i = pos; i < 4; i++)
|
2004-02-24 14:14:26 +00:00
|
|
|
|
{
|
|
|
|
|
buf[i] = '\0';
|
|
|
|
|
}
|
2010-05-29 06:42:38 +00:00
|
|
|
|
decodebase64(tail, buf);
|
2010-12-03 23:28:43 +00:00
|
|
|
|
if (pad > 3) pad = 3;
|
2010-05-29 06:42:38 +00:00
|
|
|
|
memcpy(dst, tail, 3 - pad);
|
|
|
|
|
dst += 3 - pad;
|
2002-06-12 15:00:02 +00:00
|
|
|
|
}
|
|
|
|
|
return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
|
|
|
|
|
initWithBytesNoCopy: result length: dst - result]);
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-14 09:48:15 +00:00
|
|
|
|
/**
|
2013-11-04 17:54:40 +00:00
|
|
|
|
* Converts the base64 encoded data in source to a decoded ASCII or UTF8
|
|
|
|
|
* string using the +decodeBase64: method. If the encoded data does not
|
|
|
|
|
* represent an ASCII or UTF8 string, you should use the +decodeBase64:
|
|
|
|
|
* method directly.
|
2002-06-14 09:48:15 +00:00
|
|
|
|
*/
|
|
|
|
|
+ (NSString*) decodeBase64String: (NSString*)source
|
|
|
|
|
{
|
|
|
|
|
NSData *d = [source dataUsingEncoding: NSASCIIStringEncoding];
|
|
|
|
|
NSString *r = nil;
|
|
|
|
|
|
|
|
|
|
d = [self decodeBase64: d];
|
|
|
|
|
if (d != nil)
|
|
|
|
|
{
|
2005-03-21 19:51:52 +00:00
|
|
|
|
r = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2013-11-04 17:54:40 +00:00
|
|
|
|
r = [r initWithData: d encoding: NSUTF8StringEncoding];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([r autorelease];)
|
2002-06-14 09:48:15 +00:00
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2002-11-28 11:48:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to return an autoreleased document using the
|
|
|
|
|
* specified content, type, and name value. This calls the
|
|
|
|
|
* -setContent:type:name: method to set up the document.
|
|
|
|
|
*/
|
|
|
|
|
+ (GSMimeDocument*) documentWithContent: (id)newContent
|
|
|
|
|
type: (NSString*)type
|
|
|
|
|
name: (NSString*)name
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *doc = AUTORELEASE([self new]);
|
|
|
|
|
|
|
|
|
|
[doc setContent: newContent type: type name: name];
|
|
|
|
|
return doc;
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-12 15:00:02 +00:00
|
|
|
|
+ (NSData*) encodeBase64: (NSData*)source
|
|
|
|
|
{
|
2002-06-14 09:48:15 +00:00
|
|
|
|
int length;
|
|
|
|
|
int destlen;
|
2002-06-12 15:00:02 +00:00
|
|
|
|
unsigned char *sBuf;
|
|
|
|
|
unsigned char *dBuf;
|
|
|
|
|
|
2002-06-14 09:48:15 +00:00
|
|
|
|
if (source == nil)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
length = [source length];
|
2002-06-12 15:00:02 +00:00
|
|
|
|
if (length == 0)
|
|
|
|
|
{
|
|
|
|
|
return [NSData data];
|
|
|
|
|
}
|
2004-01-22 09:37:07 +00:00
|
|
|
|
destlen = 4 * ((length + 2) / 3);
|
2002-06-12 15:00:02 +00:00
|
|
|
|
sBuf = (unsigned char*)[source bytes];
|
|
|
|
|
dBuf = NSZoneMalloc(NSDefaultMallocZone(), destlen);
|
|
|
|
|
|
2015-08-30 09:23:27 +00:00
|
|
|
|
GSPrivateEncodeBase64(sBuf, length, dBuf);
|
2002-06-12 15:00:02 +00:00
|
|
|
|
|
|
|
|
|
return AUTORELEASE([[NSData allocWithZone: NSDefaultMallocZone()]
|
2002-07-03 11:42:02 +00:00
|
|
|
|
initWithBytesNoCopy: dBuf length: destlen]);
|
2002-06-12 15:00:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-06-14 09:48:15 +00:00
|
|
|
|
/**
|
2013-11-04 17:54:40 +00:00
|
|
|
|
* Converts the ASCII or UTF8 string source into base64 encoded data using
|
|
|
|
|
* the +encodeBase64: method. If the original data is not an ASCII or UTF8
|
|
|
|
|
* string, you should use the +encodeBase64: method directly.
|
2002-06-14 09:48:15 +00:00
|
|
|
|
*/
|
|
|
|
|
+ (NSString*) encodeBase64String: (NSString*)source
|
|
|
|
|
{
|
2013-11-04 17:54:40 +00:00
|
|
|
|
NSData *d = [source dataUsingEncoding: NSUTF8StringEncoding];
|
2002-06-14 09:48:15 +00:00
|
|
|
|
NSString *r = nil;
|
|
|
|
|
|
|
|
|
|
d = [self encodeBase64: d];
|
|
|
|
|
if (d != nil)
|
|
|
|
|
{
|
2005-03-21 19:51:52 +00:00
|
|
|
|
r = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
r = [r initWithData: d encoding: NSASCIIStringEncoding];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([r autorelease];)
|
2002-06-14 09:48:15 +00:00
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-19 11:29:49 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return the string encoding corresponding to the specified MIME
|
2005-03-22 09:16:22 +00:00
|
|
|
|
* characterset name.<br />
|
|
|
|
|
* As a special case, returns NSASCIIStringEncoding if charset is nil.<br />
|
2005-03-22 10:00:55 +00:00
|
|
|
|
* Returns 0 if charset cannot be found.<br />
|
2005-03-22 09:45:58 +00:00
|
|
|
|
* NB. We treat iso-10646-ucs-2 as utf-16, which should
|
|
|
|
|
* work for most text, but is not strictly correct.<br />
|
2005-03-22 09:16:22 +00:00
|
|
|
|
* The correspondence between charsets and encodings is not
|
|
|
|
|
* a direct one to one mapping, so successive calls to +encodingFromCharset:
|
|
|
|
|
* and +charsetFromEncoding: may not produce the original input.
|
2002-06-19 11:29:49 +00:00
|
|
|
|
*/
|
|
|
|
|
+ (NSStringEncoding) encodingFromCharset: (NSString*)charset
|
|
|
|
|
{
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSStringEncoding enc = NSASCIIStringEncoding;
|
2011-10-08 17:53:17 +00:00
|
|
|
|
|
2005-03-11 12:58:54 +00:00
|
|
|
|
if (charset != nil)
|
2002-06-19 11:29:49 +00:00
|
|
|
|
{
|
2020-11-20 06:42:33 +00:00
|
|
|
|
enc = (NSStringEncoding)(intptr_t)NSMapGet(charsets, charset);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
if (enc == 0)
|
|
|
|
|
{
|
|
|
|
|
charset = [charset lowercaseString];
|
2020-11-20 06:42:33 +00:00
|
|
|
|
enc = (NSStringEncoding)(intptr_t)NSMapGet(charsets, charset);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
}
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
2005-03-11 12:58:54 +00:00
|
|
|
|
return enc;
|
2002-06-19 11:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
|
|
|
|
if (self == [GSMimeDocument class])
|
|
|
|
|
{
|
2005-03-21 19:16:15 +00:00
|
|
|
|
if (documentClass == 0)
|
|
|
|
|
{
|
|
|
|
|
documentClass = [GSMimeDocument class];
|
|
|
|
|
}
|
2024-11-27 16:25:08 +00:00
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if (NSArrayClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSArrayClass = [NSArray class];
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if (NSStringClass == 0)
|
|
|
|
|
{
|
|
|
|
|
NSStringClass = [NSString class];
|
|
|
|
|
}
|
2016-01-21 12:49:15 +00:00
|
|
|
|
if (0 == charsets)
|
2005-03-11 12:58:54 +00:00
|
|
|
|
{
|
|
|
|
|
charsets = NSCreateMapTable (NSObjectMapKeyCallBacks,
|
2011-05-31 06:46:17 +00:00
|
|
|
|
NSIntegerMapValueCallBacks, 0);
|
2013-08-22 15:44:54 +00:00
|
|
|
|
[[NSObject leakAt: &charsets] release];
|
2005-03-22 10:00:55 +00:00
|
|
|
|
|
2005-03-22 10:40:37 +00:00
|
|
|
|
/*
|
2006-10-29 09:17:05 +00:00
|
|
|
|
* These mappings were obtained primarily from
|
2005-03-22 10:40:37 +00:00
|
|
|
|
* http://www.iana.org/assignments/character-sets
|
2006-10-29 09:17:05 +00:00
|
|
|
|
* with additions determined empirically.
|
2005-03-22 10:40:37 +00:00
|
|
|
|
*
|
|
|
|
|
* We should ideally have all the aliases for each
|
|
|
|
|
* encoding we support, but I just did the aliases
|
|
|
|
|
* for ascii and latin1 as these (and utf-8 which
|
|
|
|
|
* has no aliases) account for most mime documents.
|
|
|
|
|
* Feel free to add more.
|
|
|
|
|
*/
|
|
|
|
|
|
2005-03-22 10:00:55 +00:00
|
|
|
|
// All the ascii mappings from IANA
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ansi_x3.4-1968",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-ir-6",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ansi_x3.4-1986",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso_646.irv:1991",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2006-10-29 09:17:05 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso_646.991-irv",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ascii",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2005-03-22 10:00:55 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso646-us",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2005-03-22 09:16:22 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"us-ascii",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2005-03-22 10:00:55 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"us",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm367",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp367",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"csascii",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
|
|
|
|
|
// All the latin1 mappings from IANA
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-1:1987",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-1:1987",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2005-03-22 10:00:55 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-ir-100",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso_8859-1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2005-03-21 19:51:52 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2005-03-22 10:00:55 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"latin1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"l1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm819",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp819",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"csisolatin1",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
2006-10-05 18:28:32 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ia5",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-2",
|
|
|
|
|
(void*)NSISOLatin2StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-2",
|
|
|
|
|
(void*)NSISOLatin2StringEncoding);
|
2010-02-25 09:05:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-symbol",
|
|
|
|
|
(void*)NSSymbolStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-symbol",
|
|
|
|
|
(void*)NSSymbolStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-cp1250",
|
|
|
|
|
(void*)NSWindowsCP1250StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1250",
|
|
|
|
|
(void*)NSWindowsCP1250StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-cp1251",
|
|
|
|
|
(void*)NSWindowsCP1251StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1251",
|
|
|
|
|
(void*)NSWindowsCP1251StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-cp1252",
|
|
|
|
|
(void*)NSWindowsCP1252StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1252",
|
|
|
|
|
(void*)NSWindowsCP1252StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-cp1253",
|
|
|
|
|
(void*)NSWindowsCP1253StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1253",
|
|
|
|
|
(void*)NSWindowsCP1253StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"microsoft-cp1254",
|
|
|
|
|
(void*)NSWindowsCP1254StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1254",
|
|
|
|
|
(void*)NSWindowsCP1254StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-10646-ucs-2",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso10646-ucs-2",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"utf-16",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"utf16",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-10646-1",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"iso10646-1",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"jisx0201.1976",
|
|
|
|
|
(void*)NSShiftJISStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"jisx0201",
|
|
|
|
|
(void*)NSShiftJISStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"shift_JIS",
|
|
|
|
|
(void*)NSShiftJISStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"utf-8",
|
|
|
|
|
(void*)NSUTF8StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"utf8",
|
|
|
|
|
(void*)NSUTF8StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"apple-roman",
|
|
|
|
|
(void*)NSMacOSRomanStringEncoding);
|
|
|
|
|
|
2010-05-31 09:01:46 +00:00
|
|
|
|
/* Also map from Apple encoding names.
|
|
|
|
|
*/
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSASCIIStringEncoding",
|
|
|
|
|
(void*)NSASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSNEXTSTEPStringEncoding",
|
|
|
|
|
(void*)NSNEXTSTEPStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSJapaneseEUCStringEncoding",
|
|
|
|
|
(void*)NSJapaneseEUCStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF8StringEncoding",
|
|
|
|
|
(void*)NSUTF8StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin1StringEncoding",
|
|
|
|
|
(void*)NSISOLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSSymbolStringEncoding",
|
|
|
|
|
(void*)NSSymbolStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSNonLossyASCIIStringEncoding",
|
|
|
|
|
(void*)NSNonLossyASCIIStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSShiftJISStringEncoding",
|
|
|
|
|
(void*)NSShiftJISStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin2StringEncoding",
|
|
|
|
|
(void*)NSISOLatin2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUnicodeStringEncoding",
|
|
|
|
|
(void*)NSUnicodeStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsCP1251StringEncoding",
|
|
|
|
|
(void*)NSWindowsCP1251StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsCP1252StringEncoding",
|
|
|
|
|
(void*)NSWindowsCP1252StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsCP1253StringEncoding",
|
|
|
|
|
(void*)NSWindowsCP1253StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsCP1254StringEncoding",
|
|
|
|
|
(void*)NSWindowsCP1254StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsCP1250StringEncoding",
|
|
|
|
|
(void*)NSWindowsCP1250StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISO2022JPStringEncoding",
|
|
|
|
|
(void*)NSISO2022JPStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSMacOSRomanStringEncoding",
|
|
|
|
|
(void*)NSMacOSRomanStringEncoding);
|
|
|
|
|
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF16BigEndianStringEncoding",
|
|
|
|
|
(void*)NSUTF16BigEndianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF16LittleEndianStringEncoding",
|
|
|
|
|
(void*)NSUTF16LittleEndianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF32StringEncoding",
|
|
|
|
|
(void*)NSUTF32StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF32BigEndianStringEncoding",
|
|
|
|
|
(void*)NSUTF32BigEndianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF32LittleEndianStringEncoding",
|
|
|
|
|
(void*)NSUTF32LittleEndianStringEncoding);
|
|
|
|
|
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#if !defined(NeXT_Foundation_LIBRARY)
|
|
|
|
|
NSMapInsert(charsets, (void*)@"gsm0338",
|
|
|
|
|
(void*)NSGSM0338StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-3",
|
|
|
|
|
(void*)NSISOLatin3StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-3",
|
|
|
|
|
(void*)NSISOLatin3StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-4",
|
|
|
|
|
(void*)NSISOLatin4StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-4",
|
|
|
|
|
(void*)NSISOLatin4StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-5",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOCyrillicStringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-5",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOCyrillicStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-6",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOArabicStringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-6",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOArabicStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-7",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOGreekStringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-7",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOGreekStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-8",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOHebrewStringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-8",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOHebrewStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-9",
|
|
|
|
|
(void*)NSISOLatin5StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-9",
|
|
|
|
|
(void*)NSISOLatin5StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-10",
|
|
|
|
|
(void*)NSISOLatin6StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-10",
|
|
|
|
|
(void*)NSISOLatin6StringEncoding);
|
2006-10-29 09:17:05 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-11",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOThaiStringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-11",
|
2022-01-04 10:12:58 +00:00
|
|
|
|
(void*)NSISOThaiStringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-13",
|
|
|
|
|
(void*)NSISOLatin7StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-13",
|
|
|
|
|
(void*)NSISOLatin7StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-14",
|
|
|
|
|
(void*)NSISOLatin8StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-14",
|
|
|
|
|
(void*)NSISOLatin8StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso-8859-15",
|
|
|
|
|
(void*)NSISOLatin9StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"iso8859-15",
|
|
|
|
|
(void*)NSISOLatin9StringEncoding);
|
2005-03-11 12:58:54 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"big5",
|
2021-12-25 10:01:42 +00:00
|
|
|
|
(void*)NSBig5StringEncoding);
|
2005-03-22 09:16:22 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"utf-7",
|
|
|
|
|
(void*)NSUTF7StringEncoding);
|
2006-11-03 15:19:45 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"utf7",
|
|
|
|
|
(void*)NSUTF7StringEncoding);
|
2006-10-29 09:17:05 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"koi8-r",
|
|
|
|
|
(void*)NSKOI8RStringEncoding);
|
2006-10-29 19:44:31 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ksc5601.1987",
|
2006-10-29 09:17:05 +00:00
|
|
|
|
(void*)NSKoreanEUCStringEncoding);
|
2008-06-15 09:25:52 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ksc5601",
|
|
|
|
|
(void*)NSKoreanEUCStringEncoding);
|
2006-10-29 19:44:31 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ksc5601.1997",
|
2006-10-29 09:17:05 +00:00
|
|
|
|
(void*)NSKoreanEUCStringEncoding);
|
2008-06-15 09:25:52 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ksc5601",
|
|
|
|
|
(void*)NSKoreanEUCStringEncoding);
|
2010-02-25 09:05:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"gb2312.1980",
|
2021-12-25 10:01:42 +00:00
|
|
|
|
(void*)NSChineseEUCStringEncoding);
|
2010-02-25 09:05:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"gb2312",
|
2021-12-25 10:01:42 +00:00
|
|
|
|
(void*)NSChineseEUCStringEncoding);
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm437",
|
|
|
|
|
(void*)NSDOSLatinUSStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp437",
|
|
|
|
|
(void*)NSDOSLatinUSStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm737",
|
|
|
|
|
(void*)NSDOSGreekStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp737",
|
|
|
|
|
(void*)NSDOSGreekStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm775",
|
|
|
|
|
(void*)NSDOSBalticRimStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp775",
|
|
|
|
|
(void*)NSDOSBalticRimStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm850",
|
|
|
|
|
(void*)NSDOSLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp850",
|
|
|
|
|
(void*)NSDOSLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm851",
|
|
|
|
|
(void*)NSDOSGreek1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp851",
|
|
|
|
|
(void*)NSDOSGreek1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm852",
|
|
|
|
|
(void*)NSDOSLatin2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp852",
|
|
|
|
|
(void*)NSDOSLatin2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm855",
|
|
|
|
|
(void*)NSDOSCyrillicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp855",
|
|
|
|
|
(void*)NSDOSCyrillicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm857",
|
|
|
|
|
(void*)NSDOSTurkishStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp857",
|
|
|
|
|
(void*)NSDOSTurkishStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm861",
|
|
|
|
|
(void*)NSDOSIcelandicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp861",
|
|
|
|
|
(void*)NSDOSIcelandicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm862",
|
|
|
|
|
(void*)NSDOSHebrewStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp862",
|
|
|
|
|
(void*)NSDOSHebrewStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm863",
|
|
|
|
|
(void*)NSDOSCanadianFrenchStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp863",
|
|
|
|
|
(void*)NSDOSCanadianFrenchStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm864",
|
|
|
|
|
(void*)NSDOSArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp864",
|
|
|
|
|
(void*)NSDOSArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm865",
|
|
|
|
|
(void*)NSDOSNordicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp865",
|
|
|
|
|
(void*)NSDOSNordicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm866",
|
|
|
|
|
(void*)NSDOSRussianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp866",
|
|
|
|
|
(void*)NSDOSRussianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm869",
|
|
|
|
|
(void*)NSDOSGreek2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp869",
|
|
|
|
|
(void*)NSDOSGreek2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm874",
|
|
|
|
|
(void*)NSDOSThaiStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp874",
|
|
|
|
|
(void*)NSDOSThaiStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm932",
|
|
|
|
|
(void*)NSDOSJapaneseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp932",
|
|
|
|
|
(void*)NSDOSJapaneseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm936",
|
|
|
|
|
(void*)NSDOSChineseSimplifStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp936",
|
|
|
|
|
(void*)NSDOSChineseSimplifStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"gbk",
|
|
|
|
|
(void*)NSDOSChineseSimplifStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm949",
|
|
|
|
|
(void*)NSDOSKoreanStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp949",
|
|
|
|
|
(void*)NSDOSKoreanStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"ibm950",
|
|
|
|
|
(void*)NSDOSChineseTradStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"cp950",
|
|
|
|
|
(void*)NSDOSChineseTradStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1255",
|
|
|
|
|
(void*)NSWindowsHebrewStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1256",
|
|
|
|
|
(void*)NSWindowsArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1257",
|
|
|
|
|
(void*)NSWindowsBalticRimStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1258",
|
|
|
|
|
(void*)NSWindowsVietnameseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"windows-1361",
|
|
|
|
|
(void*)NSWindowsKoreanJohabStringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
|
|
|
|
|
/* Also map from GNUstep encoding names.
|
|
|
|
|
*/
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOCyrillicStringEncoding",
|
|
|
|
|
(void*)NSISOCyrillicStringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSKOI8RStringEncoding",
|
|
|
|
|
(void*)NSKOI8RStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin3StringEncoding",
|
|
|
|
|
(void*)NSISOLatin3StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin4StringEncoding",
|
|
|
|
|
(void*)NSISOLatin4StringEncoding);
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOArabicStringEncoding",
|
|
|
|
|
(void*)NSISOArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOGreekStringEncoding",
|
|
|
|
|
(void*)NSISOGreekStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOHebrewStringEncoding",
|
|
|
|
|
(void*)NSISOHebrewStringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin5StringEncoding",
|
|
|
|
|
(void*)NSISOLatin5StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin6StringEncoding",
|
|
|
|
|
(void*)NSISOLatin6StringEncoding);
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOThaiStringEncoding",
|
|
|
|
|
(void*)NSISOThaiStringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin7StringEncoding",
|
|
|
|
|
(void*)NSISOLatin7StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin8StringEncoding",
|
|
|
|
|
(void*)NSISOLatin8StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSISOLatin9StringEncoding",
|
|
|
|
|
(void*)NSISOLatin9StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSUTF7StringEncoding",
|
|
|
|
|
(void*)NSUTF7StringEncoding);
|
2021-12-25 10:01:42 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSChineseEUCStringEncoding",
|
|
|
|
|
(void*)NSChineseEUCStringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSGSM0338StringEncoding",
|
|
|
|
|
(void*)NSGSM0338StringEncoding);
|
2021-12-25 10:01:42 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSBig5StringEncoding",
|
|
|
|
|
(void*)NSBig5StringEncoding);
|
2010-05-31 09:01:46 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSKoreanEUCStringEncoding",
|
|
|
|
|
(void*)NSKoreanEUCStringEncoding);
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSLatinUSStringEncoding",
|
|
|
|
|
(void*)NSDOSLatinUSStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSGreekStringEncoding",
|
|
|
|
|
(void*)NSDOSGreekStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSBalticRimStringEncoding",
|
|
|
|
|
(void*)NSDOSBalticRimStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSLatin1StringEncoding",
|
|
|
|
|
(void*)NSDOSLatin1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSGreek1StringEncoding",
|
|
|
|
|
(void*)NSDOSGreek1StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSLatin2StringEncoding",
|
|
|
|
|
(void*)NSDOSLatin2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSCyrillicStringEncoding",
|
|
|
|
|
(void*)NSDOSCyrillicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSTurkishStringEncoding",
|
|
|
|
|
(void*)NSDOSTurkishStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOICortugueseStringEncoding",
|
|
|
|
|
(void*)NSDOICortugueseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSIcelandicStringEncoding",
|
|
|
|
|
(void*)NSDOSIcelandicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSHebrewStringEncoding",
|
|
|
|
|
(void*)NSDOSHebrewStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSCanadianFrenchStringEncoding",
|
|
|
|
|
(void*)NSDOSCanadianFrenchStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSArabicStringEncoding",
|
|
|
|
|
(void*)NSDOSArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSNordicStringEncoding",
|
|
|
|
|
(void*)NSDOSNordicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSRussianStringEncoding",
|
|
|
|
|
(void*)NSDOSRussianStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSGreek2StringEncoding",
|
|
|
|
|
(void*)NSDOSGreek2StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSThaiStringEncoding",
|
|
|
|
|
(void*)NSDOSThaiStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSJapaneseStringEncoding",
|
|
|
|
|
(void*)NSDOSJapaneseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSChineseSimplifStringEncoding",
|
|
|
|
|
(void*)NSDOSChineseSimplifStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSKoreanStringEncoding",
|
|
|
|
|
(void*)NSDOSKoreanStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSDOSChineseTradStringEncoding",
|
|
|
|
|
(void*)NSDOSChineseTradStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsHebrewStringEncoding",
|
|
|
|
|
(void*)NSWindowsHebrewStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsArabicStringEncoding",
|
|
|
|
|
(void*)NSWindowsArabicStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsBalticRimStringEncoding",
|
|
|
|
|
(void*)NSWindowsBalticRimStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsVietnameseStringEncoding",
|
|
|
|
|
(void*)NSWindowsVietnameseStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSWindowsKoreanJohabStringEncoding",
|
|
|
|
|
(void*)NSWindowsKoreanJohabStringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSGB_2312_80StringEncoding",
|
|
|
|
|
(void*)NSGB_2312_80StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSGBK_95StringEncoding",
|
|
|
|
|
(void*)NSGBK_95StringEncoding);
|
|
|
|
|
NSMapInsert(charsets, (void*)@"NSGB_18030_2000StringEncoding",
|
|
|
|
|
(void*)NSGB_18030_2000StringEncoding);
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#endif
|
2005-03-11 12:58:54 +00:00
|
|
|
|
}
|
2005-03-21 19:16:15 +00:00
|
|
|
|
if (encodings == 0)
|
|
|
|
|
{
|
2011-05-31 06:46:17 +00:00
|
|
|
|
encodings = NSCreateMapTable (NSIntegerMapKeyCallBacks,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
NSObjectMapValueCallBacks, 0);
|
2013-08-22 15:44:54 +00:00
|
|
|
|
[[NSObject leakAt: &encodings] release];
|
2005-03-22 10:40:37 +00:00
|
|
|
|
|
|
|
|
|
/* While the charset mappings above are many to one,
|
|
|
|
|
* mapping a variety of names to one encoding,
|
|
|
|
|
* the encodings map is a one to one mapping.
|
|
|
|
|
*
|
|
|
|
|
* The charset names used here should be the PREFERRED
|
|
|
|
|
* charset names from the IANA registration if one is
|
|
|
|
|
* specified.
|
|
|
|
|
* We adopt the convention that all names are in lowercase.
|
|
|
|
|
*/
|
2005-03-21 19:16:15 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSASCIIStringEncoding,
|
2005-03-22 09:45:58 +00:00
|
|
|
|
(void*)@"us-ascii");
|
2005-03-21 19:51:52 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin1StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-1");
|
2005-03-21 19:16:15 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin2StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-2");
|
2010-02-25 09:05:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsCP1250StringEncoding,
|
|
|
|
|
(void*)@"windows-1250");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsCP1251StringEncoding,
|
|
|
|
|
(void*)@"windows-1251");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsCP1252StringEncoding,
|
|
|
|
|
(void*)@"windows-1252");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsCP1253StringEncoding,
|
|
|
|
|
(void*)@"windows-1253");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsCP1254StringEncoding,
|
|
|
|
|
(void*)@"windows-1254");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSUnicodeStringEncoding,
|
|
|
|
|
(void*)@"utf-16");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSShiftJISStringEncoding,
|
|
|
|
|
(void*)@"shift_JIS");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSUTF8StringEncoding,
|
|
|
|
|
(void*)@"utf-8");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSMacOSRomanStringEncoding,
|
|
|
|
|
(void*)@"apple-roman");
|
|
|
|
|
#if !defined(NeXT_Foundation_LIBRARY)
|
2005-03-21 19:16:15 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin3StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-3");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin4StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-4");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOCyrillicStringEncoding,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
(void*)@"iso-8859-5");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOArabicStringEncoding,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
(void*)@"iso-8859-6");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOGreekStringEncoding,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
(void*)@"iso-8859-7");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOHebrewStringEncoding,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
(void*)@"iso-8859-8");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin5StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-9");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin6StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-10");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOThaiStringEncoding,
|
2006-10-29 09:17:05 +00:00
|
|
|
|
(void*)@"iso-8859-11");
|
2005-03-21 19:16:15 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin7StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-13");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin8StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-14");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSISOLatin9StringEncoding,
|
|
|
|
|
(void*)@"iso-8859-15");
|
2021-12-25 10:01:42 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSBig5StringEncoding,
|
2005-03-21 19:16:15 +00:00
|
|
|
|
(void*)@"big5");
|
2005-03-22 09:16:22 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSUTF7StringEncoding,
|
|
|
|
|
(void*)@"utf-7");
|
2006-10-05 22:40:18 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSGSM0338StringEncoding,
|
2006-10-05 18:28:32 +00:00
|
|
|
|
(void*)@"gsm0338");
|
2006-10-29 09:17:05 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSKOI8RStringEncoding,
|
|
|
|
|
(void*)@"koi8-r");
|
2021-12-25 10:01:42 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSChineseEUCStringEncoding,
|
2006-10-29 09:17:05 +00:00
|
|
|
|
(void*)@"gb2312.1980");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSKoreanEUCStringEncoding,
|
|
|
|
|
(void*)@"ksc5601.1987");
|
2022-01-04 10:12:58 +00:00
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSLatinUSStringEncoding,
|
|
|
|
|
(void*)@"cp437");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSGreekStringEncoding,
|
|
|
|
|
(void*)@"cp737");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSBalticRimStringEncoding,
|
|
|
|
|
(void*)@"cp775");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSLatin1StringEncoding,
|
|
|
|
|
(void*)@"cp850");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSGreek1StringEncoding,
|
|
|
|
|
(void*)@"cp851");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSLatin2StringEncoding,
|
|
|
|
|
(void*)@"cp852");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSCyrillicStringEncoding,
|
|
|
|
|
(void*)@"cp855");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSTurkishStringEncoding,
|
|
|
|
|
(void*)@"cp857");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSIcelandicStringEncoding,
|
|
|
|
|
(void*)@"cp861");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSHebrewStringEncoding,
|
|
|
|
|
(void*)@"cp862");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSCanadianFrenchStringEncoding,
|
|
|
|
|
(void*)@"cp863");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSArabicStringEncoding,
|
|
|
|
|
(void*)@"cp864");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSNordicStringEncoding,
|
|
|
|
|
(void*)@"cp865");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSRussianStringEncoding,
|
|
|
|
|
(void*)@"cp866");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSGreek2StringEncoding,
|
|
|
|
|
(void*)@"cp869");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSThaiStringEncoding,
|
|
|
|
|
(void*)@"cp874");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSJapaneseStringEncoding,
|
|
|
|
|
(void*)@"cp932");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSChineseSimplifStringEncoding,
|
|
|
|
|
(void*)@"cp936");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSKoreanStringEncoding,
|
|
|
|
|
(void*)@"cp949");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSDOSChineseTradStringEncoding,
|
|
|
|
|
(void*)@"cp950");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsHebrewStringEncoding,
|
|
|
|
|
(void*)@"windows-1255");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsArabicStringEncoding,
|
|
|
|
|
(void*)@"windows-1256");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsBalticRimStringEncoding,
|
|
|
|
|
(void*)@"windows-1257");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsVietnameseStringEncoding,
|
|
|
|
|
(void*)@"windows-1258");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSWindowsKoreanJohabStringEncoding,
|
|
|
|
|
(void*)@"windows-1361");
|
|
|
|
|
NSMapInsert(encodings, (void*)NSGB_18030_2000StringEncoding,
|
|
|
|
|
(void*)@"gb18030");
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#endif
|
2005-03-21 19:16:15 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-26 15:24:05 +00:00
|
|
|
|
/**
|
|
|
|
|
* Adds a part to a multipart document
|
|
|
|
|
*/
|
2002-06-01 08:55:19 +00:00
|
|
|
|
- (void) addContent: (id)newContent
|
2002-05-26 15:24:05 +00:00
|
|
|
|
{
|
2005-03-21 19:16:15 +00:00
|
|
|
|
if ([newContent isKindOfClass: documentClass] == NO)
|
2004-03-29 14:53:37 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Content to add is not a GSMimeDocument"];
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
if (content == nil)
|
|
|
|
|
{
|
|
|
|
|
content = [NSMutableArray new];
|
|
|
|
|
}
|
|
|
|
|
if ([content isKindOfClass: [NSMutableArray class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
[content addObject: newContent];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] passed bad content",
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
|
|
|
|
* This method may be called to add a header to the document.
|
|
|
|
|
* The header must be a mutable dictionary object that contains
|
|
|
|
|
* at least the fields that are standard for all headers.
|
|
|
|
|
* </p>
|
2002-07-03 10:00:39 +00:00
|
|
|
|
* <p>
|
2005-11-06 13:53:40 +00:00
|
|
|
|
* Certain well-known headers are restricted to one occurrence in
|
2002-07-03 10:00:39 +00:00
|
|
|
|
* an email, and when extra copies are added they replace originals.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* The mime-version header is special ... it is inserted before any
|
|
|
|
|
* other mime headers rather than being added at the end.
|
|
|
|
|
* </p>
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (void) addHeader: (GSMimeHeader*)info
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-05-26 15:24:05 +00:00
|
|
|
|
NSString *name = [info name];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2003-06-14 09:40:17 +00:00
|
|
|
|
if (name == nil || [name isEqualToString: @"unknown"] == YES)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] header with invalid name",
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2002-07-03 10:00:39 +00:00
|
|
|
|
if ([name isEqualToString: @"mime-version"] == YES
|
|
|
|
|
|| [name isEqualToString: @"content-disposition"] == YES
|
|
|
|
|
|| [name isEqualToString: @"content-transfer-encoding"] == YES
|
2017-06-30 06:37:05 +00:00
|
|
|
|
|| [name isEqualToString: CteContentType] == YES
|
2002-07-03 10:00:39 +00:00
|
|
|
|
|| [name isEqualToString: @"subject"] == YES)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger index = [self _indexOfHeaderNamed: name];
|
2002-07-03 10:00:39 +00:00
|
|
|
|
|
|
|
|
|
if (index != NSNotFound)
|
|
|
|
|
{
|
|
|
|
|
[headers replaceObjectAtIndex: index withObject: info];
|
|
|
|
|
}
|
|
|
|
|
else if ([name isEqualToString: @"mime-version"] == YES)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger tmp;
|
2002-07-03 10:00:39 +00:00
|
|
|
|
|
|
|
|
|
index = [headers count];
|
|
|
|
|
tmp = [self _indexOfHeaderNamed: @"content-disposition"];
|
|
|
|
|
if (tmp != NSNotFound && tmp < index)
|
|
|
|
|
{
|
|
|
|
|
index = tmp;
|
|
|
|
|
}
|
|
|
|
|
tmp = [self _indexOfHeaderNamed: @"content-transfer-encoding"];
|
|
|
|
|
if (tmp != NSNotFound && tmp < index)
|
|
|
|
|
{
|
|
|
|
|
index = tmp;
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
tmp = [self _indexOfHeaderNamed: CteContentType];
|
2002-07-03 10:00:39 +00:00
|
|
|
|
if (tmp != NSNotFound && tmp < index)
|
|
|
|
|
{
|
|
|
|
|
index = tmp;
|
|
|
|
|
}
|
|
|
|
|
[headers insertObject: info atIndex: index];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[headers addObject: info];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[headers addObject: info];
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-02-01 12:35:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to create a new header and add it to the receiver.<br />
|
|
|
|
|
* Returns the newly created header.<br />
|
|
|
|
|
* See [GSMimeHeader-initWithName:value:parameters:] and -addHeader: methods.
|
|
|
|
|
*/
|
|
|
|
|
- (GSMimeHeader*) addHeader: (NSString*)name
|
|
|
|
|
value: (NSString*)value
|
|
|
|
|
parameters: (NSDictionary*)parameters
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [GSMimeHeader alloc];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
hdr = [hdr initWithName: name
|
|
|
|
|
value: value
|
|
|
|
|
parameters: parameters];
|
2004-02-01 12:35:35 +00:00
|
|
|
|
[self addHeader: hdr];
|
|
|
|
|
RELEASE(hdr);
|
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* This method returns an array containing GSMimeHeader objects
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* representing the headers associated with the document.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* The order of the headers in the array is the order of the
|
|
|
|
|
* headers in the document.
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
- (NSArray*) allHeaders
|
|
|
|
|
{
|
|
|
|
|
return [NSArray arrayWithArray: headers];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2003-02-12 09:32:19 +00:00
|
|
|
|
* This returns the content data of the document in the same format in
|
|
|
|
|
* which the data was placed in the document. This may be one of -
|
2002-03-06 15:50:14 +00:00
|
|
|
|
* <deflist>
|
|
|
|
|
* <term>text</term>
|
|
|
|
|
* <desc>an NSString object</desc>
|
|
|
|
|
* <term>binary</term>
|
|
|
|
|
* <desc>an NSData object</desc>
|
|
|
|
|
* <term>multipart</term>
|
|
|
|
|
* <desc>an NSArray object containing GSMimeDocument objects</desc>
|
|
|
|
|
* </deflist>
|
2003-02-12 09:32:19 +00:00
|
|
|
|
* If you want to be sure that you get a particular type of data, use the
|
|
|
|
|
* -convertToData or -convertToText method.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
- (id) content
|
|
|
|
|
{
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Search the content of this document to locate a part whose content ID
|
|
|
|
|
* matches the specified key. Recursively descend into other documents.<br />
|
2003-09-30 17:47:35 +00:00
|
|
|
|
* Wraps the supplied key in angle brackets if they are not present.<br />
|
2002-05-27 05:03:10 +00:00
|
|
|
|
* Return nil if no match is found, the matching GSMimeDocument otherwise.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
*/
|
2002-05-27 05:03:10 +00:00
|
|
|
|
- (id) contentByID: (NSString*)key
|
|
|
|
|
{
|
2002-11-28 13:24:32 +00:00
|
|
|
|
if ([key hasPrefix: @"<"] == NO)
|
|
|
|
|
{
|
2005-03-21 19:51:52 +00:00
|
|
|
|
key = [NSStringClass stringWithFormat: @"<%@>", key];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
}
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
2002-05-27 05:03:10 +00:00
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
if ([[d contentID] isEqualToString: key] == YES)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
d = [d contentByID: key];
|
|
|
|
|
if (d != nil)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
/**
|
|
|
|
|
* Search the content of this document to locate a part whose content ID
|
|
|
|
|
* matches the specified key. Recursively descend into other documents.<br />
|
|
|
|
|
* Wraps the supplied key in angle brackets if they are not present.<br />
|
|
|
|
|
* Return nil if no match is found, the matching GSMimeDocument otherwise.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
*/
|
2004-08-23 14:29:50 +00:00
|
|
|
|
- (id) contentByLocation: (NSString*)key
|
|
|
|
|
{
|
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
if ([[d contentLocation] isEqualToString: key] == YES)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
d = [d contentByLocation: key];
|
|
|
|
|
if (d != nil)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
2002-05-27 14:03:10 +00:00
|
|
|
|
* Search the content of this document to locate a part whose content-type
|
|
|
|
|
* name or content-disposition name matches the specified key.
|
|
|
|
|
* Recursively descend into other documents.<br />
|
2002-05-27 05:03:10 +00:00
|
|
|
|
* Return nil if no match is found, the matching GSMimeDocument otherwise.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
*/
|
2002-05-27 05:03:10 +00:00
|
|
|
|
- (id) contentByName: (NSString*)key
|
|
|
|
|
{
|
2002-05-27 14:03:10 +00:00
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
2002-05-27 05:03:10 +00:00
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
2002-05-27 14:03:10 +00:00
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
hdr = [d headerNamed: CteContentType];
|
2002-05-27 14:03:10 +00:00
|
|
|
|
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
hdr = [d headerNamed: @"content-disposition"];
|
|
|
|
|
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == YES)
|
2002-05-27 05:03:10 +00:00
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
d = [d contentByName: key];
|
|
|
|
|
if (d != nil)
|
|
|
|
|
{
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-08 18:30:18 +00:00
|
|
|
|
/** Convenience method to fetch the content file name from the content-type
|
|
|
|
|
* or content-disposition header.
|
2002-05-27 05:03:10 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSString*) contentFile
|
|
|
|
|
{
|
2020-08-08 18:30:18 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
|
|
|
|
NSString *str = [hdr parameterForKey: @"name"];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
|
2020-08-08 18:30:18 +00:00
|
|
|
|
if (nil == str)
|
|
|
|
|
{
|
|
|
|
|
hdr = [self headerNamed: @"content-disposition"];
|
|
|
|
|
str = [hdr parameterForKey: @"filename"];
|
|
|
|
|
}
|
|
|
|
|
return str;
|
2002-05-27 05:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convenience method to fetch the content ID from the header.
|
|
|
|
|
*/
|
2002-05-26 16:10:31 +00:00
|
|
|
|
- (NSString*) contentID
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: @"content-id"];
|
|
|
|
|
|
|
|
|
|
return [hdr value];
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to fetch the content location from the header.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) contentLocation
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: @"content-location"];
|
|
|
|
|
|
|
|
|
|
return [hdr value];
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to fetch the content name from the header.
|
|
|
|
|
*/
|
|
|
|
|
- (NSString*) contentName
|
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
|
|
|
|
|
return [hdr parameterForKey: @"name"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convenience method to fetch the content sub-type from the header.
|
|
|
|
|
*/
|
2002-11-28 13:24:32 +00:00
|
|
|
|
- (NSString*) contentSubtype
|
2002-05-26 16:10:31 +00:00
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
2003-10-08 16:26:59 +00:00
|
|
|
|
NSString *val = nil;
|
2002-05-26 16:10:31 +00:00
|
|
|
|
|
2003-10-08 16:26:59 +00:00
|
|
|
|
if (hdr != nil)
|
|
|
|
|
{
|
|
|
|
|
val = [hdr objectForKey: @"Subtype"];
|
|
|
|
|
if (val == nil)
|
|
|
|
|
{
|
|
|
|
|
val = [hdr value];
|
|
|
|
|
if (val != nil)
|
|
|
|
|
{
|
|
|
|
|
NSRange r;
|
|
|
|
|
|
|
|
|
|
r = [val rangeOfString: @"/"];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
val = [val substringFromIndex: r.location + 1];
|
|
|
|
|
r = [val rangeOfString: @"/"];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
val = [val substringToIndex: r.location];
|
|
|
|
|
}
|
|
|
|
|
val = [val stringByTrimmingSpaces];
|
|
|
|
|
[hdr setObject: val forKey: @"Subtype"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
val = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return val;
|
2002-05-26 16:10:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to fetch the content type from the header.
|
|
|
|
|
*/
|
2002-05-26 16:10:31 +00:00
|
|
|
|
- (NSString*) contentType
|
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
2003-10-08 16:26:59 +00:00
|
|
|
|
NSString *val = nil;
|
|
|
|
|
|
|
|
|
|
if (hdr != nil)
|
|
|
|
|
{
|
|
|
|
|
val = [hdr objectForKey: @"Type"];
|
|
|
|
|
if (val == nil)
|
|
|
|
|
{
|
|
|
|
|
val = [hdr value];
|
|
|
|
|
if (val != nil)
|
|
|
|
|
{
|
|
|
|
|
NSRange r;
|
|
|
|
|
|
|
|
|
|
r = [val rangeOfString: @"/"];
|
|
|
|
|
if (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
val = [val substringToIndex: r.location];
|
|
|
|
|
val = [val stringByTrimmingSpaces];
|
|
|
|
|
}
|
|
|
|
|
[hdr setObject: val forKey: @"Type"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-05-26 16:10:31 +00:00
|
|
|
|
|
2003-10-08 16:26:59 +00:00
|
|
|
|
return val;
|
2002-05-26 16:10:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-31 09:02:39 +00:00
|
|
|
|
/**
|
|
|
|
|
* Search the content of this document to locate all parts whose content-type
|
|
|
|
|
* name or content-disposition name matches the specified key.
|
|
|
|
|
* Do <em>NOT</em> recurse into other documents.<br />
|
|
|
|
|
* Return nil if no match is found, an array of matching GSMimeDocument
|
|
|
|
|
* instances otherwise.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
*/
|
2002-05-31 09:02:39 +00:00
|
|
|
|
- (NSArray*) contentsByName: (NSString*)key
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *a = nil;
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
2002-05-31 09:02:39 +00:00
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
BOOL match = YES;
|
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
hdr = [d headerNamed: CteContentType];
|
2002-05-31 09:02:39 +00:00
|
|
|
|
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
|
|
|
|
|
{
|
|
|
|
|
hdr = [d headerNamed: @"content-disposition"];
|
|
|
|
|
if ([[hdr parameterForKey: @"name"] isEqualToString: key] == NO)
|
|
|
|
|
{
|
|
|
|
|
match = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (match == YES)
|
|
|
|
|
{
|
|
|
|
|
if (a == nil)
|
|
|
|
|
{
|
|
|
|
|
a = [NSMutableArray arrayWithCapacity: 4];
|
|
|
|
|
}
|
|
|
|
|
[a addObject: d];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-28 09:47:31 +00:00
|
|
|
|
/**
|
|
|
|
|
* Converts any binary parts of the receiver's content to be base64
|
2016-06-06 21:16:30 +00:00
|
|
|
|
* (or quoted-printable for text parts) encoded rather than 8bit or
|
|
|
|
|
* binary encoded ... a convenience method to make the results of
|
|
|
|
|
* the -rawMimeData method safe for sending via routes which only
|
|
|
|
|
* support 7bit data.
|
2005-09-28 09:47:31 +00:00
|
|
|
|
*/
|
2016-06-06 21:16:30 +00:00
|
|
|
|
- (void) convertTo7BitSafe
|
2005-09-28 09:47:31 +00:00
|
|
|
|
{
|
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
2016-06-06 21:16:30 +00:00
|
|
|
|
[d convertTo7BitSafe];
|
2005-09-28 09:47:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *h = [self headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
NSString *v = [h value];
|
|
|
|
|
|
2016-07-19 12:23:40 +00:00
|
|
|
|
/* If there's no header then the implied encoding is 7bit.
|
|
|
|
|
* When there is a header, there are trwo possible 8bit encodings
|
|
|
|
|
* that we need to deal with...
|
|
|
|
|
*/
|
2016-09-16 12:28:05 +00:00
|
|
|
|
if (v != nil
|
|
|
|
|
&& ([CteBinary caseInsensitiveCompare: v] == NSOrderedSame
|
|
|
|
|
|| [Cte8bit caseInsensitiveCompare: v] == NSOrderedSame))
|
2005-09-28 09:47:31 +00:00
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *t = [self headerNamed: CteContentType];
|
2016-07-19 12:23:40 +00:00
|
|
|
|
NSString *charset = [t parameterForKey: @"charset"];
|
|
|
|
|
BOOL isText = (nil == charset) ? NO : YES;
|
2016-06-06 21:16:30 +00:00
|
|
|
|
|
2016-07-19 12:23:40 +00:00
|
|
|
|
/* The presence of a charset parameter implies that the content
|
|
|
|
|
* is text, but if it's missing we may still have text content
|
|
|
|
|
*/
|
|
|
|
|
if (NO == isText)
|
|
|
|
|
{
|
|
|
|
|
NSString *type = [t objectForKey: @"Type"];
|
|
|
|
|
|
|
|
|
|
if ([type isEqualToString: @"text"] == YES)
|
|
|
|
|
{
|
|
|
|
|
isText = YES;
|
|
|
|
|
}
|
|
|
|
|
else if ([type isEqualToString: @"application"] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSString *subtype = [t objectForKey: @"Subtype"];
|
|
|
|
|
|
|
|
|
|
if ([subtype isEqualToString: @"json"] == YES)
|
|
|
|
|
{
|
|
|
|
|
isText = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (YES == isText)
|
|
|
|
|
{
|
2016-06-07 16:45:56 +00:00
|
|
|
|
NSStringEncoding e;
|
|
|
|
|
|
|
|
|
|
e = [documentClass encodingFromCharset: charset];
|
|
|
|
|
#if defined(NeXT_Foundation_LIBRARY)
|
|
|
|
|
if (e != NSASCIIStringEncoding)
|
|
|
|
|
#else
|
|
|
|
|
if (e != NSASCIIStringEncoding && e != NSUTF7StringEncoding)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
v = CteQuotedPrintable;
|
2016-06-07 16:45:56 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
v = Cte7bit;
|
2016-06-07 16:45:56 +00:00
|
|
|
|
}
|
2016-06-07 07:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
v = CteBase64;
|
2016-06-07 07:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
if (nil == h)
|
|
|
|
|
{
|
2016-06-07 07:20:12 +00:00
|
|
|
|
[self setHeader: @"Content-Transfer-Encoding"
|
2016-06-07 16:45:56 +00:00
|
|
|
|
value: v
|
2016-06-07 07:20:12 +00:00
|
|
|
|
parameters: nil];
|
2016-06-07 07:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-06-07 16:45:56 +00:00
|
|
|
|
[h setValue: v];
|
2016-06-07 07:12:25 +00:00
|
|
|
|
}
|
2005-09-28 09:47:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-06 21:16:30 +00:00
|
|
|
|
- (void) convertToBase64
|
|
|
|
|
{
|
|
|
|
|
[self convertTo7BitSafe];
|
|
|
|
|
}
|
2005-09-28 09:47:31 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2016-06-06 21:16:30 +00:00
|
|
|
|
* Converts any base64 (or quoted-printable) encoded parts of the receiver's
|
|
|
|
|
* content to be binary encoded instead ... a convenience method to
|
2005-09-28 09:47:31 +00:00
|
|
|
|
* shrink down the size of the message when converted to data using
|
|
|
|
|
* the -rawMimeData method.
|
|
|
|
|
*/
|
|
|
|
|
- (void) convertToBinary
|
|
|
|
|
{
|
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [content objectEnumerator];
|
|
|
|
|
GSMimeDocument *d;
|
|
|
|
|
|
|
|
|
|
while ((d = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[d convertToBinary];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *h = [self headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
NSString *v = [h value];
|
|
|
|
|
|
2016-09-16 12:28:05 +00:00
|
|
|
|
if (v != nil
|
|
|
|
|
&& ([CteBase64 caseInsensitiveCompare: v] == NSOrderedSame
|
|
|
|
|
|| [CteQuotedPrintable caseInsensitiveCompare: v] == NSOrderedSame))
|
2005-09-28 09:47:31 +00:00
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
[h setValue: CteBinary];
|
2005-09-28 09:47:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 14:03:10 +00:00
|
|
|
|
/**
|
2002-06-17 15:21:15 +00:00
|
|
|
|
* Return the content as an NSData object (unless it is multipart)<br />
|
|
|
|
|
* Perform conversion from text to data using the charset specified in
|
|
|
|
|
* the content-type header, or infer the charset, and update the header
|
2003-02-12 09:32:19 +00:00
|
|
|
|
* accordingly.<br />
|
|
|
|
|
* If the content can not be represented as a plain NSData object, this
|
|
|
|
|
* method returns nil.
|
2002-05-27 14:03:10 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSData*) convertToData
|
|
|
|
|
{
|
|
|
|
|
NSData *d = nil;
|
|
|
|
|
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if ([content isKindOfClass: NSStringClass] == YES)
|
2002-05-27 14:03:10 +00:00
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
2002-05-27 16:59:43 +00:00
|
|
|
|
NSString *charset = [hdr parameterForKey: @"charset"];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
NSString *subtype;
|
2003-06-14 07:41:29 +00:00
|
|
|
|
NSStringEncoding enc;
|
2002-05-27 14:03:10 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil == charset
|
|
|
|
|
&& nil != (subtype = [self contentSubtype])
|
|
|
|
|
&& [@"xml" caseInsensitiveCompare: subtype] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
/* For an XML document (subtype is xml) we can try to get the
|
|
|
|
|
* characterset by examining the document header.
|
|
|
|
|
*/
|
|
|
|
|
if (nil == (charset = [documentClass charsetForXml: content]))
|
|
|
|
|
{
|
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-03-21 19:16:15 +00:00
|
|
|
|
enc = [documentClass encodingFromCharset: charset];
|
2003-06-14 07:41:29 +00:00
|
|
|
|
d = [content dataUsingEncoding: enc];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil == d)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
|
|
|
|
charset = selectCharacterSet(content, &d);
|
2016-06-07 18:12:13 +00:00
|
|
|
|
if (nil == hdr)
|
|
|
|
|
{
|
|
|
|
|
hdr = [self setHeader: @"Content-Type"
|
|
|
|
|
value: @"text/plain"
|
|
|
|
|
parameters: nil];
|
|
|
|
|
[hdr setObject: @"text" forKey: @"Type"];
|
|
|
|
|
[hdr setObject: @"plain" forKey: @"Subtype"];
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
[hdr setParameter: charset forKey: @"charset"];
|
|
|
|
|
}
|
2002-05-27 14:03:10 +00:00
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
else if ([content isKindOfClass: NSDataClass] == YES)
|
2002-05-27 14:03:10 +00:00
|
|
|
|
{
|
|
|
|
|
d = content;
|
|
|
|
|
}
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the content as an NSString object (unless it is multipart)
|
2003-02-12 09:32:19 +00:00
|
|
|
|
* If the content cannot be represented as text, this returns nil.
|
2002-05-27 14:03:10 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSString*) convertToText
|
|
|
|
|
{
|
|
|
|
|
NSString *s = nil;
|
|
|
|
|
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if ([content isKindOfClass: NSStringClass] == YES)
|
2002-05-27 14:03:10 +00:00
|
|
|
|
{
|
|
|
|
|
s = content;
|
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
else if ([content isKindOfClass: NSDataClass] == YES)
|
2002-05-27 14:03:10 +00:00
|
|
|
|
{
|
2017-06-30 06:37:05 +00:00
|
|
|
|
GSMimeHeader *hdr = [self headerNamed: CteContentType];
|
2002-05-27 16:59:43 +00:00
|
|
|
|
NSString *charset = [hdr parameterForKey: @"charset"];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
NSString *subtype = [self contentSubtype];
|
2002-06-19 11:29:49 +00:00
|
|
|
|
NSStringEncoding enc;
|
2002-05-27 14:03:10 +00:00
|
|
|
|
|
2015-08-28 14:06:35 +00:00
|
|
|
|
if (nil == charset)
|
2015-08-27 13:35:45 +00:00
|
|
|
|
{
|
|
|
|
|
/* Treat xml as a special case ... if we have no charset
|
|
|
|
|
* specified then we can get the charset from the xml header
|
|
|
|
|
*/
|
2015-08-28 14:18:46 +00:00
|
|
|
|
if ([subtype isEqualToString: @"xml"] == YES)
|
2015-08-27 13:35:45 +00:00
|
|
|
|
{
|
|
|
|
|
charset = [documentClass charsetForXml: content];
|
|
|
|
|
}
|
2015-08-28 14:06:35 +00:00
|
|
|
|
if (nil == charset)
|
2015-08-27 13:35:45 +00:00
|
|
|
|
{
|
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-03-21 19:16:15 +00:00
|
|
|
|
enc = [documentClass encodingFromCharset: charset];
|
2015-08-28 14:06:35 +00:00
|
|
|
|
if (NSASCIIStringEncoding == enc)
|
|
|
|
|
{
|
|
|
|
|
enc = NSUTF8StringEncoding;
|
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
s = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
s = [s initWithData: content encoding: enc];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([s autorelease];)
|
2002-05-27 14:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2003-05-13 16:00:21 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns a copy of the receiver.
|
|
|
|
|
*/
|
2002-03-06 15:50:14 +00:00
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
2005-03-21 19:16:15 +00:00
|
|
|
|
GSMimeDocument *c = [documentClass allocWithZone: z];
|
2003-05-13 16:00:21 +00:00
|
|
|
|
|
|
|
|
|
c->headers = [[NSMutableArray allocWithZone: z] initWithArray: headers
|
|
|
|
|
copyItems: YES];
|
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
2003-05-13 16:00:21 +00:00
|
|
|
|
{
|
|
|
|
|
c->content = [[NSMutableArray allocWithZone: z] initWithArray: content
|
|
|
|
|
copyItems: YES];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2003-07-07 05:25:44 +00:00
|
|
|
|
c->content = [content copyWithZone: z];
|
2003-05-13 16:00:21 +00:00
|
|
|
|
}
|
|
|
|
|
return c;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
RELEASE(headers);
|
|
|
|
|
RELEASE(content);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
2004-01-05 18:28:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Deletes all ocurrances of parts identical to aPart from the receiver.<br />
|
|
|
|
|
* Recursively deletes from enclosed documents as necessary.
|
|
|
|
|
*/
|
|
|
|
|
- (void) deleteContent: (GSMimeDocument*)aPart
|
|
|
|
|
{
|
|
|
|
|
if (aPart != nil)
|
|
|
|
|
{
|
|
|
|
|
if ([content isKindOfClass: [NSMutableArray class]] == YES)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger count = [content count];
|
2004-01-05 18:28:18 +00:00
|
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *part = [content objectAtIndex: count];
|
|
|
|
|
|
|
|
|
|
if (part == aPart)
|
|
|
|
|
{
|
|
|
|
|
[content removeObjectAtIndex: count];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[part deleteContent: part]; // Recursive.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
2005-11-06 13:53:40 +00:00
|
|
|
|
* This method removes all occurrences of header objects identical to
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* the one supplied as an argument.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (void) deleteHeader: (GSMimeHeader*)aHeader
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-06-15 12:06:12 +00:00
|
|
|
|
[headers removeObjectIdenticalTo: aHeader];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2005-11-06 13:53:40 +00:00
|
|
|
|
* This method removes all occurrences of headers whose name
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* matches the supplied string.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
- (void) deleteHeaderNamed: (NSString*)name
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger count = [headers count];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
if (count > 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2020-04-15 09:31:55 +00:00
|
|
|
|
oaiIMP imp1;
|
2011-08-17 17:45:10 +00:00
|
|
|
|
boolIMP imp2;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
name = [name lowercaseString];
|
|
|
|
|
|
2020-04-15 09:31:55 +00:00
|
|
|
|
imp1 = (oaiIMP)[headers methodForSelector: @selector(objectAtIndex:)];
|
2011-08-17 17:45:10 +00:00
|
|
|
|
imp2 = (boolIMP)[name methodForSelector: @selector(isEqualToString:)];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
while (count-- > 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
GSMimeHeader *info;
|
|
|
|
|
|
|
|
|
|
info = (*imp1)(headers, @selector(objectAtIndex:), count);
|
|
|
|
|
if ((*imp2)(name, @selector(isEqualToString:), [info name]))
|
|
|
|
|
{
|
|
|
|
|
[headers removeObjectAtIndex: count];
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-29 16:52:31 +00:00
|
|
|
|
- (void) _descriptionTo: (NSMutableString*)m level: (NSUInteger)level
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2015-08-29 16:52:31 +00:00
|
|
|
|
NSUInteger count;
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
NSUInteger pad;
|
|
|
|
|
|
|
|
|
|
for (pad = 0; pad < level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendString: [super description]];
|
|
|
|
|
[m appendString: @"\n"];
|
|
|
|
|
level++;
|
|
|
|
|
if ((count = [headers count]) > 0)
|
|
|
|
|
{
|
|
|
|
|
for (pad = 0; pad < level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendString: @"Headers:\n"];
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
for (pad = 0; pad <= level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendString: [[headers objectAtIndex: index] description]];
|
|
|
|
|
[m appendString: @"\n"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (pad = 0; pad < level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendString: @"Content:\n"];
|
2015-08-27 13:35:45 +00:00
|
|
|
|
if ([content isKindOfClass: NSDataClass])
|
|
|
|
|
{
|
2015-08-30 10:50:59 +00:00
|
|
|
|
NSString *t = [self convertToText];
|
|
|
|
|
NSUInteger l = [content length];
|
|
|
|
|
int hl = (int)(((l + 2) / 3) * 4);
|
|
|
|
|
uint8_t *hex;
|
|
|
|
|
|
|
|
|
|
hex = (uint8_t*)malloc(hl + 1);
|
|
|
|
|
hex[hl] = '\0';
|
|
|
|
|
GSPrivateEncodeBase64([content bytes], l, hex);
|
2015-08-27 13:35:45 +00:00
|
|
|
|
if (nil != t)
|
|
|
|
|
{
|
2015-08-29 16:52:31 +00:00
|
|
|
|
for (pad = 0; pad <= level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendFormat: @"%lu chars: ", (unsigned long)[t length]];
|
|
|
|
|
[m appendString: t];
|
|
|
|
|
[m appendString: @"\n"];
|
2015-08-27 13:35:45 +00:00
|
|
|
|
}
|
2015-08-29 16:52:31 +00:00
|
|
|
|
for (pad = 0; pad <= level; pad++) { [m appendString: @" "]; }
|
2015-08-30 10:50:59 +00:00
|
|
|
|
[m appendFormat: @"%lu bytes: <[%s]>\n", (unsigned long)l, (char*)hex];
|
|
|
|
|
free(hex);
|
2015-08-29 16:52:31 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([content isKindOfClass: NSStringClass])
|
|
|
|
|
{
|
|
|
|
|
for (pad = 0; pad <= level; pad++) { [m appendString: @" "]; }
|
|
|
|
|
[m appendFormat: @"%lu chars: ", (unsigned long)[content length]];
|
|
|
|
|
[m appendString: content];
|
|
|
|
|
[m appendString: @"\n"];
|
2015-08-27 13:35:45 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-08-29 16:52:31 +00:00
|
|
|
|
count = [content count];
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
[[content objectAtIndex: index] _descriptionTo: m
|
|
|
|
|
level: level+1];
|
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
}
|
2015-08-29 16:52:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
|
|
|
|
NSString *s;
|
|
|
|
|
|
2020-02-08 16:42:17 +00:00
|
|
|
|
ENTER_POOL
|
|
|
|
|
NSMutableString *m = [NSMutableString stringWithCapacity: 1000];
|
2015-08-29 16:52:31 +00:00
|
|
|
|
[self _descriptionTo: m level: 0];
|
|
|
|
|
s = RETAIN(m);
|
2020-02-08 16:42:17 +00:00
|
|
|
|
LEAVE_POOL
|
|
|
|
|
|
2015-08-29 16:52:31 +00:00
|
|
|
|
return AUTORELEASE(s);
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
- (NSUInteger) estimatedSize
|
|
|
|
|
{
|
|
|
|
|
NSUInteger total = 0;
|
|
|
|
|
NSEnumerator *enumerator = [headers objectEnumerator];
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
|
|
|
|
/* Accumulate approximate size of all the headers.
|
|
|
|
|
*/
|
|
|
|
|
while (nil != (hdr = [enumerator nextObject]))
|
|
|
|
|
{
|
|
|
|
|
total += [hdr estimatedSize];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([content isKindOfClass: [NSArray class]])
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *doc;
|
|
|
|
|
|
|
|
|
|
/* For each part, add the size of the part plus the typical size
|
|
|
|
|
* of an inter-part boundary.
|
|
|
|
|
*/
|
|
|
|
|
enumerator = [content objectEnumerator];
|
|
|
|
|
while (nil != (doc = [enumerator nextObject]))
|
|
|
|
|
{
|
|
|
|
|
total += [doc estimatedSize] + 40;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* If we base64 encode the data it will be 4/3 the size of the
|
|
|
|
|
* raw byte counts ... assume that as an estimate.
|
|
|
|
|
*/
|
|
|
|
|
total += [content length] * 4 / 3;
|
|
|
|
|
}
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-10 09:28:15 +00:00
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
|
|
|
|
return [[self content] hash];
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
2002-05-26 15:24:05 +00:00
|
|
|
|
* This method returns the first header whose name equals the supplied argument.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
2002-05-26 15:24:05 +00:00
|
|
|
|
- (GSMimeHeader*) headerNamed: (NSString*)name
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
NSUInteger count = [headers count];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
if (count > 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
NSUInteger index;
|
2020-04-15 09:31:55 +00:00
|
|
|
|
oaiIMP imp1;
|
2011-08-17 17:45:10 +00:00
|
|
|
|
boolIMP imp2;
|
2010-09-28 13:23:53 +00:00
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
name = [GSMimeHeader makeToken: name preservingCase: NO];
|
2020-04-15 09:31:55 +00:00
|
|
|
|
imp1 = (oaiIMP)[headers methodForSelector: @selector(objectAtIndex:)];
|
2011-08-17 17:45:10 +00:00
|
|
|
|
imp2 = (boolIMP)[name methodForSelector: @selector(isEqualToString:)];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *info;
|
|
|
|
|
|
|
|
|
|
info = (*imp1)(headers, @selector(objectAtIndex:), index);
|
|
|
|
|
if ((*imp2)(name, @selector(isEqualToString:), [info name]))
|
|
|
|
|
{
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-05-26 16:10:31 +00:00
|
|
|
|
* This method returns an array of GSMimeHeader objects for all headers
|
2002-05-27 05:03:10 +00:00
|
|
|
|
* whose names equal the supplied argument.
|
2002-03-06 15:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSArray*) headersNamed: (NSString*)name
|
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
NSUInteger count;
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
name = [GSMimeHeader makeToken: name preservingCase: NO];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
count = [headers count];
|
|
|
|
|
if (count > 0)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
NSUInteger index;
|
|
|
|
|
NSMutableArray *array;
|
2020-04-15 09:31:55 +00:00
|
|
|
|
oaiIMP imp1;
|
2011-08-17 17:45:10 +00:00
|
|
|
|
boolIMP imp2;
|
2010-09-28 13:23:53 +00:00
|
|
|
|
|
2020-04-15 09:31:55 +00:00
|
|
|
|
imp1 = (oaiIMP)[headers methodForSelector: @selector(objectAtIndex:)];
|
2011-08-17 17:45:10 +00:00
|
|
|
|
imp2 = (boolIMP)[name methodForSelector: @selector(isEqualToString:)];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
array = [NSMutableArray array];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
for (index = 0; index < count; index++)
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
GSMimeHeader *info;
|
|
|
|
|
|
|
|
|
|
info = (*imp1)(headers, @selector(objectAtIndex:), index);
|
|
|
|
|
if ((*imp2)(name, @selector(isEqualToString:), [info name]))
|
|
|
|
|
{
|
|
|
|
|
[array addObject: info];
|
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
2010-09-28 13:23:53 +00:00
|
|
|
|
return array;
|
2005-02-22 11:22:44 +00:00
|
|
|
|
}
|
2010-09-28 13:23:53 +00:00
|
|
|
|
return [NSArray array];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
if ((self = [super init]) != nil)
|
|
|
|
|
{
|
|
|
|
|
headers = [NSMutableArray new];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-10 09:28:15 +00:00
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
|
|
|
{
|
|
|
|
|
if (other == self)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [other isKindOfClass: [GSMimeDocument class]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2012-09-27 08:29:24 +00:00
|
|
|
|
if (NO == [headers isEqual: ((GSMimeDocument*)other)->headers])
|
2010-06-10 09:28:15 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
2012-09-27 08:29:24 +00:00
|
|
|
|
if (NO == [content isEqual: ((GSMimeDocument*)other)->content])
|
2010-06-10 09:28:15 +00:00
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2002-11-26 10:15:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>Make a probably unique string suitable for use as the
|
|
|
|
|
* boundary parameter in the content of a multipart document.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>This implementation provides base64 encoded data
|
|
|
|
|
* consisting of an MD5 digest of some pseudo random stuff,
|
2003-01-23 10:51:51 +00:00
|
|
|
|
* plus an incrementing counter. The inclusion of the counter
|
|
|
|
|
* guarantees that we won't produce two identical strings in
|
|
|
|
|
* the same run of the program.
|
2002-11-26 10:15:35 +00:00
|
|
|
|
* </p>
|
2011-05-22 06:22:05 +00:00
|
|
|
|
* <p>The boundary has a suffix of '=_' to ensure it's not mistaken
|
|
|
|
|
* for quoted-printable data.
|
|
|
|
|
* </p>
|
2002-11-26 10:15:35 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSString*) makeBoundary
|
|
|
|
|
{
|
|
|
|
|
static int count = 0;
|
2015-08-30 09:23:27 +00:00
|
|
|
|
uint8_t output[20];
|
|
|
|
|
uint8_t *ptr;
|
2002-11-26 10:15:35 +00:00
|
|
|
|
NSString *result;
|
2003-01-23 10:51:51 +00:00
|
|
|
|
NSData *source;
|
|
|
|
|
NSData *digest;
|
|
|
|
|
int sequence = ++count;
|
|
|
|
|
|
|
|
|
|
source = [[[NSProcessInfo processInfo] globallyUniqueString]
|
|
|
|
|
dataUsingEncoding: NSUTF8StringEncoding];
|
2005-03-21 19:16:15 +00:00
|
|
|
|
|
2003-01-24 12:06:33 +00:00
|
|
|
|
digest = [source md5Digest];
|
2003-01-23 10:51:51 +00:00
|
|
|
|
memcpy(output, [digest bytes], 16);
|
|
|
|
|
output[16] = (sequence >> 24) & 0xff;
|
|
|
|
|
output[17] = (sequence >> 16) & 0xff;
|
|
|
|
|
output[18] = (sequence >> 8) & 0xff;
|
|
|
|
|
output[19] = sequence & 0xff;
|
2002-11-26 10:15:35 +00:00
|
|
|
|
|
2015-08-30 09:23:27 +00:00
|
|
|
|
ptr = (uint8_t*)NSZoneMalloc(NSDefaultMallocZone(), 30);
|
|
|
|
|
GSPrivateEncodeBase64(output, 20, ptr);
|
|
|
|
|
ptr[28] = '=';
|
|
|
|
|
ptr[29] = '_';
|
2005-03-21 19:51:52 +00:00
|
|
|
|
result = [NSStringClass allocWithZone: NSDefaultMallocZone()];
|
2015-08-30 09:23:27 +00:00
|
|
|
|
result = [result initWithBytesNoCopy: ptr
|
|
|
|
|
length: 30
|
|
|
|
|
encoding: NSASCIIStringEncoding
|
|
|
|
|
freeWhenDone: YES];
|
2002-11-26 10:15:35 +00:00
|
|
|
|
return AUTORELEASE(result);
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Create new content ID header, set it as the content ID of the document
|
2002-11-25 17:02:55 +00:00
|
|
|
|
* and return it.<br />
|
|
|
|
|
* This is a convenience method which simply places angle brackets around
|
2002-11-25 18:18:18 +00:00
|
|
|
|
* an [NSProcessInfo-globallyUniqueString] to form the header value.
|
2002-05-27 05:03:10 +00:00
|
|
|
|
*/
|
|
|
|
|
- (GSMimeHeader*) makeContentID
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
2002-11-25 17:02:55 +00:00
|
|
|
|
NSString *str = [[NSProcessInfo processInfo] globallyUniqueString];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
|
2005-03-21 19:51:52 +00:00
|
|
|
|
str = [NSStringClass stringWithFormat: @"<%@>", str];
|
2016-06-07 18:12:13 +00:00
|
|
|
|
hdr = [self setHeader: @"Content-ID"
|
|
|
|
|
value: str
|
|
|
|
|
parameters: nil];
|
2002-11-25 17:02:55 +00:00
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
|
2003-07-28 10:53:18 +00:00
|
|
|
|
/**
|
2004-02-01 12:35:35 +00:00
|
|
|
|
* Deprecated ... use -setHeader:value:parameters:
|
2003-07-28 10:53:18 +00:00
|
|
|
|
*/
|
|
|
|
|
- (GSMimeHeader*) makeHeader: (NSString*)name
|
|
|
|
|
value: (NSString*)value
|
|
|
|
|
parameters: (NSDictionary*)parameters
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [[GSMimeHeader alloc] initWithName: name
|
|
|
|
|
value: value
|
|
|
|
|
parameters: parameters];
|
2003-07-28 10:53:18 +00:00
|
|
|
|
[self setHeader: hdr];
|
|
|
|
|
RELEASE(hdr);
|
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
|
2002-11-25 17:02:55 +00:00
|
|
|
|
/**
|
|
|
|
|
* Create new message ID header, set it as the message ID of the document
|
|
|
|
|
* and return it.<br />
|
|
|
|
|
* This is a convenience method which simply places angle brackets around
|
2002-11-25 18:18:18 +00:00
|
|
|
|
* an [NSProcessInfo-globallyUniqueString] to form the header value.
|
2002-11-25 17:02:55 +00:00
|
|
|
|
*/
|
|
|
|
|
- (GSMimeHeader*) makeMessageID
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
NSString *str = [[NSProcessInfo processInfo] globallyUniqueString];
|
|
|
|
|
|
2005-03-21 19:51:52 +00:00
|
|
|
|
str = [NSStringClass stringWithFormat: @"<%@>", str];
|
2016-06-07 18:12:13 +00:00
|
|
|
|
hdr = [self setHeader: @"Message-ID"
|
|
|
|
|
value: str
|
|
|
|
|
parameters: nil];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-17 15:21:15 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return an NSData object representing the MIME document as raw data
|
2002-06-18 12:07:57 +00:00
|
|
|
|
* ready to be sent via an email system.<br />
|
|
|
|
|
* Calls -rawMimeData: with the isOuter flag set to YES.
|
2002-06-17 15:21:15 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSMutableData*) rawMimeData
|
2002-06-18 12:07:57 +00:00
|
|
|
|
{
|
|
|
|
|
return [self rawMimeData: YES];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2002-11-26 10:47:46 +00:00
|
|
|
|
* <p>Return an NSData object representing the MIME document as raw data
|
|
|
|
|
* ready to be sent via an email system.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>The isOuter flag denotes whether this document is the outermost
|
2002-06-18 12:07:57 +00:00
|
|
|
|
* part of a MIME message, or is a part of a multipart message.
|
2002-11-26 10:47:46 +00:00
|
|
|
|
* </p>
|
2012-11-27 16:13:25 +00:00
|
|
|
|
* <p>Long lines are folded at the default column.
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
- (NSMutableData*) rawMimeData: (BOOL)isOuter
|
|
|
|
|
{
|
2015-07-16 08:44:15 +00:00
|
|
|
|
// 78 is the maximum line length specified by MIME RFCs
|
2012-11-27 16:13:25 +00:00
|
|
|
|
return [self rawMimeData: isOuter foldedAt: 78];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Return an NSData object representing the MIME document as raw data
|
|
|
|
|
* ready to be sent via an email system.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>The isOuter flag denotes whether this document is the outermost
|
|
|
|
|
* part of a MIME message, or is a part of a multipart message.
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>The fold number specifes the column at which lines are considered
|
|
|
|
|
* to be 'long', and get broken/folded.
|
|
|
|
|
* </p>
|
2002-11-26 10:47:46 +00:00
|
|
|
|
* <p>During generation of the document this method will perform some
|
|
|
|
|
* consistency checks and try to automatically generate missing header
|
|
|
|
|
* information needed to build the mime data (eg. filling in the boundary
|
|
|
|
|
* parameter in the content-type header for multipart documents).<br />
|
|
|
|
|
* However, you should not depend on automatic behaviors but should
|
|
|
|
|
* fill in as much detail as possible before generating data.
|
|
|
|
|
* </p>
|
2002-06-18 12:07:57 +00:00
|
|
|
|
*/
|
2012-11-27 16:13:25 +00:00
|
|
|
|
- (NSMutableData*) rawMimeData: (BOOL)isOuter foldedAt: (NSUInteger)fold
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2002-11-28 13:24:32 +00:00
|
|
|
|
NSMutableArray *partData = nil;
|
|
|
|
|
NSMutableData *md = [NSMutableData dataWithCapacity: 1024];
|
2011-02-28 19:49:57 +00:00
|
|
|
|
NSData *d = nil;
|
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
|
GSMimeHeader *type;
|
|
|
|
|
GSMimeHeader *enc;
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
NSData *boundary = 0;
|
|
|
|
|
BOOL contentIsBinary = NO;
|
|
|
|
|
BOOL contentIs7bit = YES;
|
|
|
|
|
NSUInteger count;
|
|
|
|
|
NSUInteger i;
|
|
|
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2002-06-18 12:07:57 +00:00
|
|
|
|
if (isOuter == YES)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2002-06-18 12:07:57 +00:00
|
|
|
|
/*
|
|
|
|
|
* Ensure there is a mime version header.
|
|
|
|
|
*/
|
2018-02-11 12:03:52 +00:00
|
|
|
|
if (nil == [self headerNamed: @"mime-version"])
|
2002-06-18 12:07:57 +00:00
|
|
|
|
{
|
2018-02-11 12:03:52 +00:00
|
|
|
|
[self setHeader: @"MIME-Version"
|
|
|
|
|
value: @"1.0"
|
|
|
|
|
parameters: nil];
|
2002-06-18 12:07:57 +00:00
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2003-10-08 13:19:20 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Inner documents should not contain the mime version header.
|
|
|
|
|
*/
|
2018-02-11 12:03:52 +00:00
|
|
|
|
if (nil != (hdr = [self headerNamed: @"mime-version"]))
|
2003-10-08 13:19:20 +00:00
|
|
|
|
{
|
|
|
|
|
[self deleteHeader: hdr];
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2004-08-23 14:29:50 +00:00
|
|
|
|
if ([content isKindOfClass: NSArrayClass] == YES)
|
2002-11-26 10:15:35 +00:00
|
|
|
|
{
|
2002-11-28 13:24:32 +00:00
|
|
|
|
count = [content count];
|
|
|
|
|
partData = [NSMutableArray arrayWithCapacity: count];
|
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *part = [content objectAtIndex: i];
|
|
|
|
|
|
2012-11-27 16:13:25 +00:00
|
|
|
|
[partData addObject: [part rawMimeData: NO foldedAt: fold]];
|
2003-04-02 08:44:46 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If any part of a multipart document is not 7bit then
|
|
|
|
|
* the document as a whole must not be 7bit either.
|
2003-06-14 09:40:17 +00:00
|
|
|
|
* It is important to check this *after* the part has been
|
2012-11-27 16:13:25 +00:00
|
|
|
|
* processed by -rawMimeData:foldedAt:, so we know that the
|
|
|
|
|
* encoding set for the part is valid.
|
2005-02-22 11:22:44 +00:00
|
|
|
|
*/
|
2003-06-14 07:41:29 +00:00
|
|
|
|
if (contentIs7bit == YES)
|
2003-04-02 08:44:46 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *v;
|
|
|
|
|
|
|
|
|
|
enc = [part headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
v = [enc value];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil != v
|
|
|
|
|
&& ([Cte8bit caseInsensitiveCompare: v] == NSOrderedSame
|
|
|
|
|
|| [CteBinary caseInsensitiveCompare: v] == NSOrderedSame))
|
2003-04-02 08:44:46 +00:00
|
|
|
|
{
|
2003-06-14 07:41:29 +00:00
|
|
|
|
contentIs7bit = NO;
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if ([CteBinary caseInsensitiveCompare: v] == NSOrderedSame)
|
2003-06-14 09:40:17 +00:00
|
|
|
|
{
|
|
|
|
|
contentIsBinary = YES;
|
|
|
|
|
}
|
2003-04-02 08:44:46 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-11-28 13:24:32 +00:00
|
|
|
|
}
|
2002-11-26 10:15:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
type = [self headerNamed: CteContentType];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
if (type == nil)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Attempt to infer the content type from the content.
|
|
|
|
|
*/
|
2002-11-28 13:24:32 +00:00
|
|
|
|
if (partData != nil)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2002-11-28 11:48:35 +00:00
|
|
|
|
[self setContent: content type: @"multipart/mixed" name: nil];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2005-03-21 19:51:52 +00:00
|
|
|
|
else if ([content isKindOfClass: NSStringClass] == YES)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2002-11-28 11:48:35 +00:00
|
|
|
|
[self setContent: content type: @"text/plain" name: nil];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
else if ([content isKindOfClass: NSDataClass] == YES)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
|
|
|
|
[self setContent: content
|
2002-11-28 11:48:35 +00:00
|
|
|
|
type: @"application/octet-stream"
|
2002-06-17 15:21:15 +00:00
|
|
|
|
name: nil];
|
|
|
|
|
}
|
2005-09-23 11:43:23 +00:00
|
|
|
|
else if (content == nil)
|
|
|
|
|
{
|
|
|
|
|
[self setContent: @"" type: @"text/plain" name: nil];
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] with bad content",
|
2002-06-17 15:21:15 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
type = [self headerNamed: CteContentType];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2002-06-19 15:57:23 +00:00
|
|
|
|
|
2002-11-28 13:24:32 +00:00
|
|
|
|
if (partData != nil)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *v;
|
2003-06-14 07:41:29 +00:00
|
|
|
|
BOOL shouldSet;
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2002-06-19 15:57:23 +00:00
|
|
|
|
enc = [self headerNamed: @"content-transfer-encoding"];
|
2003-04-02 08:44:46 +00:00
|
|
|
|
v = [enc value];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil == v)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* For 7bit encoding, we can accept the setting if the content
|
|
|
|
|
* is all 7bit data, otherwise we must change it to 8bit so
|
|
|
|
|
* that the content can be handled properly.
|
|
|
|
|
*/
|
|
|
|
|
if (contentIs7bit == YES)
|
|
|
|
|
{
|
|
|
|
|
shouldSet = NO;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
shouldSet = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBinary caseInsensitiveCompare: v] == NSOrderedSame)
|
2003-04-02 08:44:46 +00:00
|
|
|
|
{
|
2003-06-14 07:41:29 +00:00
|
|
|
|
/*
|
2003-06-14 09:40:17 +00:00
|
|
|
|
* For binary encoding, we can just accept the setting.
|
2003-06-14 07:41:29 +00:00
|
|
|
|
*/
|
|
|
|
|
shouldSet = NO;
|
2003-04-02 08:44:46 +00:00
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([Cte8bit caseInsensitiveCompare: v] == NSOrderedSame)
|
2003-06-14 09:40:17 +00:00
|
|
|
|
{
|
|
|
|
|
if (contentIsBinary == YES)
|
|
|
|
|
{
|
|
|
|
|
shouldSet = YES; // Need to promote from 8bit to binary
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
shouldSet = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([v isEqualToString: Cte7bit] == YES
|
|
|
|
|
|| [v isEqualToString: CteQuotedPrintable] == YES
|
|
|
|
|
|| [v isEqualToString: CteXuuencode] == YES)
|
2003-06-14 07:41:29 +00:00
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* For 7bit encoding, we can accept the setting if the content
|
|
|
|
|
* is all 7bit data, otherwise we must change it to 8bit so
|
|
|
|
|
* that the content can be handled properly.
|
|
|
|
|
*/
|
|
|
|
|
if (contentIs7bit == YES)
|
|
|
|
|
{
|
|
|
|
|
shouldSet = NO;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
shouldSet = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* A multipart document can't have any other encoding, so we need
|
|
|
|
|
* to fix it.
|
|
|
|
|
*/
|
|
|
|
|
shouldSet = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldSet == YES)
|
|
|
|
|
{
|
2003-06-14 09:40:17 +00:00
|
|
|
|
NSString *encoding;
|
|
|
|
|
|
2003-06-14 07:41:29 +00:00
|
|
|
|
/*
|
|
|
|
|
* Force a change to the current transfer encoding setting.
|
|
|
|
|
*/
|
2003-06-14 09:40:17 +00:00
|
|
|
|
if (contentIs7bit == YES)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = Cte7bit;
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
else if (contentIsBinary == YES)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteBinary;
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = Cte8bit;
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
if (enc == nil)
|
|
|
|
|
{
|
2016-06-07 18:12:13 +00:00
|
|
|
|
enc = [self setHeader: @"Content-Transfer-Encoding"
|
|
|
|
|
value: encoding
|
|
|
|
|
parameters: nil];
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[enc setValue: encoding];
|
|
|
|
|
}
|
2002-06-21 17:06:50 +00:00
|
|
|
|
}
|
2003-06-14 07:41:29 +00:00
|
|
|
|
|
2002-06-17 15:21:15 +00:00
|
|
|
|
v = [type parameterForKey: @"boundary"];
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
2002-11-26 10:15:35 +00:00
|
|
|
|
v = [self makeBoundary];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
[type setParameter: v forKey: @"boundary"];
|
|
|
|
|
}
|
|
|
|
|
boundary = [v dataUsingEncoding: NSASCIIStringEncoding];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
|
|
|
|
|
v = [type objectForKey: @"Subtype"];
|
2003-06-14 09:40:17 +00:00
|
|
|
|
if ([v isEqualToString: @"related"] == YES)
|
2002-11-28 13:24:32 +00:00
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *start;
|
|
|
|
|
|
|
|
|
|
v = [type parameterForKey: @"start"];
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
start = [content objectAtIndex: 0];
|
2003-01-22 10:54:29 +00:00
|
|
|
|
#if 0
|
|
|
|
|
/*
|
|
|
|
|
* The 'start' parameter is not compulsory ... should we
|
|
|
|
|
* force it to be set anyway in case some dumb software
|
|
|
|
|
* doesn't default to the first part of the message?
|
|
|
|
|
*/
|
2003-01-21 16:53:56 +00:00
|
|
|
|
v = [start contentID];
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
hdr = [start makeContentID];
|
|
|
|
|
v = [hdr value];
|
|
|
|
|
}
|
|
|
|
|
[type setParameter: v forKey: @"start"];
|
2003-01-22 10:54:29 +00:00
|
|
|
|
#endif
|
2002-11-28 13:24:32 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
start = [self contentByID: v];
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
hdr = [start headerNamed: CteContentType];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
v = [hdr value];
|
|
|
|
|
/*
|
|
|
|
|
* If there is no 'type' parameter, we can fill it in automatically.
|
|
|
|
|
*/
|
|
|
|
|
if ([type parameterForKey: @"type"] == nil)
|
|
|
|
|
{
|
|
|
|
|
[type setParameter: v forKey: @"type"];
|
|
|
|
|
}
|
|
|
|
|
if ([v isEqual: [type parameterForKey: @"type"]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2003-05-11 07:42:16 +00:00
|
|
|
|
format: @"multipart/related 'type' (%@) does not match "
|
2016-02-17 14:11:50 +00:00
|
|
|
|
@"that of the 'start' part (%@) in %@",
|
|
|
|
|
[type parameterForKey: @"type"], v, self];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2002-06-19 15:57:23 +00:00
|
|
|
|
else
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2003-06-14 09:40:17 +00:00
|
|
|
|
NSString *encoding;
|
|
|
|
|
|
2002-06-18 12:07:57 +00:00
|
|
|
|
d = [self convertToData];
|
2002-06-19 15:57:23 +00:00
|
|
|
|
enc = [self headerNamed: @"content-transfer-encoding"];
|
2003-06-14 09:40:17 +00:00
|
|
|
|
encoding = [enc value];
|
|
|
|
|
if (encoding == nil)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
2003-06-14 09:40:17 +00:00
|
|
|
|
if ([[type objectForKey: @"Type"] isEqualToString: @"text"] == YES)
|
2002-06-18 12:07:57 +00:00
|
|
|
|
{
|
2005-03-22 10:00:55 +00:00
|
|
|
|
NSString *charset;
|
|
|
|
|
NSStringEncoding e;
|
2002-06-19 15:57:23 +00:00
|
|
|
|
|
2005-03-22 10:00:55 +00:00
|
|
|
|
charset = [type parameterForKey: @"charset"];
|
|
|
|
|
e = [documentClass encodingFromCharset: charset];
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#if defined(NeXT_Foundation_LIBRARY)
|
|
|
|
|
if (e != NSASCIIStringEncoding)
|
|
|
|
|
#else
|
2005-03-22 10:00:55 +00:00
|
|
|
|
if (e != NSASCIIStringEncoding && e != NSUTF7StringEncoding)
|
2010-02-25 09:05:58 +00:00
|
|
|
|
#endif
|
2002-06-19 15:57:23 +00:00
|
|
|
|
{
|
2016-06-07 18:12:13 +00:00
|
|
|
|
enc = [self setHeader: @"Content-Transfer-Encoding"
|
2016-08-03 09:24:53 +00:00
|
|
|
|
value: Cte8bit
|
2002-06-19 15:57:23 +00:00
|
|
|
|
parameters: nil];
|
|
|
|
|
}
|
2002-06-18 12:07:57 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-06-07 18:12:13 +00:00
|
|
|
|
enc = [self setHeader: @"Content-Transfer-Encoding"
|
2016-08-03 09:24:53 +00:00
|
|
|
|
value: CteBase64
|
2016-06-07 18:12:13 +00:00
|
|
|
|
parameters: nil];
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (encoding == nil
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|| [Cte7bit caseInsensitiveCompare: encoding] == NSOrderedSame
|
|
|
|
|
|| [Cte8bit caseInsensitiveCompare: encoding] == NSOrderedSame)
|
2003-06-14 09:40:17 +00:00
|
|
|
|
{
|
|
|
|
|
unsigned char *bytes = (unsigned char*)[d bytes];
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger length = [d length];
|
2003-06-14 09:40:17 +00:00
|
|
|
|
BOOL hadCarriageReturn = NO;
|
2016-06-08 11:49:42 +00:00
|
|
|
|
BOOL want7Bit = YES;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger lineLength = 0;
|
|
|
|
|
NSUInteger i;
|
2003-06-14 09:40:17 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil != encoding
|
|
|
|
|
&& [Cte8bit caseInsensitiveCompare: encoding] == NSOrderedSame)
|
2016-06-08 11:49:42 +00:00
|
|
|
|
{
|
|
|
|
|
want7Bit = NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check to see if the data is actually compatible (unaltered)
|
|
|
|
|
* with the specified content transfer encoding.
|
|
|
|
|
*/
|
2003-06-14 09:40:17 +00:00
|
|
|
|
for (i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
unsigned char c = bytes[i];
|
|
|
|
|
|
|
|
|
|
if (hadCarriageReturn == YES)
|
|
|
|
|
{
|
|
|
|
|
if (c != '\n')
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteBinary; // CR not part of CRLF
|
2003-06-14 09:40:17 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
hadCarriageReturn = NO;
|
|
|
|
|
lineLength = 0;
|
|
|
|
|
}
|
2003-06-18 15:48:13 +00:00
|
|
|
|
else if (c == '\n')
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteBinary; // LF not part of CRLF
|
2003-06-18 15:48:13 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2003-06-14 09:40:17 +00:00
|
|
|
|
else if (c == '\r')
|
|
|
|
|
{
|
|
|
|
|
hadCarriageReturn = YES;
|
|
|
|
|
}
|
|
|
|
|
else if (++lineLength > 998)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteBinary; // Line of more than 998
|
2003-06-14 09:40:17 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c == 0)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteBinary;
|
2003-06-14 09:40:17 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if (c > 127)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = Cte8bit; // Not 7bit data
|
2003-06-14 09:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (encoding != nil)
|
|
|
|
|
{
|
2016-07-19 12:23:40 +00:00
|
|
|
|
/* Not OK ... need to change content transfer encoding.
|
2016-06-08 11:49:42 +00:00
|
|
|
|
*/
|
|
|
|
|
if (YES == want7Bit)
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
encoding = CteQuotedPrintable;
|
2016-06-08 11:49:42 +00:00
|
|
|
|
}
|
|
|
|
|
enc = [self setHeader: @"Content-Transfer-Encoding"
|
|
|
|
|
value: encoding
|
|
|
|
|
parameters: nil];
|
2002-06-18 12:07:57 +00:00
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Add all the headers.
|
|
|
|
|
*/
|
|
|
|
|
enumerator = [headers objectEnumerator];
|
|
|
|
|
while ((hdr = [enumerator nextObject]) != nil)
|
|
|
|
|
{
|
2024-02-02 15:40:44 +00:00
|
|
|
|
[hdr rawMimeDataPreservingCase: NO foldedAt: fold to: md];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-11-28 13:24:32 +00:00
|
|
|
|
if (partData != nil)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
|
|
|
|
count = [content count];
|
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
|
{
|
2002-06-21 17:06:50 +00:00
|
|
|
|
GSMimeDocument *part = [content objectAtIndex: i];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
NSMutableData *rawPart = [partData objectAtIndex: i];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2003-06-14 07:41:29 +00:00
|
|
|
|
if (contentIs7bit == YES)
|
2002-06-21 17:06:50 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *v;
|
|
|
|
|
|
|
|
|
|
enc = [part headerNamed: @"content-transport-encoding"];
|
|
|
|
|
v = [enc value];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (v != nil
|
|
|
|
|
&& ([Cte8bit caseInsensitiveCompare: v] == NSOrderedSame
|
|
|
|
|
|| [CteBinary caseInsensitiveCompare: v] == NSOrderedSame))
|
2002-06-21 17:06:50 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] bad part encoding for 7bit container",
|
2002-06-21 17:06:50 +00:00
|
|
|
|
NSStringFromClass([self class]),
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-07-03 11:42:02 +00:00
|
|
|
|
/*
|
|
|
|
|
* For a multipart document, insert the boundary before each part.
|
|
|
|
|
*/
|
2002-06-18 11:08:37 +00:00
|
|
|
|
[md appendBytes: "\r\n--" length: 4];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
[md appendData: boundary];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
2002-07-03 11:42:02 +00:00
|
|
|
|
[md appendData: rawPart];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2002-07-03 11:42:02 +00:00
|
|
|
|
[md appendBytes: "\r\n--" length: 4];
|
|
|
|
|
[md appendData: boundary];
|
|
|
|
|
[md appendBytes: "--\r\n" length: 4];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
NSString *v = [enc value];
|
|
|
|
|
|
2002-07-03 11:42:02 +00:00
|
|
|
|
/*
|
|
|
|
|
* Separate headers from body.
|
|
|
|
|
*/
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil == v)
|
|
|
|
|
{
|
|
|
|
|
[md appendData: d];
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBase64 caseInsensitiveCompare: v] == NSOrderedSame)
|
2002-06-17 15:21:15 +00:00
|
|
|
|
{
|
|
|
|
|
const char *ptr;
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger len;
|
|
|
|
|
NSUInteger pos = 0;
|
2002-06-17 15:21:15 +00:00
|
|
|
|
|
2005-03-21 19:16:15 +00:00
|
|
|
|
d = [documentClass encodeBase64: d];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
ptr = [d bytes];
|
|
|
|
|
len = [d length];
|
|
|
|
|
|
|
|
|
|
while (len - pos > 76)
|
|
|
|
|
{
|
|
|
|
|
[md appendBytes: &ptr[pos] length: 76];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
pos += 76;
|
|
|
|
|
}
|
2005-03-16 14:38:04 +00:00
|
|
|
|
if (pos < len)
|
|
|
|
|
{
|
|
|
|
|
[md appendBytes: &ptr[pos] length: len-pos];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([CteQuotedPrintable caseInsensitiveCompare: v] == NSOrderedSame)
|
2011-05-22 06:22:05 +00:00
|
|
|
|
{
|
|
|
|
|
encodeQuotedPrintable(md, [d bytes], [d length]);
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
else if ([CteXuuencode caseInsensitiveCompare: v] == NSOrderedSame)
|
2004-02-19 11:21:46 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *name;
|
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
name = [[self headerNamed: CteContentType] parameterForKey: @"name"];
|
2004-02-19 11:21:46 +00:00
|
|
|
|
if (name == nil)
|
|
|
|
|
{
|
2011-03-09 10:24:18 +00:00
|
|
|
|
name = @"untitled";
|
2004-02-19 11:21:46 +00:00
|
|
|
|
}
|
2011-03-09 10:24:18 +00:00
|
|
|
|
[d uuencodeInto: md name: name mode: 0644];
|
2004-02-19 11:21:46 +00:00
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[md appendData: d];
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-05-27 11:48:44 +00:00
|
|
|
|
[arp drain];
|
2002-06-17 15:21:15 +00:00
|
|
|
|
return md;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* Sets a new value for the content of the document.
|
|
|
|
|
*/
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (void) setContent: (id)newContent
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2005-03-21 19:51:52 +00:00
|
|
|
|
if ([newContent isKindOfClass: NSStringClass] == YES)
|
2002-05-26 15:24:05 +00:00
|
|
|
|
{
|
2002-06-17 15:21:15 +00:00
|
|
|
|
if (newContent != content)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(content, newContent);
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
2015-08-27 13:35:45 +00:00
|
|
|
|
else if ([newContent isKindOfClass: NSDataClass] == YES)
|
2002-05-26 15:24:05 +00:00
|
|
|
|
{
|
2002-06-17 15:21:15 +00:00
|
|
|
|
if (newContent != content)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(content, newContent);
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
2004-08-23 14:29:50 +00:00
|
|
|
|
else if ([newContent isKindOfClass: NSArrayClass] == YES)
|
2002-05-26 15:24:05 +00:00
|
|
|
|
{
|
2002-06-17 15:21:15 +00:00
|
|
|
|
if (newContent != content)
|
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger c = [newContent count];
|
2004-03-29 14:53:37 +00:00
|
|
|
|
|
|
|
|
|
while (c-- > 0)
|
|
|
|
|
{
|
|
|
|
|
id o = [newContent objectAtIndex: c];
|
|
|
|
|
|
2005-03-21 19:16:15 +00:00
|
|
|
|
if ([o isKindOfClass: documentClass] == NO)
|
2004-03-29 14:53:37 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Content contains non-GSMimeDocument"];
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-06-17 15:21:15 +00:00
|
|
|
|
newContent = [newContent mutableCopy];
|
|
|
|
|
ASSIGN(content, newContent);
|
|
|
|
|
RELEASE(newContent);
|
|
|
|
|
}
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2005-05-04 17:19:11 +00:00
|
|
|
|
format: @"[%@ -%@] passed bad content: %@",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
|
|
|
|
|
newContent];
|
2002-05-26 15:24:05 +00:00
|
|
|
|
}
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-10-02 12:23:36 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method calling -setContent:type:name: to set document
|
2002-11-28 11:48:35 +00:00
|
|
|
|
* content and type with a nil value for name ... useful for top-level
|
2002-11-25 17:02:55 +00:00
|
|
|
|
* documents rather than parts within a document (parts should really
|
2002-10-02 12:23:36 +00:00
|
|
|
|
* be named).
|
|
|
|
|
*/
|
|
|
|
|
- (void) setContent: (id)newContent
|
|
|
|
|
type: (NSString*)type
|
|
|
|
|
{
|
|
|
|
|
[self setContent: newContent type: type name: nil];
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-27 05:03:10 +00:00
|
|
|
|
/**
|
2002-11-26 10:15:35 +00:00
|
|
|
|
* <p>Convenience method to set the content of the document along with
|
|
|
|
|
* creating a content-type header for it.
|
|
|
|
|
* </p>
|
2002-11-29 08:01:08 +00:00
|
|
|
|
* <p>The type parameter may be a simple common content type (text,
|
|
|
|
|
* multipart, or application), in which case the default subtype for
|
|
|
|
|
* that type is used. Alternatively it may be full detail of a
|
|
|
|
|
* content type header value, which will be parsed into 'type', 'subtype'
|
|
|
|
|
* and 'parameters'.<br />
|
|
|
|
|
* NB. In this case, if the parsed data contains a 'name' parameter
|
|
|
|
|
* and the name argument is non-nil, the argument value will
|
|
|
|
|
* override the parsed value.
|
|
|
|
|
* </p>
|
2002-11-26 10:15:35 +00:00
|
|
|
|
* <p>You can get the same effect by calling -setContent: to set the document
|
2002-11-25 17:02:55 +00:00
|
|
|
|
* content, then creating a [GSMimeHeader] instance, initialising it with
|
|
|
|
|
* the content type information you want using
|
|
|
|
|
* [GSMimeHeader-initWithName:value:parameters:], and calling the
|
|
|
|
|
* -setHeader: method to attach it to the document.
|
2002-11-26 10:15:35 +00:00
|
|
|
|
* </p>
|
|
|
|
|
* <p>Using this method imposes a few extra checks and restrictions on the
|
|
|
|
|
* combination of content and type/subtype you may use ... so you may want
|
|
|
|
|
* to use the more primitive methods in order to bypass these checks if
|
|
|
|
|
* you are using unusual type/subtype information or if you need to provide
|
2002-11-29 08:01:08 +00:00
|
|
|
|
* additional parameters in the header.
|
2002-11-26 10:15:35 +00:00
|
|
|
|
* </p>
|
2002-05-27 05:03:10 +00:00
|
|
|
|
*/
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (void) setContent: (id)newContent
|
2002-05-27 05:03:10 +00:00
|
|
|
|
type: (NSString*)type
|
|
|
|
|
name: (NSString*)name
|
|
|
|
|
{
|
2011-02-28 19:49:57 +00:00
|
|
|
|
NSString *subtype = nil;
|
|
|
|
|
GSMimeHeader *hdr = nil;
|
|
|
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
2002-11-28 11:48:35 +00:00
|
|
|
|
|
|
|
|
|
if (type == nil)
|
|
|
|
|
{
|
|
|
|
|
type = @"text";
|
|
|
|
|
}
|
|
|
|
|
|
2003-06-14 09:40:17 +00:00
|
|
|
|
if ([type isEqualToString: @"text"] == YES)
|
2002-11-28 11:48:35 +00:00
|
|
|
|
{
|
|
|
|
|
subtype = @"plain";
|
|
|
|
|
}
|
2003-06-14 09:40:17 +00:00
|
|
|
|
else if ([type isEqualToString: @"multipart"] == YES)
|
2002-11-28 11:48:35 +00:00
|
|
|
|
{
|
|
|
|
|
subtype = @"mixed";
|
|
|
|
|
}
|
2003-06-14 09:40:17 +00:00
|
|
|
|
else if ([type isEqualToString: @"application"] == YES)
|
2002-11-28 11:48:35 +00:00
|
|
|
|
{
|
|
|
|
|
subtype = @"octet-stream";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GSMimeParser *p = AUTORELEASE([GSMimeParser new]);
|
|
|
|
|
NSScanner *scanner = [NSScanner scannerWithString: type];
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [GSMimeHeader headerWithName: @"Content-Type"
|
|
|
|
|
value: nil
|
|
|
|
|
parameters: nil];
|
2002-11-28 11:48:35 +00:00
|
|
|
|
if ([p scanHeaderBody: scanner into: hdr] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Unable to parse type information"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hdr == nil)
|
|
|
|
|
{
|
|
|
|
|
NSString *val;
|
|
|
|
|
|
2005-03-21 19:51:52 +00:00
|
|
|
|
val = [NSStringClass stringWithFormat: @"%@/%@", type, subtype];
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [GSMimeHeader alloc];
|
2016-06-07 18:12:13 +00:00
|
|
|
|
hdr = [hdr initWithName: @"Content-Type" value: val parameters: nil];
|
2002-11-28 11:48:35 +00:00
|
|
|
|
[hdr setObject: type forKey: @"Type"];
|
2002-11-28 13:24:32 +00:00
|
|
|
|
[hdr setObject: subtype forKey: @"Subtype"];
|
2022-02-17 10:08:18 +00:00
|
|
|
|
IF_NO_ARC([hdr autorelease];)
|
2002-11-28 11:48:35 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
type = [hdr objectForKey: @"Type"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (name != nil)
|
|
|
|
|
{
|
|
|
|
|
[hdr setParameter: name forKey: @"name"];
|
|
|
|
|
}
|
2002-05-28 11:30:15 +00:00
|
|
|
|
|
2002-09-26 13:33:54 +00:00
|
|
|
|
if ([type isEqualToString: @"multipart"] == NO
|
2002-11-26 10:15:35 +00:00
|
|
|
|
&& [type isEqualToString: @"application"] == NO
|
2004-08-23 14:29:50 +00:00
|
|
|
|
&& [content isKindOfClass: NSArrayClass] == YES)
|
2002-05-27 05:03:10 +00:00
|
|
|
|
{
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2003-04-02 08:44:46 +00:00
|
|
|
|
format: @"[%@ -%@] content doesn't match content-type",
|
2002-05-28 11:30:15 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[self setContent: newContent];
|
|
|
|
|
[self setHeader: hdr];
|
2011-05-27 11:48:44 +00:00
|
|
|
|
[arp drain];
|
2002-05-27 05:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2003-10-07 15:49:52 +00:00
|
|
|
|
/**
|
|
|
|
|
* <p>Convenience method to set the content type of the document without
|
|
|
|
|
* altering any content.
|
|
|
|
|
* The supplied newType may be full type information including subtype
|
|
|
|
|
* and parameters as found after the colon in a mime Content-Type header.
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
2004-01-22 09:37:07 +00:00
|
|
|
|
- (void) setContentType: (NSString *)newType
|
2003-10-07 15:49:52 +00:00
|
|
|
|
{
|
2011-02-28 19:49:57 +00:00
|
|
|
|
GSMimeHeader *hdr = nil;
|
|
|
|
|
GSMimeParser *p;
|
|
|
|
|
NSScanner *scanner;
|
|
|
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
2003-10-07 15:49:52 +00:00
|
|
|
|
|
2011-02-27 17:53:14 +00:00
|
|
|
|
p = AUTORELEASE([GSMimeParser new]);
|
|
|
|
|
scanner = [NSScanner scannerWithString: newType];
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [GSMimeHeader headerWithName: @"Content-Type"
|
|
|
|
|
value: nil
|
|
|
|
|
parameters: nil];
|
2003-10-07 15:49:52 +00:00
|
|
|
|
if ([p scanHeaderBody: scanner into: hdr] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Unable to parse type information"];
|
|
|
|
|
}
|
|
|
|
|
[self setHeader: hdr];
|
2011-05-27 11:48:44 +00:00
|
|
|
|
[arp drain];
|
2003-10-07 15:49:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* This method may be called to set a header in the document.
|
|
|
|
|
* Any other headers with the same name will be removed from
|
|
|
|
|
* the document.
|
|
|
|
|
*/
|
2002-05-28 11:30:15 +00:00
|
|
|
|
- (void) setHeader: (GSMimeHeader*)info
|
2002-03-06 15:50:14 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
[self deleteHeaderNamed: [info name]];
|
2002-05-28 11:30:15 +00:00
|
|
|
|
[self addHeader: info];
|
2002-03-06 15:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-02-01 12:35:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* Convenience method to create a new header and add it to the receiver
|
|
|
|
|
* replacing any existing header of the same name.<br />
|
|
|
|
|
* Returns the newly created header.<br />
|
|
|
|
|
* See [GSMimeHeader-initWithName:value:parameters:] and -setHeader: methods.
|
|
|
|
|
*/
|
|
|
|
|
- (GSMimeHeader*) setHeader: (NSString*)name
|
|
|
|
|
value: (NSString*)value
|
|
|
|
|
parameters: (NSDictionary*)parameters
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
|
2024-11-27 16:25:08 +00:00
|
|
|
|
hdr = [GSMimeHeader alloc];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
hdr = [hdr initWithName: name
|
|
|
|
|
value: value
|
|
|
|
|
parameters: parameters];
|
2004-02-01 12:35:35 +00:00
|
|
|
|
[self setHeader: hdr];
|
|
|
|
|
RELEASE(hdr);
|
|
|
|
|
return hdr;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-16 08:44:15 +00:00
|
|
|
|
- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude
|
2015-07-15 14:14:21 +00:00
|
|
|
|
{
|
2015-07-16 08:44:15 +00:00
|
|
|
|
NSUInteger size = [super sizeInBytesExcluding: exclude];
|
2015-07-15 14:14:21 +00:00
|
|
|
|
|
|
|
|
|
if (size > 0)
|
|
|
|
|
{
|
2015-07-16 08:44:15 +00:00
|
|
|
|
size += [headers sizeInBytesExcluding: exclude];
|
|
|
|
|
size += [content sizeInBytesExcluding: exclude];
|
2015-07-15 14:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
2002-03-06 15:50:14 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2002-07-03 10:00:39 +00:00
|
|
|
|
@implementation GSMimeDocument (Private)
|
|
|
|
|
/**
|
|
|
|
|
* Returns the index of the first header matching the specified name
|
|
|
|
|
* or NSNotFound if no match is found.<br />
|
|
|
|
|
* NB. The supplied name <em>must</em> be lowercase.<br />
|
|
|
|
|
* This method is for internal use
|
|
|
|
|
*/
|
2009-02-23 20:42:32 +00:00
|
|
|
|
- (NSUInteger) _indexOfHeaderNamed: (NSString*)name
|
2002-07-03 10:00:39 +00:00
|
|
|
|
{
|
2010-02-25 08:19:52 +00:00
|
|
|
|
NSUInteger count = [headers count];
|
2002-07-03 10:00:39 +00:00
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
if (count > 0)
|
2002-07-03 10:00:39 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
NSUInteger index;
|
2020-04-15 09:31:55 +00:00
|
|
|
|
oaiIMP imp1;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
boolIMP imp2;
|
2002-07-03 10:00:39 +00:00
|
|
|
|
|
2020-04-15 09:31:55 +00:00
|
|
|
|
imp1 = (oaiIMP)[headers methodForSelector: @selector(objectAtIndex:)];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
imp2 = (boolIMP)[name methodForSelector: @selector(isEqualToString:)];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
for (index = 0; index < count; index++)
|
2002-07-03 10:00:39 +00:00
|
|
|
|
{
|
2010-09-28 13:23:53 +00:00
|
|
|
|
GSMimeHeader *info;
|
|
|
|
|
|
|
|
|
|
info = (*imp1)(headers, @selector(objectAtIndex:), index);
|
|
|
|
|
if ((*imp2)(name, @selector(isEqualToString:), [info name]))
|
|
|
|
|
{
|
|
|
|
|
return index;
|
|
|
|
|
}
|
2002-07-03 10:00:39 +00:00
|
|
|
|
}
|
2005-02-22 11:22:44 +00:00
|
|
|
|
}
|
2002-07-03 10:00:39 +00:00
|
|
|
|
return NSNotFound;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-28 13:23:53 +00:00
|
|
|
|
- (GSMimeHeader*) _lastHeaderNamed: (NSString*)name
|
|
|
|
|
{
|
|
|
|
|
NSUInteger count = [headers count];
|
|
|
|
|
|
|
|
|
|
if (count > 0)
|
|
|
|
|
{
|
2020-04-15 09:31:55 +00:00
|
|
|
|
oaiIMP imp1;
|
2016-01-21 12:49:15 +00:00
|
|
|
|
boolIMP imp2;
|
2010-09-28 13:23:53 +00:00
|
|
|
|
|
2020-04-15 09:31:55 +00:00
|
|
|
|
imp1 = (oaiIMP)[headers methodForSelector: @selector(objectAtIndex:)];
|
2016-01-21 12:49:15 +00:00
|
|
|
|
imp2 = (boolIMP)[name methodForSelector: @selector(isEqualToString:)];
|
2010-09-28 13:23:53 +00:00
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *info;
|
|
|
|
|
|
|
|
|
|
info = (*imp1)(headers, @selector(objectAtIndex:), count);
|
|
|
|
|
if ((*imp2)(name, @selector(isEqualToString:), [info name]))
|
|
|
|
|
{
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2002-07-03 10:00:39 +00:00
|
|
|
|
@end
|
2002-05-27 16:59:43 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
@implementation GSMimeSerializer
|
2010-03-18 09:56:51 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
+ (GSMimeSerializer*) binarySerializer
|
|
|
|
|
{
|
|
|
|
|
GSMimeSerializer *binarySerializer;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
binarySerializer = AUTORELEASE([GSMimeSerializer alloc]);
|
|
|
|
|
binarySerializer->foldAt = 0;
|
|
|
|
|
binarySerializer->use8bit = YES;
|
|
|
|
|
binarySerializer->dataEncoding = CteBinary;
|
|
|
|
|
binarySerializer->dataEncoding = CteBinary;
|
|
|
|
|
return binarySerializer;
|
|
|
|
|
}
|
2010-10-28 22:50:38 +00:00
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
+ (GSMimeSerializer*) smtp7bitSerializer
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
return AUTORELEASE([GSMimeSerializer new]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (instancetype) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
GSMimeSerializer *c = [[self class] new];
|
|
|
|
|
|
|
|
|
|
c->foldAt = foldAt;
|
|
|
|
|
c->use8bit = use8bit;
|
|
|
|
|
ASSIGNCOPY(c->dataEncoding, dataEncoding);
|
|
|
|
|
ASSIGNCOPY(c->textEncoding, textEncoding);
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) dataEncoding
|
|
|
|
|
{
|
|
|
|
|
return dataEncoding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
RELEASE(dataEncoding);
|
|
|
|
|
RELEASE(textEncoding);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableData*) encodeDocument: (GSMimeDocument*)document
|
|
|
|
|
{
|
|
|
|
|
NSUInteger size = [document estimatedSize];
|
|
|
|
|
NSMutableData *md = [NSMutableData dataWithCapacity: size];
|
|
|
|
|
|
2018-02-11 12:03:52 +00:00
|
|
|
|
if (nil == [document headerNamed: @"mime-version"])
|
2016-08-03 09:24:53 +00:00
|
|
|
|
{
|
2018-02-11 12:03:52 +00:00
|
|
|
|
[document setHeader: @"MIME-Version" value: @"1.0" parameters: nil];
|
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
[self encodePart: document to: md];
|
|
|
|
|
return md;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) encodePart: (GSMimeDocument*)document to: (NSMutableData*)md
|
|
|
|
|
{
|
2020-02-08 16:42:17 +00:00
|
|
|
|
ENTER_POOL
|
2016-08-03 09:24:53 +00:00
|
|
|
|
NSData *d = nil;
|
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
|
NSString *subtype;
|
|
|
|
|
NSString *charset;
|
|
|
|
|
NSString *enc;
|
|
|
|
|
GSMimeHeader *ct;
|
|
|
|
|
GSMimeHeader *cte;
|
|
|
|
|
GSMimeHeader *hdr;
|
|
|
|
|
NSData *boundary = 0;
|
|
|
|
|
BOOL contentIsArray = NO;
|
|
|
|
|
id content = [document content];
|
|
|
|
|
|
|
|
|
|
/* Do we have multipart data?
|
|
|
|
|
*/
|
|
|
|
|
contentIsArray = [content isKindOfClass: NSArrayClass];
|
|
|
|
|
|
2017-06-30 06:37:05 +00:00
|
|
|
|
ct = [document headerNamed: CteContentType];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil == ct)
|
|
|
|
|
{
|
2018-02-11 12:03:52 +00:00
|
|
|
|
NSString *type;
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/*
|
|
|
|
|
* Attempt to infer the content type from the content.
|
|
|
|
|
*/
|
|
|
|
|
if (YES == contentIsArray)
|
|
|
|
|
{
|
|
|
|
|
ct = [document setHeader: @"Content-Type"
|
|
|
|
|
value: @"multipart/mixed"
|
|
|
|
|
parameters: nil];
|
|
|
|
|
type = @"multipart";
|
|
|
|
|
subtype = @"mixed";
|
|
|
|
|
}
|
|
|
|
|
else if ([content isKindOfClass: NSDataClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
ct = [document setHeader: @"Content-Type"
|
|
|
|
|
value: @"application/octet-stream"
|
|
|
|
|
parameters: nil];
|
|
|
|
|
type = @"application";
|
|
|
|
|
subtype = @"octet-stream";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (nil == content)
|
|
|
|
|
{
|
|
|
|
|
/* An empty body is treated as an empty string part.
|
|
|
|
|
*/
|
|
|
|
|
content = @"";
|
|
|
|
|
[document setContent: content];
|
|
|
|
|
}
|
|
|
|
|
else if ([content isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"[%@ -%@] with bad content",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
ct = [document setHeader: @"Content-Type"
|
|
|
|
|
value: @"text/plain"
|
|
|
|
|
parameters: nil];
|
|
|
|
|
type = @"text";
|
|
|
|
|
subtype = @"plain";
|
|
|
|
|
}
|
|
|
|
|
[ct setObject: type forKey: @"Type"];
|
|
|
|
|
[ct setObject: subtype forKey: @"Subtype"];
|
|
|
|
|
charset = nil;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
subtype = [ct objectForKey: @"Subtype"];
|
|
|
|
|
charset = [ct parameterForKey: @"charset"];
|
|
|
|
|
if (nil == content)
|
|
|
|
|
{
|
|
|
|
|
[document setContent: [NSData data]];
|
|
|
|
|
content = [document content];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the content transfer encoding.
|
|
|
|
|
*/
|
|
|
|
|
cte = [document headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
enc = [cte value];
|
|
|
|
|
if (nil == enc)
|
|
|
|
|
{
|
|
|
|
|
if (YES == use8bit)
|
|
|
|
|
{
|
|
|
|
|
if (0 == foldAt)
|
|
|
|
|
{
|
|
|
|
|
enc = CteBinary;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
enc = Cte8bit;
|
|
|
|
|
}
|
|
|
|
|
cte = [document setHeader: @"Content-Transfer-Encoding"
|
|
|
|
|
value: enc
|
|
|
|
|
parameters: nil];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
enc = Cte7bit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ([Cte7bit caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = Cte7bit;
|
|
|
|
|
}
|
|
|
|
|
else if ([Cte8bit caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = Cte8bit;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBinary caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = CteBinary;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBase64 caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = CteBase64;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteQuotedPrintable caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = CteQuotedPrintable;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteXuuencode caseInsensitiveCompare: enc] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
enc = CteXuuencode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NO == use8bit && (Cte8bit == enc || CteBinary == enc))
|
|
|
|
|
{
|
|
|
|
|
enc = Cte7bit;
|
|
|
|
|
if (nil != cte)
|
|
|
|
|
{
|
|
|
|
|
cte = nil;
|
|
|
|
|
[document deleteHeaderNamed: @"Content-Transfer-Encoding"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check the sanity of the parts of a multipart document.
|
|
|
|
|
*/
|
|
|
|
|
if (YES == contentIsArray)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger count = [content count];
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
|
|
|
|
|
if (enc != Cte7bit && enc != Cte8bit && enc != CteBinary)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"[%@ -%@] bad content transfer encoding '%@' for %@",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
|
|
|
|
|
enc, document];
|
|
|
|
|
}
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *d = [content objectAtIndex: index];
|
|
|
|
|
|
|
|
|
|
/* Parts of a multipart document must be MIME documents
|
|
|
|
|
* in their own right.
|
|
|
|
|
*/
|
|
|
|
|
if (NO == [d isKindOfClass: documentClass])
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"[%@ -%@] with bad body part %lu in %@",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
|
|
|
|
|
(unsigned long)index, document];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The MIME-Version header is not permitted in parts of
|
|
|
|
|
* a multipart document.
|
|
|
|
|
*/
|
|
|
|
|
[d deleteHeaderNamed: @"MIME-Version"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (nil == charset)
|
|
|
|
|
{
|
|
|
|
|
if (nil != subtype
|
|
|
|
|
&& [@"xml" caseInsensitiveCompare: subtype] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
/* For an XML document (subtype is xml) we can try to get the
|
|
|
|
|
* characterset by examining the document header.
|
|
|
|
|
*/
|
|
|
|
|
if (nil == (charset = [documentClass charsetForXml: content]))
|
|
|
|
|
{
|
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (YES == [content isKindOfClass: [NSString class]])
|
|
|
|
|
{
|
|
|
|
|
if (nil == charset)
|
|
|
|
|
{
|
|
|
|
|
/* Any string can be converted to utf-8
|
|
|
|
|
*/
|
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (YES == [content isKindOfClass: [NSString class]])
|
|
|
|
|
{
|
|
|
|
|
NSStringEncoding e;
|
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
|
|
/* Get content as a data object, adjusting charset if necessary.
|
|
|
|
|
*/
|
|
|
|
|
e = [documentClass encodingFromCharset: charset];
|
|
|
|
|
if (0 == e)
|
|
|
|
|
{
|
|
|
|
|
e = NSUTF8StringEncoding;
|
|
|
|
|
charset = @"utf-8";
|
|
|
|
|
}
|
|
|
|
|
d = [content dataUsingEncoding: e];
|
|
|
|
|
if (nil == d)
|
|
|
|
|
{
|
|
|
|
|
charset = selectCharacterSet(content, &d);
|
|
|
|
|
[ct setParameter: charset forKey: @"charset"];
|
|
|
|
|
}
|
|
|
|
|
content = d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (YES == contentIsArray)
|
|
|
|
|
{
|
|
|
|
|
NSString *v;
|
|
|
|
|
|
|
|
|
|
v = [ct parameterForKey: @"boundary"];
|
|
|
|
|
if (nil == v)
|
|
|
|
|
{
|
|
|
|
|
v = [document makeBoundary];
|
|
|
|
|
[ct setParameter: v forKey: @"boundary"];
|
|
|
|
|
}
|
|
|
|
|
boundary = [v dataUsingEncoding: NSASCIIStringEncoding];
|
|
|
|
|
|
|
|
|
|
if ([subtype isEqualToString: @"related"] == YES)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *start;
|
|
|
|
|
|
|
|
|
|
v = [ct parameterForKey: @"start"];
|
|
|
|
|
if (nil == v)
|
|
|
|
|
{
|
|
|
|
|
start = [content objectAtIndex: 0];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
start = [document contentByID: v];
|
|
|
|
|
}
|
2017-06-30 06:37:05 +00:00
|
|
|
|
hdr = [start headerNamed: CteContentType];
|
2016-08-03 09:24:53 +00:00
|
|
|
|
v = [hdr value];
|
|
|
|
|
/*
|
|
|
|
|
* If there is no 'type' parameter, we can fill it in automatically.
|
|
|
|
|
*/
|
|
|
|
|
if ([ct parameterForKey: @"type"] == nil)
|
|
|
|
|
{
|
|
|
|
|
[ct setParameter: v forKey: @"type"];
|
|
|
|
|
}
|
|
|
|
|
if ([v isEqual: [ct parameterForKey: @"type"]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"multipart/related 'type' (%@) does not match "
|
|
|
|
|
@"that of the 'start' part (%@) in %@",
|
|
|
|
|
[ct parameterForKey: @"type"], v, document];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (Cte7bit == enc || Cte8bit == enc)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *bytes = (unsigned char*)[content bytes];
|
|
|
|
|
NSUInteger length = [content length];
|
|
|
|
|
BOOL hadCarriageReturn = NO;
|
|
|
|
|
NSUInteger lineLength = 0;
|
|
|
|
|
NSUInteger i;
|
|
|
|
|
|
|
|
|
|
/* Check to see if the data is actually compatible (unaltered)
|
|
|
|
|
* with the specified content transfer encoding.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
unsigned char c = bytes[i];
|
|
|
|
|
|
|
|
|
|
if (hadCarriageReturn == YES)
|
|
|
|
|
{
|
|
|
|
|
if (c != '\n')
|
|
|
|
|
{
|
|
|
|
|
/* CR not part of CRLF
|
|
|
|
|
*/
|
|
|
|
|
enc = (nil == charset) ? dataEncoding : textEncoding;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
hadCarriageReturn = NO;
|
|
|
|
|
lineLength = 0;
|
|
|
|
|
}
|
|
|
|
|
else if ('\n' == c)
|
|
|
|
|
{
|
|
|
|
|
/* LF not part of CRLF
|
|
|
|
|
*/
|
|
|
|
|
enc = (nil == charset) ? dataEncoding : textEncoding;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ('\r' == c)
|
|
|
|
|
{
|
|
|
|
|
hadCarriageReturn = YES;
|
|
|
|
|
}
|
|
|
|
|
else if (++lineLength > 998)
|
|
|
|
|
{
|
|
|
|
|
/* Line of more than 998 chars cannot be 7bit or 8bit
|
|
|
|
|
*/
|
|
|
|
|
enc = (nil == charset) ? dataEncoding : textEncoding;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if (0 == c)
|
|
|
|
|
{
|
|
|
|
|
/* Can't have nul byte in 7bit or 8bit
|
|
|
|
|
*/
|
|
|
|
|
enc = (nil == charset) ? dataEncoding : textEncoding;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if (c > 127)
|
|
|
|
|
{
|
|
|
|
|
if (YES == use8bit)
|
|
|
|
|
{
|
|
|
|
|
enc = Cte8bit; // Not 7bit data
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* any 8bit value must be encoded
|
|
|
|
|
*/
|
|
|
|
|
enc = (nil == charset) ? dataEncoding : textEncoding;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NO == [enc isEqual: [cte value]])
|
|
|
|
|
{
|
|
|
|
|
/* We need to change content transfer encoding.
|
|
|
|
|
*/
|
|
|
|
|
if (Cte7bit == enc)
|
|
|
|
|
{
|
|
|
|
|
[document deleteHeaderNamed: @"Content-Transfer-Encoding"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-02-11 12:03:52 +00:00
|
|
|
|
[document setHeader: @"Content-Transfer-Encoding"
|
|
|
|
|
value: enc
|
|
|
|
|
parameters: nil];
|
2024-02-02 15:40:44 +00:00
|
|
|
|
}
|
2016-08-03 09:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add all the headers.
|
|
|
|
|
*/
|
|
|
|
|
enumerator = [[document allHeaders] objectEnumerator];
|
|
|
|
|
while ((hdr = [enumerator nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[hdr rawMimeDataPreservingCase: NO foldedAt: foldAt to: md];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (YES == contentIsArray)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger count = [content count];
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *part = [content objectAtIndex: index];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* For a multipart document, insert the boundary before each part.
|
|
|
|
|
*/
|
|
|
|
|
[md appendBytes: "\r\n--" length: 4];
|
|
|
|
|
[md appendData: boundary];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
[self encodePart: part to: md];
|
|
|
|
|
}
|
|
|
|
|
[md appendBytes: "\r\n--" length: 4];
|
|
|
|
|
[md appendData: boundary];
|
|
|
|
|
[md appendBytes: "--\r\n" length: 4];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Separate headers from body.
|
|
|
|
|
*/
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
|
|
|
|
|
if (CteBase64 == enc)
|
|
|
|
|
{
|
|
|
|
|
const char *ptr;
|
|
|
|
|
NSUInteger len;
|
|
|
|
|
NSUInteger pos = 0;
|
|
|
|
|
|
|
|
|
|
d = [documentClass encodeBase64: d];
|
|
|
|
|
ptr = [d bytes];
|
|
|
|
|
len = [d length];
|
|
|
|
|
|
|
|
|
|
while (len - pos > 76)
|
|
|
|
|
{
|
|
|
|
|
[md appendBytes: &ptr[pos] length: 76];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
pos += 76;
|
|
|
|
|
}
|
|
|
|
|
if (pos < len)
|
|
|
|
|
{
|
|
|
|
|
[md appendBytes: &ptr[pos] length: len-pos];
|
|
|
|
|
[md appendBytes: "\r\n" length: 2];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (CteQuotedPrintable == enc)
|
|
|
|
|
{
|
|
|
|
|
encodeQuotedPrintable(md, [d bytes], [d length]);
|
|
|
|
|
}
|
|
|
|
|
else if (CteXuuencode == enc)
|
|
|
|
|
{
|
|
|
|
|
NSString *name = [ct parameterForKey: @"name"];
|
|
|
|
|
|
|
|
|
|
if (nil == name)
|
|
|
|
|
{
|
|
|
|
|
name = @"untitled";
|
|
|
|
|
}
|
|
|
|
|
[d uuencodeInto: md name: name mode: 0644];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[md appendData: d];
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-08 16:42:17 +00:00
|
|
|
|
LEAVE_POOL
|
2016-08-03 09:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger) foldAt
|
|
|
|
|
{
|
|
|
|
|
return foldAt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (instancetype) init
|
|
|
|
|
{
|
|
|
|
|
if (nil != (self = [super init]))
|
|
|
|
|
{
|
|
|
|
|
foldAt = 78;
|
|
|
|
|
use8bit = NO;
|
|
|
|
|
|
2018-02-07 13:31:26 +00:00
|
|
|
|
#if 0 // Which is best?
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/* The default content transfer encoding to make 8bit data into
|
|
|
|
|
* 7bit-safe data is 'base64'
|
|
|
|
|
*/
|
|
|
|
|
dataEncoding = CteBase64;
|
2018-02-07 13:31:26 +00:00
|
|
|
|
#else
|
2016-08-03 09:24:53 +00:00
|
|
|
|
/* The default content transfer encoding to make 8bit text into
|
|
|
|
|
* 7bit-safe data is 'quoted-printable'
|
|
|
|
|
*/
|
|
|
|
|
dataEncoding = CteQuotedPrintable;
|
2018-02-07 13:31:26 +00:00
|
|
|
|
#endif
|
2016-08-03 09:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDataEncoding: (NSString*)encoding
|
|
|
|
|
{
|
|
|
|
|
if ([encoding length] == 0)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteBase64;
|
|
|
|
|
}
|
|
|
|
|
if ([CteQuotedPrintable caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteQuotedPrintable;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBase64 caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteBase64;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteXuuencode caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteXuuencode;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"[%@ -%@: %@] bad encoding",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd), encoding];
|
|
|
|
|
}
|
|
|
|
|
ASSIGN(dataEncoding, encoding);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setFoldAt: (NSUInteger)position
|
|
|
|
|
{
|
|
|
|
|
if (position < 20 || position > 998)
|
|
|
|
|
{
|
|
|
|
|
position = 0;
|
|
|
|
|
}
|
|
|
|
|
foldAt = position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setTextEncoding: (NSString*)encoding
|
|
|
|
|
{
|
|
|
|
|
if ([encoding length] == 0)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteQuotedPrintable;
|
|
|
|
|
}
|
|
|
|
|
if ([CteQuotedPrintable caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteQuotedPrintable;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteBase64 caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteBase64;
|
|
|
|
|
}
|
|
|
|
|
else if ([CteXuuencode caseInsensitiveCompare: encoding] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
encoding = CteXuuencode;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"[%@ -%@: %@] bad encoding",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd), encoding];
|
|
|
|
|
}
|
|
|
|
|
ASSIGN(textEncoding, encoding);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setUse8bit: (BOOL)aFlag
|
|
|
|
|
{
|
|
|
|
|
use8bit = (NO == aFlag) ? NO : YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) textEncoding
|
|
|
|
|
{
|
|
|
|
|
return textEncoding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) use8bit
|
|
|
|
|
{
|
|
|
|
|
return use8bit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
2021-03-26 15:06:49 +00:00
|
|
|
|
GS_DECLARE NSString* const GSMimeErrorDomain = @"GSMimeErrorDomain";
|
2016-08-03 09:24:53 +00:00
|
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
|
TP_IDLE,
|
|
|
|
|
TP_OPEN,
|
|
|
|
|
TP_INTRO,
|
|
|
|
|
TP_EHLO,
|
|
|
|
|
TP_HELO,
|
|
|
|
|
TP_AUTH,
|
|
|
|
|
TP_MESG,
|
|
|
|
|
TP_FROM,
|
|
|
|
|
TP_TO,
|
|
|
|
|
TP_DATA,
|
|
|
|
|
TP_BODY
|
|
|
|
|
} CState;
|
|
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
|
SMTPE_DSN, // delivery status notification extension
|
|
|
|
|
} SMTPE;
|
|
|
|
|
|
|
|
|
|
NSString *
|
|
|
|
|
eventText(NSStreamEvent e)
|
|
|
|
|
{
|
|
|
|
|
if (e == NSStreamEventNone)
|
|
|
|
|
return @"NSStreamEventNone";
|
2010-03-17 15:46:20 +00:00
|
|
|
|
if (e == NSStreamEventOpenCompleted)
|
|
|
|
|
return @"NSStreamEventOpenCompleted";
|
|
|
|
|
if (e == NSStreamEventHasBytesAvailable)
|
|
|
|
|
return @"NSStreamEventHasBytesAvailable";
|
|
|
|
|
if (e == NSStreamEventHasSpaceAvailable)
|
|
|
|
|
return @"NSStreamEventHasSpaceAvailable";
|
|
|
|
|
if (e == NSStreamEventErrorOccurred)
|
|
|
|
|
return @"NSStreamEventErrorOccurred";
|
|
|
|
|
if (e == NSStreamEventEndEncountered)
|
|
|
|
|
return @"NSStreamEventEndEncountered";
|
|
|
|
|
return @"unknown event";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSString *
|
|
|
|
|
statusText(NSStreamStatus s)
|
|
|
|
|
{
|
|
|
|
|
if (s == NSStreamStatusNotOpen) return @"NSStreamStatusNotOpen";
|
|
|
|
|
if (s == NSStreamStatusOpening) return @"NSStreamStatusOpening";
|
|
|
|
|
if (s == NSStreamStatusOpen) return @"NSStreamStatusOpen";
|
|
|
|
|
if (s == NSStreamStatusReading) return @"NSStreamStatusReading";
|
|
|
|
|
if (s == NSStreamStatusWriting) return @"NSStreamStatusWriting";
|
|
|
|
|
if (s == NSStreamStatusAtEnd) return @"NSStreamStatusAtEnd";
|
|
|
|
|
if (s == NSStreamStatusClosed) return @"NSStreamStatusClosed";
|
|
|
|
|
if (s == NSStreamStatusError) return @"NSStreamStatusError";
|
|
|
|
|
return @"unknown status";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Convert 8bit/binary data parts to base64 encoding for old mail
|
|
|
|
|
* software which can't handle 8bit data.
|
|
|
|
|
*/
|
|
|
|
|
static void makeBase64(GSMimeDocument *doc)
|
|
|
|
|
{
|
|
|
|
|
id o = [doc content];
|
|
|
|
|
|
|
|
|
|
if ([o isKindOfClass: [NSArray class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [o objectEnumerator];
|
|
|
|
|
|
|
|
|
|
while ((doc = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
makeBase64(doc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GSMimeHeader *h = [doc headerNamed: @"content-transfer-encoding"];
|
|
|
|
|
NSString *v = [h value];
|
|
|
|
|
|
2016-08-03 09:24:53 +00:00
|
|
|
|
if (nil != v
|
|
|
|
|
&& ([CteBinary caseInsensitiveCompare: v] == NSOrderedSame
|
|
|
|
|
|| [Cte8bit caseInsensitiveCompare: v] == NSOrderedSame))
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2016-08-03 09:24:53 +00:00
|
|
|
|
[h setValue: CteBase64];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
@interface GSMimeSMTPClient (Private)
|
|
|
|
|
- (NSError*) _commsEnd;
|
|
|
|
|
- (NSError*) _commsError;
|
|
|
|
|
- (void) _doMessage;
|
|
|
|
|
- (NSString*) _identity;
|
|
|
|
|
- (void) _performIO;
|
|
|
|
|
- (void) _recvData: (NSData*)m;
|
|
|
|
|
- (NSError*) _response: (NSString*)r;
|
|
|
|
|
- (void) _sendData: (NSData*)m;
|
|
|
|
|
- (void) _shutdown: (NSError*)e;
|
|
|
|
|
- (void) _startup;
|
|
|
|
|
- (void) _timer: (NSTimeInterval)s;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
@end
|
2010-03-18 09:56:51 +00:00
|
|
|
|
#define GSInternal GSMimeSMTPClientInternal
|
|
|
|
|
#include "GSInternal.h"
|
|
|
|
|
GS_PRIVATE_INTERNAL(GSMimeSMTPClient)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
@implementation NSObject (GSMimeSMTPClient)
|
|
|
|
|
- (void) smtpClient: (GSMimeSMTPClient*)client
|
|
|
|
|
mimeFailed: (GSMimeDocument*)doc
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
- (void) smtpClient: (GSMimeSMTPClient*)client
|
|
|
|
|
mimeSent: (GSMimeDocument*)doc
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
- (void) smtpClient: (GSMimeSMTPClient*)client
|
|
|
|
|
mimeUnsent: (GSMimeDocument*)doc
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation GSMimeSMTPClient
|
|
|
|
|
|
2020-11-14 04:25:34 +00:00
|
|
|
|
/* Shuts the connection down, fails any message in progress, and discards all
|
2010-03-17 15:46:20 +00:00
|
|
|
|
* queued messages as 'unsent'
|
|
|
|
|
*/
|
|
|
|
|
- (void) abort
|
|
|
|
|
{
|
|
|
|
|
NSUInteger c;
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSError *e;
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
|
|
|
|
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
|
[NSString stringWithFormat: @"Abort while %@", [self stateDesc]],
|
|
|
|
|
NSLocalizedDescriptionKey,
|
|
|
|
|
nil];
|
|
|
|
|
e = [NSError errorWithDomain: GSMimeErrorDomain
|
|
|
|
|
code: GSMimeSMTPAbort
|
|
|
|
|
userInfo: d];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: e];
|
|
|
|
|
[internal->timer invalidate];
|
|
|
|
|
internal->timer = nil;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
|
|
|
|
/* For any message not yet sent, we inform the delegate of the failure
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
c = [internal->queue count];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
while (c-- > 0)
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
GSMimeDocument *d = [internal->queue objectAtIndex: c];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2011-04-07 10:58:07 +00:00
|
|
|
|
if (nil == internal->delegate)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"-smtpClient:mimeUnsent: %@ %@", self, d);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[internal->delegate smtpClient: self mimeUnsent: d];
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->queue removeAllObjects];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
[self abort];
|
2024-11-17 16:41:59 +00:00
|
|
|
|
if (GS_EXISTS_INTERNAL)
|
2010-03-18 09:56:51 +00:00
|
|
|
|
{
|
|
|
|
|
DESTROY(internal->reply);
|
|
|
|
|
DESTROY(internal->wdata);
|
|
|
|
|
DESTROY(internal->rdata);
|
|
|
|
|
DESTROY(internal->pending);
|
|
|
|
|
DESTROY(internal->queue);
|
|
|
|
|
DESTROY(internal->username);
|
|
|
|
|
DESTROY(internal->port);
|
|
|
|
|
DESTROY(internal->hostname);
|
|
|
|
|
DESTROY(internal->identity);
|
|
|
|
|
DESTROY(internal->originator);
|
|
|
|
|
DESTROY(internal->lastError);
|
|
|
|
|
GS_DESTROY_INTERNAL(GSMimeSMTPClient);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (id) delegate
|
|
|
|
|
{
|
|
|
|
|
return internal->delegate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) flush: (NSDate*)limit
|
|
|
|
|
{
|
|
|
|
|
if (limit == nil)
|
|
|
|
|
{
|
|
|
|
|
limit = [NSDate distantFuture];
|
|
|
|
|
}
|
|
|
|
|
while ([internal->queue count] > 0)
|
|
|
|
|
{
|
2010-10-27 11:24:44 +00:00
|
|
|
|
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
|
|
|
|
beforeDate: limit];
|
2010-03-18 09:56:51 +00:00
|
|
|
|
}
|
|
|
|
|
return [internal->queue count] == 0 ? YES : NO;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-17 15:46:20 +00:00
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
if ((self = [super init]) != 0)
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
GS_CREATE_INTERNAL(GSMimeSMTPClient);
|
|
|
|
|
internal->queue = [NSMutableArray new];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (NSError*) lastError
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
return internal->lastError;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 04:25:34 +00:00
|
|
|
|
- (NSUInteger) queueSize
|
|
|
|
|
{
|
|
|
|
|
return [internal->queue count];
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) send: (GSMimeDocument*)message
|
|
|
|
|
{
|
2010-10-28 22:50:38 +00:00
|
|
|
|
[self send: message envelopeID: nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) send: (GSMimeDocument*)message envelopeID: (NSString*)envid
|
|
|
|
|
{
|
|
|
|
|
if (nil == [message headerNamed: @"mime-version"])
|
|
|
|
|
{
|
|
|
|
|
[message setHeader: @"MIME-Version" value: @"1.0" parameters: nil];
|
|
|
|
|
}
|
|
|
|
|
if (nil != envid)
|
|
|
|
|
{
|
|
|
|
|
[[message headerNamed: @"mime-version"] setObject: envid
|
|
|
|
|
forKey: @"ENVID"];
|
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->queue addObject: message];
|
|
|
|
|
if (internal->cState == TP_IDLE)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->timer != nil)
|
|
|
|
|
{
|
|
|
|
|
[internal->timer invalidate];
|
|
|
|
|
internal->timer = nil;
|
|
|
|
|
}
|
|
|
|
|
[self _startup];
|
|
|
|
|
}
|
|
|
|
|
else if (internal->cState == TP_MESG)
|
|
|
|
|
{
|
|
|
|
|
[self _doMessage];
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setDelegate: (id)d
|
|
|
|
|
{
|
|
|
|
|
internal->delegate = d;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setHostname: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(internal->hostname, s);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setIdentity: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(internal->identity, s);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2020-11-14 04:25:34 +00:00
|
|
|
|
- (NSUInteger) setMaximum: (NSUInteger)m
|
|
|
|
|
{
|
|
|
|
|
NSUInteger old = internal->maximum;
|
|
|
|
|
|
|
|
|
|
internal->maximum = m;
|
|
|
|
|
return old;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setOriginator: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(internal->originator, s);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setPort: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(internal->port, s);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) setUsername: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(internal->username, s);
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (int) state
|
|
|
|
|
{
|
|
|
|
|
return internal->cState;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (NSString*) stateDesc
|
|
|
|
|
{
|
|
|
|
|
switch (internal->cState)
|
|
|
|
|
{
|
|
|
|
|
case TP_OPEN: return @"waiting for connection to SMTP server";
|
|
|
|
|
case TP_INTRO: return @"waiting for initial prompt from SMTP server";
|
2010-10-28 22:50:38 +00:00
|
|
|
|
case TP_EHLO: return @"waiting for SMTP server EHLO completion";
|
2010-03-18 09:56:51 +00:00
|
|
|
|
case TP_HELO: return @"waiting for SMTP server HELO completion";
|
|
|
|
|
case TP_AUTH: return @"waiting for SMTP server AUTH response";
|
|
|
|
|
case TP_FROM: return @"waiting for ack of FROM command";
|
|
|
|
|
case TP_TO: return @"waiting for ack of TO command";
|
|
|
|
|
case TP_DATA: return @"waiting for ack of DATA command";
|
|
|
|
|
case TP_BODY: return @"waiting for ack of message body";
|
|
|
|
|
case TP_MESG: return @"waiting for message to send";
|
|
|
|
|
case TP_IDLE: return @"idle ... not connected to SMTP server";
|
|
|
|
|
}
|
|
|
|
|
return @"idle ... not connected to SMTP server";
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2011-10-08 17:53:17 +00:00
|
|
|
|
/** Handler for stream events ...
|
2010-03-18 09:56:51 +00:00
|
|
|
|
*/
|
|
|
|
|
- (void) stream: (NSStream*)aStream handleEvent: (NSStreamEvent)anEvent
|
|
|
|
|
{
|
|
|
|
|
NSStreamStatus sStatus = [aStream streamStatus];
|
|
|
|
|
|
|
|
|
|
if (aStream == internal->istream)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ istream event %@ in %@",
|
|
|
|
|
self, eventText(anEvent), statusText(sStatus));
|
|
|
|
|
if (anEvent == NSStreamEventHasBytesAvailable)
|
|
|
|
|
{
|
|
|
|
|
internal->readable = YES;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ ostream event %@ in %@",
|
|
|
|
|
self, eventText(anEvent), statusText(sStatus));
|
|
|
|
|
if (anEvent == NSStreamEventHasSpaceAvailable)
|
|
|
|
|
{
|
|
|
|
|
internal->writable = YES;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
|
|
|
|
|
if (anEvent == NSStreamEventEndEncountered)
|
|
|
|
|
{
|
|
|
|
|
[self _shutdown: [self _commsEnd]];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (anEvent == NSStreamEventErrorOccurred)
|
|
|
|
|
{
|
|
|
|
|
[self _shutdown: [self _commsError]];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (anEvent == NSStreamEventOpenCompleted)
|
|
|
|
|
{
|
|
|
|
|
internal->cState = TP_INTRO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self _performIO];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GSMimeSMTPClient (Private)
|
|
|
|
|
|
|
|
|
|
- (NSError*) _commsEnd
|
|
|
|
|
{
|
|
|
|
|
NSError *e;
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
|
|
|
|
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
|
[NSString stringWithFormat: @"End of input while %@", [self stateDesc]],
|
|
|
|
|
NSLocalizedDescriptionKey,
|
|
|
|
|
nil];
|
|
|
|
|
e = [NSError errorWithDomain: GSMimeErrorDomain
|
|
|
|
|
code: GSMimeSMTPCommsEnd
|
|
|
|
|
userInfo: d];
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSError*) _commsError
|
|
|
|
|
{
|
|
|
|
|
NSError *e;
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
|
|
|
|
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
|
[NSString stringWithFormat: @"Error on I/O while %@", [self stateDesc]],
|
|
|
|
|
NSLocalizedDescriptionKey,
|
|
|
|
|
nil];
|
|
|
|
|
e = [NSError errorWithDomain: GSMimeErrorDomain
|
|
|
|
|
code: GSMimeSMTPCommsError
|
|
|
|
|
userInfo: d];
|
|
|
|
|
return e;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Initiates sending of the next message (or the next stage of the
|
|
|
|
|
* current message).
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _doMessage
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->queue count] > 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->current = [internal->queue objectAtIndex: 0];
|
2010-10-28 22:50:38 +00:00
|
|
|
|
internal->version = [internal->current headerNamed: @"mime-version"];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->cState == TP_IDLE)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _startup];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else if (internal->cState == TP_MESG)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSString *from = internal->originator;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
DESTROY(internal->lastError);
|
|
|
|
|
if (from == nil)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
from = [[NSUserDefaults standardUserDefaults]
|
|
|
|
|
stringForKey: @"GSMimeSMTPClientOriginator"];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([from length] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
from = [[internal->current headerNamed: @"from"] value];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([from length] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
/* If we have no sender address ... use postmaster.
|
|
|
|
|
*/
|
2010-10-27 11:24:44 +00:00
|
|
|
|
from = [NSString stringWithFormat: @"postmaster@%@",
|
|
|
|
|
[self _identity]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-10-28 22:50:38 +00:00
|
|
|
|
tmp = [internal->version objectForKey: @"ENVID"];
|
|
|
|
|
if (nil == tmp)
|
|
|
|
|
{
|
|
|
|
|
tmp = [NSString stringWithFormat: @"MAIL FROM: <%@>\r\n", from];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Tell the mail server we want headers, not the full body
|
|
|
|
|
* when an email is bounced or acknowledged.
|
|
|
|
|
* Set the envelope ID to be the ID of the current message.
|
|
|
|
|
*/
|
|
|
|
|
tmp = [NSString stringWithFormat:
|
|
|
|
|
@"MAIL FROM: <%@> RET=HDRS ENVID=%@\r\n", from, tmp];
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Initiating new mail message - %@", tmp);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_FROM;
|
|
|
|
|
[self _timer: 20.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else if (internal->cState == TP_FROM)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
tmp = [[internal->current headerNamed: @"to"] value];
|
2010-10-28 22:50:38 +00:00
|
|
|
|
if (nil == [internal->version objectForKey: @"ENVID"])
|
|
|
|
|
{
|
|
|
|
|
tmp = [NSString stringWithFormat: @"RCPT TO: <%@>\r\n", tmp];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* We have an envelope ID, so we need success/failure reports.
|
|
|
|
|
*/
|
|
|
|
|
tmp = [NSString stringWithFormat:
|
|
|
|
|
@"RCPT TO: <%@> NOTIFY=SUCCESS,FAILURE\r\n", tmp];
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Destination - %@", tmp);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_TO;
|
|
|
|
|
[self _timer: 20.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else if (internal->cState == TP_TO)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_DATA;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
tmp = @"DATA\r\n";
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _timer: 20.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else if (internal->cState == TP_DATA)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableData *md;
|
|
|
|
|
NSData *data;
|
|
|
|
|
const char *ibuf;
|
|
|
|
|
char *obuf;
|
|
|
|
|
BOOL sol = YES;
|
|
|
|
|
unsigned ilen;
|
|
|
|
|
unsigned olen;
|
|
|
|
|
unsigned osiz;
|
|
|
|
|
unsigned ipos = 0;
|
|
|
|
|
unsigned opos = 0;
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_BODY;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
makeBase64(internal->current);
|
|
|
|
|
data = [internal->current rawMimeData];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Any line in the message which begins with a dot must have
|
|
|
|
|
* that dot escaped by another dot.
|
|
|
|
|
*/
|
|
|
|
|
ilen = [data length];
|
|
|
|
|
olen = ilen + 5; // Allow for CR-LF-.-CR-LF termination
|
|
|
|
|
osiz = olen + 10; // Allow some expansion to escape dots
|
|
|
|
|
|
|
|
|
|
md = [[NSMutableData alloc] initWithLength: osiz];
|
|
|
|
|
ibuf = [data bytes];
|
|
|
|
|
obuf = [md mutableBytes];
|
|
|
|
|
|
|
|
|
|
while (ipos < ilen)
|
|
|
|
|
{
|
|
|
|
|
char c = ibuf[ipos++];
|
|
|
|
|
|
|
|
|
|
if (c == '\n')
|
|
|
|
|
{
|
|
|
|
|
sol = YES;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (c == '.' && sol == YES)
|
|
|
|
|
{
|
|
|
|
|
obuf[opos++] = '.'; // Extra dot acts as an escape
|
|
|
|
|
if (olen++ == osiz) // Lengthen to allow for dot
|
|
|
|
|
{
|
|
|
|
|
osiz += 16;
|
|
|
|
|
[md setLength: osiz];
|
|
|
|
|
obuf = [md mutableBytes];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sol = NO;
|
|
|
|
|
}
|
|
|
|
|
obuf[opos++] = c;
|
|
|
|
|
}
|
|
|
|
|
obuf[opos++] = '\r';
|
|
|
|
|
obuf[opos++] = '\n';
|
|
|
|
|
/*
|
|
|
|
|
* Now terminate the message with a line consisting of a dot.
|
|
|
|
|
*/
|
|
|
|
|
obuf[opos++] = '.';
|
|
|
|
|
obuf[opos++] = '\r';
|
|
|
|
|
obuf[opos++] = '\n';
|
|
|
|
|
[md setLength: opos];
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _timer: 60.0];
|
|
|
|
|
[self _sendData: md];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
RELEASE(md);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSLog(@"_doMessage called in unexpected state.");
|
|
|
|
|
[self _shutdown: nil];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) _identity
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp = internal->identity;
|
|
|
|
|
|
|
|
|
|
if (tmp == nil)
|
|
|
|
|
{
|
|
|
|
|
tmp = [[NSUserDefaults standardUserDefaults]
|
|
|
|
|
stringForKey: @"GSMimeSMTPClientIdentity"];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([tmp length] == 0)
|
|
|
|
|
{
|
|
|
|
|
tmp = [[NSHost currentHost] name];
|
|
|
|
|
}
|
|
|
|
|
return tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Does low level writing and reading of data.
|
|
|
|
|
*/
|
|
|
|
|
- (void) _performIO
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2011-02-28 19:49:57 +00:00
|
|
|
|
[self retain]; // Make sure we don't get released until done.
|
2010-03-18 09:56:51 +00:00
|
|
|
|
|
|
|
|
|
/* First perform all reads ... so we process incoming data,
|
|
|
|
|
*/
|
|
|
|
|
while (internal->readable == YES && internal->cState != TP_OPEN)
|
|
|
|
|
{
|
|
|
|
|
uint8_t buf[BUFSIZ];
|
|
|
|
|
int length;
|
|
|
|
|
|
|
|
|
|
/* Try to fill the buffer, then process any data we have.
|
|
|
|
|
*/
|
|
|
|
|
length = [internal->istream read: buf maxLength: sizeof(buf)];
|
|
|
|
|
if (length > 0)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *ptr;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (internal->rdata == nil)
|
|
|
|
|
{
|
|
|
|
|
internal->rdata = [[NSMutableData alloc] initWithBytes: buf
|
2010-10-27 11:24:44 +00:00
|
|
|
|
length: length];
|
2010-03-18 09:56:51 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[internal->rdata appendBytes: buf length: length];
|
|
|
|
|
length = [internal->rdata length];
|
|
|
|
|
}
|
|
|
|
|
ptr = [internal->rdata mutableBytes];
|
|
|
|
|
for (i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (ptr[i] == '\n')
|
|
|
|
|
{
|
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
if (i == length)
|
|
|
|
|
{
|
|
|
|
|
d = [internal->rdata autorelease];
|
|
|
|
|
internal->rdata = nil;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
d = [NSData dataWithBytes: ptr length: i];
|
|
|
|
|
memcpy(ptr, ptr + i, length - i);
|
|
|
|
|
length -= i;
|
|
|
|
|
[internal->rdata setLength: length];
|
|
|
|
|
ptr = [internal->rdata mutableBytes];
|
|
|
|
|
i = -1;
|
|
|
|
|
}
|
|
|
|
|
[self _recvData: d];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
internal->readable = NO; // Can't read more right now.
|
|
|
|
|
if (length == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"EOF on input stream ... terminating");
|
|
|
|
|
[self _shutdown: [self _commsEnd]];
|
|
|
|
|
}
|
|
|
|
|
else if ([internal->istream streamStatus] == NSStreamStatusError)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Error on input stream ... terminating");
|
|
|
|
|
[self _shutdown: [self _commsError]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Perform write operations after read operations, so that we are able
|
|
|
|
|
* to write any packets resulting from the incoming data as a single
|
|
|
|
|
* block of outgoing data if possible.
|
|
|
|
|
*/
|
|
|
|
|
while (internal->writable == YES && [internal->pending count] > 0)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *wbytes = [internal->wdata mutableBytes];
|
|
|
|
|
unsigned wlength = [internal->wdata length];
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
|
|
result = [internal->ostream write: wbytes + internal->woffset
|
|
|
|
|
maxLength: wlength - internal->woffset];
|
|
|
|
|
if (result > 0)
|
|
|
|
|
{
|
|
|
|
|
NSData *d = [internal->pending objectAtIndex: 0];
|
|
|
|
|
unsigned dlength = [d length];
|
|
|
|
|
|
|
|
|
|
internal->woffset += result;
|
|
|
|
|
if (internal->woffset >= dlength)
|
|
|
|
|
{
|
|
|
|
|
unsigned total = 0;
|
|
|
|
|
|
|
|
|
|
while (internal->woffset >= total + dlength)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ Write: %@", self, d);
|
|
|
|
|
[internal->pending removeObjectAtIndex: 0];
|
|
|
|
|
total += dlength;
|
|
|
|
|
if ([internal->pending count] > 0)
|
|
|
|
|
{
|
|
|
|
|
d = [internal->pending objectAtIndex: 0];
|
|
|
|
|
dlength = [d length];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (total < wlength)
|
|
|
|
|
{
|
|
|
|
|
memcpy(wbytes, wbytes + total, wlength - total);
|
|
|
|
|
}
|
|
|
|
|
[internal->wdata setLength: wlength - total];
|
|
|
|
|
internal->woffset -= total;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
internal->writable = NO; // Can't write more right now.
|
|
|
|
|
if (result == 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"EOF on output stream ... terminating");
|
|
|
|
|
[self _shutdown: [self _commsEnd]];
|
|
|
|
|
}
|
|
|
|
|
else if ([internal->ostream streamStatus] == NSStreamStatusError)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Error on output stream ... terminating");
|
|
|
|
|
[self _shutdown: [self _commsError]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-28 19:49:57 +00:00
|
|
|
|
[self release];
|
2010-03-18 09:56:51 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Exception handling stream event: %@", localException);
|
|
|
|
|
RELEASE(self);
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Receives a chunk of data from the input stream and performs state
|
|
|
|
|
* transitions based on the current state and the information received
|
|
|
|
|
* from the SMTP server.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _recvData: (NSData*)m
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
unsigned int c = 0;
|
|
|
|
|
NSMutableString *s = nil;
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->queue count] > 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->current = [internal->queue objectAtIndex: 0];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ _recvData: %@", self, m);
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
|
|
|
|
if (m != nil)
|
|
|
|
|
{
|
|
|
|
|
unichar sep;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Get this reply line and check it is of the correct format.
|
|
|
|
|
*/
|
|
|
|
|
s = [[NSMutableString alloc] initWithData: m
|
|
|
|
|
encoding: NSASCIIStringEncoding];
|
|
|
|
|
[s trimSpaces];
|
|
|
|
|
if ([s length] <= 4)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server made short response ... %@", s);
|
|
|
|
|
RELEASE(s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: @"short data"]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sep = [s characterAtIndex: 3];
|
|
|
|
|
if (sep != ' ' && sep != '-')
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server made illegal response ... %@", s);
|
2013-04-14 06:25:22 +00:00
|
|
|
|
RELEASE(s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: @"bad format"]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Accumulate multiline replies in the 'reply' ivar.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->reply length] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
ASSIGN(internal->reply, s);
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[s replaceCharactersInRange: NSMakeRange(0, 4) withString: @" "];
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->reply appendString: s];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
RELEASE(s);
|
|
|
|
|
if (sep == '-')
|
|
|
|
|
{
|
|
|
|
|
return; // Continuation line ... wait for more.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Got end of reply ... move from ivar to local variable ready for
|
|
|
|
|
* accumulating the next reply.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
c = [internal->reply intValue];
|
|
|
|
|
s = AUTORELEASE(internal->reply);
|
|
|
|
|
internal->reply = nil;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
switch (internal->cState)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
case TP_INTRO:
|
|
|
|
|
if (c == 220)
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
tmp = [NSString stringWithFormat: @"HELO %@\r\n", [self _identity]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Intro OK - sending helo");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_HELO;
|
|
|
|
|
[self _timer: 30.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server went away ... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2010-10-28 22:50:38 +00:00
|
|
|
|
case TP_EHLO:
|
|
|
|
|
if (c == 220)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"System acknowledged EHLO");
|
|
|
|
|
if ([internal->username length] == 0)
|
|
|
|
|
{
|
|
|
|
|
internal->cState = TP_MESG;
|
|
|
|
|
[self _doMessage];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
|
|
|
|
|
|
|
|
|
tmp = [NSString stringWithFormat: @"AUTH PLAIN %@\r\n",
|
|
|
|
|
[GSMimeDocument encodeBase64String: internal->username]];
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"Ehlo OK - sending auth");
|
|
|
|
|
internal->cState = TP_AUTH;
|
|
|
|
|
[self _timer: 30.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
|
|
|
|
|
|
|
|
|
tmp = [NSString stringWithFormat: @"HELO %@\r\n", [self _identity]];
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"Ehlo failed - sending helo");
|
|
|
|
|
internal->cState = TP_HELO;
|
|
|
|
|
[self _timer: 30.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2010-03-17 15:46:20 +00:00
|
|
|
|
case TP_HELO:
|
|
|
|
|
if (c == 250)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"System acknowledged HELO");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->username length] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_MESG;
|
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSString *tmp;
|
|
|
|
|
|
|
|
|
|
tmp = [NSString stringWithFormat: @"AUTH PLAIN %@\r\n",
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[GSMimeDocument encodeBase64String: internal->username]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"Helo OK - sending auth");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_AUTH;
|
|
|
|
|
[self _timer: 30.0];
|
|
|
|
|
[self _sendData: [tmp dataUsingEncoding: NSUTF8StringEncoding]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked helo ... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_AUTH:
|
|
|
|
|
if (c == 250)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"System acknowledged AUTH");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_MESG;
|
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked auth ... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_FROM:
|
|
|
|
|
if (c != 250)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked FROM... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"System acknowledged FROM");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_TO:
|
|
|
|
|
if (c != 250)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked TO... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"System acknowledged TO");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_DATA:
|
|
|
|
|
if (c != 354)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked DATA... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_BODY:
|
|
|
|
|
if (c != 250)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Server nacked body ... %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_MESG;
|
|
|
|
|
if (internal->current != nil)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
GSMimeDocument *d = [internal->current retain];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->current = nil;
|
|
|
|
|
[internal->queue removeObjectAtIndex: 0];
|
2011-04-07 10:58:07 +00:00
|
|
|
|
if (nil == internal->delegate)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"-smtpClient:mimeSent: %@ %@",
|
|
|
|
|
self, d);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[internal->delegate smtpClient: self mimeSent: d];
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
[d release];
|
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _doMessage];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TP_MESG:
|
|
|
|
|
NSLog(@"Unknown response from SMTP system. - %@", s);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
NSLog(@"system in unexpected state.");
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: [self _response: s]];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (NSError*) _response: (NSString*)r
|
|
|
|
|
{
|
|
|
|
|
NSError *e;
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
NSString *s;
|
|
|
|
|
|
2010-10-27 11:24:44 +00:00
|
|
|
|
s = [NSString stringWithFormat:
|
|
|
|
|
@"Unexpected response form server while %@: %@",
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self stateDesc], r];
|
|
|
|
|
|
|
|
|
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
|
s, NSLocalizedDescriptionKey,
|
|
|
|
|
nil];
|
|
|
|
|
e = [NSError errorWithDomain: GSMimeErrorDomain
|
|
|
|
|
code: GSMimeSMTPServerResponse
|
|
|
|
|
userInfo: d];
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-17 15:46:20 +00:00
|
|
|
|
/** Add a chunk of data to the output stream.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _sendData: (NSData*)m
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ _sendData: %@", self, m);
|
|
|
|
|
if (internal->pending == nil)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->pending = [NSMutableArray new];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->pending addObject: m];
|
|
|
|
|
if (internal->wdata == nil)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->wdata = [m mutableCopy];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->wdata appendData: m];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->pending count] > 0 && internal->writable == YES)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _performIO];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Shuts down the connection to the SMTP server and fails any message
|
|
|
|
|
* currently in progress. If there are queued messages, this sets a
|
|
|
|
|
* timer to reconnect.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _shutdown: (NSError*)e
|
|
|
|
|
{
|
|
|
|
|
[internal->istream removeFromRunLoop: [NSRunLoop currentRunLoop]
|
|
|
|
|
forMode: NSDefaultRunLoopMode];
|
|
|
|
|
[internal->ostream removeFromRunLoop: [NSRunLoop currentRunLoop]
|
|
|
|
|
forMode: NSDefaultRunLoopMode];
|
|
|
|
|
[internal->istream setDelegate: nil];
|
|
|
|
|
[internal->ostream setDelegate: nil];
|
|
|
|
|
[internal->istream close];
|
|
|
|
|
[internal->ostream close];
|
|
|
|
|
|
|
|
|
|
DESTROY(internal->istream);
|
|
|
|
|
DESTROY(internal->ostream);
|
|
|
|
|
|
|
|
|
|
[internal->wdata setLength: 0];
|
|
|
|
|
internal->woffset = 0;
|
|
|
|
|
internal->readable = NO;
|
|
|
|
|
internal->writable = NO;
|
|
|
|
|
internal->cState = TP_IDLE;
|
|
|
|
|
|
|
|
|
|
[internal->pending removeAllObjects];
|
|
|
|
|
ASSIGN(internal->lastError, e);
|
2020-11-14 04:25:34 +00:00
|
|
|
|
if (nil == internal->current)
|
|
|
|
|
{
|
|
|
|
|
while ([self queueSize] > internal->maximum)
|
|
|
|
|
{
|
|
|
|
|
GSMimeDocument *d = RETAIN([internal->queue objectAtIndex: 0]);
|
|
|
|
|
|
|
|
|
|
[internal->queue removeObjectAtIndex: 0];
|
|
|
|
|
if (nil == internal->delegate)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"-smtpClient:mimeUnsent: %@ %@",
|
|
|
|
|
self, d);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[internal->delegate smtpClient: self mimeUnsent: d];
|
|
|
|
|
}
|
|
|
|
|
RELEASE(d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2010-03-18 09:56:51 +00:00
|
|
|
|
{
|
2020-11-14 04:25:34 +00:00
|
|
|
|
GSMimeDocument *d = RETAIN(internal->current);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
|
|
|
|
|
[internal->queue removeObjectAtIndex: 0];
|
|
|
|
|
internal->current = nil;
|
2011-04-07 10:58:07 +00:00
|
|
|
|
if (nil == internal->delegate)
|
|
|
|
|
{
|
|
|
|
|
NSDebugMLLog(@"GSMime", @"-smtpClient:mimeFailed: %@ %@", self, d);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[internal->delegate smtpClient: self mimeFailed: d];
|
|
|
|
|
}
|
2020-11-14 04:25:34 +00:00
|
|
|
|
RELEASE(d);
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->queue count] > 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _timer: 10.0]; // Try connecting again in 10 seconds
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** If the receiver is in an idle state, this method initiates a connection
|
|
|
|
|
* to the SMTP server.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _startup
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->cState == TP_IDLE)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
|
|
|
|
|
NSHost *h;
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSString *n = internal->hostname;
|
|
|
|
|
NSString *p = internal->port;
|
2011-04-07 10:58:07 +00:00
|
|
|
|
int pnum;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
DESTROY(internal->lastError);
|
2011-04-07 10:58:07 +00:00
|
|
|
|
|
2010-03-17 15:46:20 +00:00
|
|
|
|
/* Need to start up ...
|
|
|
|
|
*/
|
|
|
|
|
if (n == nil)
|
|
|
|
|
{
|
|
|
|
|
n = [defs stringForKey: @"GSMimeSMTPClientHost"];
|
|
|
|
|
if ([n length] == 0)
|
|
|
|
|
{
|
|
|
|
|
n = @"localhost";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
h = [NSHost hostWithName: n];
|
|
|
|
|
if (h == nil)
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->istream = nil;
|
|
|
|
|
internal->ostream = nil;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSLog(@"Unable to find host %@", n);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: nil];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (p == nil)
|
|
|
|
|
{
|
|
|
|
|
p = [defs stringForKey: @"GSMimeSMTPClientPort"];
|
2011-04-07 10:58:07 +00:00
|
|
|
|
if ([p length] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
p = @"25";
|
|
|
|
|
}
|
2011-04-07 10:58:07 +00:00
|
|
|
|
}
|
|
|
|
|
if ((pnum = [p intValue]) <= 0 || pnum > 65535)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Bad port '%@' ... using 25", p);
|
|
|
|
|
pnum = 25;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[NSStream getStreamsToHost: h
|
|
|
|
|
port: pnum
|
|
|
|
|
inputStream: &internal->istream
|
|
|
|
|
outputStream: &internal->ostream];
|
|
|
|
|
[internal->istream retain];
|
|
|
|
|
[internal->ostream retain];
|
|
|
|
|
if (internal->istream == nil || internal->ostream == nil)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Unable to connect to %@:%@", n, p);
|
|
|
|
|
[self _shutdown: nil];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->istream setDelegate: self];
|
|
|
|
|
[internal->ostream setDelegate: self];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->istream scheduleInRunLoop: [NSRunLoop currentRunLoop]
|
|
|
|
|
forMode: NSDefaultRunLoopMode];
|
|
|
|
|
[internal->ostream scheduleInRunLoop: [NSRunLoop currentRunLoop]
|
|
|
|
|
forMode: NSDefaultRunLoopMode];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->cState = TP_OPEN;
|
|
|
|
|
[self _timer: 30.0]; // Allow 30 seconds for login
|
|
|
|
|
[internal->istream open];
|
|
|
|
|
[internal->ostream open];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Handles a timeout.
|
|
|
|
|
* Behavior depends on the state of the connection.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _timeout: (NSTimer*)t
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->timer == t)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
internal->timer = nil;
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->cState == TP_IDLE)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
/* Not connected.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->queue count] > 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _startup]; // Try connecting
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-03-18 09:56:51 +00:00
|
|
|
|
else if (internal->cState == TP_MESG)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
|
|
|
|
/* Already connected to server.
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if ([internal->queue count] == 0)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: nil]; // Nothing to send ... disconnect
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _doMessage]; // Send the next message
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
NSError *e;
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
|
|
|
|
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
|
[NSString stringWithFormat: @"Timeout while %@", [self stateDesc]],
|
|
|
|
|
NSLocalizedDescriptionKey,
|
|
|
|
|
nil];
|
|
|
|
|
e = [NSError errorWithDomain: GSMimeErrorDomain
|
|
|
|
|
code: GSMimeSMTPTimeout
|
|
|
|
|
userInfo: d];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
NSDebugMLLog(@"GSMime", @"%@ timeout at %@", self, [self stateDesc]);
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[self _shutdown: e];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* A convenience method to set the receivers timer to go off after the
|
|
|
|
|
* specified interval. Cancels previous timer (if any).
|
|
|
|
|
*/
|
2010-03-18 09:56:51 +00:00
|
|
|
|
- (void) _timer: (NSTimeInterval)s
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
if (internal->timer != nil)
|
2010-03-17 15:46:20 +00:00
|
|
|
|
{
|
2010-03-18 09:56:51 +00:00
|
|
|
|
[internal->timer invalidate];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
2010-10-27 11:24:44 +00:00
|
|
|
|
internal->timer
|
|
|
|
|
= [NSTimer scheduledTimerWithTimeInterval: s
|
|
|
|
|
target: self
|
|
|
|
|
selector: @selector(_timeout:)
|
|
|
|
|
userInfo: nil
|
|
|
|
|
repeats: NO];
|
2010-03-17 15:46:20 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|