You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1132 lines
33 KiB

/*
** CWParser.m
**
** Copyright (c) 2001-2007
**
** Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#import "Pantomime/CWParser.h"
#import <Foundation/Foundation.h>
#import <pEpIOSToolboxForExtensions/PEPLogger.h>
#import "CWConstants.h"
#import "CWFlags.h"
#import "CWInternetAddress.h"
#import <PantomimeFramework/CWMessage.h>
#import "CWMIMEUtility.h"
#import "NSMutableString+Extension.h"
#import "NSData+Extensions.h"
#import "NSData+CWParsingUtils.h"
#import "Pantomime/NSString+Extensions.h"
#import <stdlib.h>
#import <string.h> // For NULL on OS X
#import <ctype.h>
#import <stdio.h>
//#import "Pantomime/elm_defs.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSString.h>
#import <Foundation/NSURL.h>
//
//
//
static char *month_name[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
static struct _timezone {
char *name; /* time zone name */
int offset; /* offset, in minutes, EAST of GMT */
} timezone_info[] = {
/* the following are from RFC-822 */
{ "ut", 0 },
{ "gmt", 0 },
{ "est", -5*3600 }, { "edt", -4*3600 }, /* USA eastern standard */
{ "cst", -6*3600 }, { "cdt", -5*3600 }, /* USA central standard */
{ "mst", -7*3600 }, { "mdt", -6*3600 }, /* USA mountain standard */
{ "pst", -8*3600 }, { "pdt", -7*3600 }, /* USA pacific standard */
{ "z", 0 }, /* zulu time (the rest of the military codes are bogus) */
/* popular European timezones */
{ "wet", 0*3600 }, /* western european */
{ "met", 1*3600 }, /* middle european */
{ "eet", 2*3600 }, /* eastern european */
{ "bst", 1*3600 }, /* ??? british summer time */
/* Canadian timezones */
{ "ast", -4*3600 }, { "adt", -3*3600 }, /* atlantic */
{ "nst", -3*1800 },{ "ndt", -2*1800 }, /* newfoundland */
{ "yst", -9*3600 }, { "ydt", -8*3600 }, /* yukon */
{ "hst", -10*3600 }, /* hawaii (not really canada) */
/* Asian timezones */
{ "jst", 9*3600 }, /* japan */
{ "sst", 8*3600 }, /* singapore */
/* South-Pacific timezones */
{ "nzst", 12*3600 }, { "nzdt", 13*3600 }, /* new zealand */
{ "wst", 8*3600 }, { "wdt", 9*3600 }, /* western australia */
/*
* Daylight savings modifiers. These are not real timezones.
* They are used for things like "met dst". The "met" timezone
* is 1*3600, and applying the "dst" modifier makes it 2*3600.
*/
{ "dst", 1*3600 },
{ "dt", 1*3600 },
{ "st", 1*3600 }
};
//
//
//
NSInteger next_word(unsigned char *buf, NSUInteger start, NSUInteger len, unsigned char *word)
{
unsigned char *p;
NSUInteger i;
for (p = buf+start, i = start; (isspace(*p) || *p == ','); ++p, ++i);
if (start >= len) return -1;
while (i < len && !(isspace(*p) || *p == ','))
{
*word++ = *p++;
i++;
}
*word = '\0';
return p-buf-start;
}
// MARK: - private interface
@interface CWParser (Private)
+ (id)_parameterValueUsingLine:(NSData *)theLine
range:(NSRange)theRange
decode:(BOOL)decode
charset:(NSString *)theCharset;
@end
//
//
//
@implementation CWParser
+ (void) parseContentDescription: (NSData *) theLine
inPart: (CWPart *) thePart
{
NSData *aData;
aData = [[theLine subdataFromIndex: 20] dataByTrimmingWhiteSpaces];
if (aData && [aData length])
{
[thePart setContentDescription: [[aData dataFromQuotedData] asciiString] ];
}
}
//
//
//
+ (void)parseContentDisposition:(NSData *)theLine
inPart: (CWPart *)thePart;
{
NSInteger keyLength = @"Content-Disposition: ".length;
if ([theLine length] > keyLength) {
NSData *aData = [theLine subdataFromIndex: keyLength];
NSRange aRange = [aData firstSemicolonOrNewlineInRange:NSMakeRange(0, aData.length)];
if (aRange.location != NSNotFound) {
// We set the content disposition to this part
[thePart setContentDisposition:
([[[aData subdataWithRange: NSMakeRange(0, aRange.location)] asciiString] caseInsensitiveCompare:@"inline"] == NSOrderedSame ?
PantomimeInlineDisposition :
PantomimeAttachmentDisposition)];
// We now decode our filename
NSRange filenameRange = [aData rangeOfCString: "filename"];
if (filenameRange.location != NSNotFound) {
[thePart setFilename: [CWParser _parameterValueUsingLine: aData
range: filenameRange
decode: YES
charset: [thePart defaultCharset]]];
}
} else {
[thePart setContentDisposition:
([[[aData dataByTrimmingWhiteSpaces] asciiString] caseInsensitiveCompare: @"inline"] == NSOrderedSame ?
PantomimeInlineDisposition :
PantomimeAttachmentDisposition)];
}
} else {
[thePart setContentDisposition: PantomimeAttachmentDisposition];
}
}
//
//
//
+ (void) parseContentID: (NSData *) theLine
inPart: (CWPart *) thePart
{
if ([theLine length] > 12)
{
NSData *aData;
aData = [theLine subdataFromIndex: 12];
if ([aData hasCPrefix: "<"] && [aData hasCSuffix: ">"])
{
[thePart setContentID: [[aData subdataWithRange: NSMakeRange(1, [aData length]-2)] asciiString]];
}
else
{
[thePart setContentID: [aData asciiString]];
}
}
else
{
[thePart setContentID: @""];
}
}
//
//
//
+ (void) parseContentTransferEncoding: (NSData *) theLine
inPart: (CWPart *) thePart
{
if ([theLine length] > 26)
{
NSData *aData;
aData = [[theLine subdataFromIndex: 26] dataByTrimmingWhiteSpaces];
if ([aData caseInsensitiveCCompare: "quoted-printable"] == NSOrderedSame)
{
[thePart setContentTransferEncoding: PantomimeEncodingQuotedPrintable];
}
else if ([aData caseInsensitiveCCompare: "base64"] == NSOrderedSame)
{
[thePart setContentTransferEncoding: PantomimeEncodingBase64];
}
else if ([aData caseInsensitiveCCompare: "8bit"] == NSOrderedSame)
{
[thePart setContentTransferEncoding: PantomimeEncoding8bit];
}
else if ([aData caseInsensitiveCCompare: "binary"] == NSOrderedSame)
{
[thePart setContentTransferEncoding: PantomimeEncodingBinary];
}
else
{
[thePart setContentTransferEncoding: PantomimeEncodingNone];
}
}
else
{
[thePart setContentTransferEncoding: PantomimeEncodingNone];
}
}
/**
Shrinks any valid range by one in length.
A valid range is one with a valid position (not NSNotFound) and a length > 0.
*/
NSRange shrinkRange(NSRange range)
{
if (range.location != NSNotFound && range.location != NSNotFound) {
return NSMakeRange(range.location, range.length - 1);
} else {
return range;
}
}
//
//
//
+ (void) parseContentType: (NSData *) theLine
inPart: (CWPart *) thePart
{
NSRange aRange;
NSData *aData;
NSInteger x;
if ([theLine length] <= 14)
{
[thePart setContentType: @"text/plain"];
return;
}
aData = [[theLine subdataFromIndex: 13] dataByTrimmingWhiteSpaces];
// We first skip the parameters, if we need to
x = [aData indexOfCharacter: ';'];
if (x > 0)
{
aData = [aData subdataToIndex: x];
}
// We see if there's a subtype specified for text, if none was specified, we append "/plain"
x = [aData indexOfCharacter: '/'];
if (x < 0 && [aData hasCaseInsensitiveCPrefix: "text"])
{
[thePart setContentType: [[[aData asciiString] stringByAppendingString: @"/plain"] lowercaseString]];
}
else
{
[thePart setContentType: [[aData asciiString] lowercaseString]];
}
//
// We decode our boundary (if we need to)
//
aRange = shrinkRange([theLine rangeOfCString: "boundary=" options: NSCaseInsensitiveSearch]);
if (aRange.location != NSNotFound)
{
[thePart setBoundary: [CWParser _parameterValueUsingLine: theLine range: aRange decode: NO charset: nil]];
}
//
// We decode our charset (if we need to)
//
aRange = shrinkRange([theLine rangeOfCString: "charset=" options: NSCaseInsensitiveSearch]);
if (aRange.location != NSNotFound)
{
[thePart setCharset: [[CWParser _parameterValueUsingLine: theLine range: aRange decode: NO charset: nil] asciiString]];
}
//
// We decode our format (if we need to). See RFC2646.
//
aRange = shrinkRange([theLine rangeOfCString: "format=" options: NSCaseInsensitiveSearch]);
if (aRange.location != NSNotFound)
{
NSData *aFormat;
aFormat = [CWParser _parameterValueUsingLine: theLine range: aRange decode: NO charset: nil];
if ([aFormat caseInsensitiveCCompare: "flowed"] == NSOrderedSame)
{
[thePart setFormat: PantomimeFormatFlowed];
}
else
{
[thePart setFormat: PantomimeFormatUnknown];
}
}
else
{
[thePart setFormat: PantomimeFormatUnknown];
}
//
// We decode the parameter "name" if the thePart is an instance of Part
//
if ([thePart isKindOfClass: [CWPart class]])
{
aRange = [theLine rangeOfCString: "name=" options: NSCaseInsensitiveSearch];
if (aRange.location != NSNotFound)
{
[thePart setFilename: [CWParser _parameterValueUsingLine: theLine range: aRange decode: YES charset: [thePart defaultCharset]]];
}
}
}
//
//
//
+ (void) parseDate: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([[theLine asciiString] containsString:@"__Smtpdate"]) {
// We have seen spammer to use this date: "Date: __Smtpdate", which we consider invalid.
// Do nothing.
return;
}
if ([theLine length] > 6)
{
NSData *aData;
NSInteger month;
int day, year, hours, mins, secs;
NSUInteger tz, i, j, len, tot, s;
unsigned char *bytes, *word;
aData = [theLine subdataFromIndex: 6];
word = malloc(256);
*word = '\0';
//LogInfo(@"Have to parse |%@|", [aData asciiString]);
bytes = (unsigned char*)[aData bytes];
tot = [aData length];
i = len = 0;
s = 0;
tz = 0;
// date-time = [ day-of-week "," ] date FWS time [CFWS]
// day-of-week = ([FWS] day-name) / obs-day-of-week
// day-name = "Mon" / "Tue" / "Wed" / "Thu" /
// "Fri" / "Sat" / "Sun"
// date = day month year
// year = 4*DIGIT / obs-year
// month = (FWS month-name FWS) / obs-month
// month-name = "Jan" / "Feb" / "Mar" / "Apr" /
// "May" / "Jun" / "Jul" / "Aug" /
// "Sep" / "Oct" / "Nov" / "Dec"
//
// day = ([FWS] 1*2DIGIT) / obs-day
// time = time-of-day FWS zone
// time-of-day = hour ":" minute [ ":" second ]
// hour = 2DIGIT / obs-hour
// minute = 2DIGIT / obs-minute
// second = 2DIGIT / obs-second
// zone = (( "+" / "-" ) 4DIGIT) / obs-zone
//
// We need to handle RFC2822 and UNIX time:
//
// Date: Wed, 02 Jan 2002 09:07:19 -0700
// Date: 02 Jan 2002 19:57:49 +0000
//
// And broken dates such as:
//
// Date: Thu, 03 Jan 2002 16:40:30 GMT
// Date: Wed, 2 Jan 2002 08:56:18 -0700 (MST)
// Date: Wed, 9 Jan 2002 10:04:23 -0500 (Eastern Standard Time)
// Date: 11-Jan-02
// Date: Tue, 15 Jan 2002 15:45:53 -0801
// Date: Thu, 17 Jan 2002 11:54:11 -0900<br>
//
//while (i < tot && isspace(*bytes))
// {
// i++; bytes++;
// }
len = next_word(bytes, i, tot, word); if (len <= 0) { free(word); return; }
if (isalpha(*word))
{
//LogInfo(@"UNIX DATE");
// We skip the first word, no need for it.
i += len+1; len = next_word(bytes, i, tot, word); if (len <= 0) { free(word); return; }
}
month = year = -1;
// We got a RFC 822 date. The syntax is:
// day month year hh:mm:ss zone
// For example: 03 Apr 2003 17:27:06 +0200
//LogInfo(@"RFC-822 time");
day = atoi((const char*)word);
//printf("len = %d |%s| day = %d\n", len, word, day);
// We get the month name and we convert it.
i += len+1; len = next_word(bytes, i, tot, word); if (len <= 0) { free(word); return; }
for (j = 0; j < 12; j++)
{
if (strncasecmp((const char*)word, month_name[j], 3) == 0)
{
month = j+1;
}
}
if (month < 0) { free(word); return; }
//printf("len = %d |%s| month = %d\n", len, word, month);
// We get the year.
i += len+1; len = next_word(bytes, i, tot, word); if (len <= 0) { free(word); return; }
year = atoi((const char*)word);
if (year < 70) year += 2000;
if (year < 100) year += 1900;
//printf("len = %d |%s| year = %d\n", len, word, year);
// We parse the time using the hh:mm:ss format.
i += len+1; len = next_word(bytes, i, tot, word); if (len <= 0) { free(word); return; }
sscanf((const char*)word, "%d:%d:%d", &hours, &mins, &secs);
//printf("len = %d |%s| %d:%d:%d\n", len, word, hours, mins, secs);
// We parse the timezone.
i += len+1; len = next_word(bytes, i, tot, word);
if (len <= 0)
{
tz = 0;
}
else
{
unsigned char *p;
p = word;
if (*p == '-' || *p == '+')
{
s = (*p == '-' ? -1 : 1);
p++;
}
len = strlen((const char*)p);
if (isdigit(*p))
{
if (len == 2)
{
tz = (*(p)-48)*36000+*((p+1)-48)*3600;
}
else
{
tz = (*(p)-48)*36000+(*(p+1)-48)*3600+(*(p+2)-48)*10+(*(p+3)-48);
}
}
else
{
for (j = 0; j < sizeof(timezone_info)/sizeof(timezone_info[0]); j++)
{
if (strncasecmp((const char*)p, timezone_info[j].name, len) == 0)
{
tz = timezone_info[j].offset;
}
}
}
tz = s*tz;
}
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:day];
[components setMonth:month];
[components setYear:year];
[components setHour:hours];
[components setMinute:mins];
[components setSecond:secs];
[components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT: tz]];
NSDate *date = [calendar dateFromComponents:components];
[theMessage setOriginationDate:date];
free(word);
}
}
//
//
//
+ (NSData *) parseDestination: (NSData *) theLine
forType: (PantomimeRecipientType) theType
inMessage: (CWMessage *) theMessage
quick: (BOOL) theBOOL
{
CWInternetAddress *anInternetAddress;
NSData *aData;
NSUInteger i, len, s_len, x, y;
unsigned char *bytes;
BOOL b;
len = 0;
if (theBOOL)
{
aData = theLine;
}
else
{
switch (theType)
{
case PantomimeBccRecipient:
len = 5;
break;
case PantomimeCcRecipient:
case PantomimeToRecipient:
len = 4;
break;
case PantomimeResentBccRecipient:
len = 12;
break;
case PantomimeResentCcRecipient:
case PantomimeResentToRecipient:
len = 11;
break;
}
// We skip over emtpy headers.
if (len >= [theLine length]) return [NSData data];
aData = [theLine subdataFromIndex: len];
}
bytes = (unsigned char*)[aData bytes];
len = [aData length];
b = NO; x = 0;
for (i = 0; i < len; i++)
{
if (*bytes == '"')
{
b = !b;
}
if (*bytes == ',' || i == len-1)
{
if (b)
{
bytes++;
continue;
}
y = i;
// We strip the trailing comma for all but the last entries.
s_len = y-x;
if (i == len-1) s_len++;
anInternetAddress = [[CWInternetAddress alloc]
initWithString: [CWMIMEUtility decodeHeader: [[aData subdataWithRange: NSMakeRange(x, s_len)] dataByTrimmingWhiteSpaces]
charset: [theMessage defaultCharset]]];
[anInternetAddress setType: theType];
[theMessage addRecipient: anInternetAddress];
RELEASE(anInternetAddress);
x = y+1;
}
bytes++;
}
return aData;
}
//
//
//
+ (NSData *) parseFrom: (NSData *) theLine
inMessage: (CWMessage *) theMessage
quick: (BOOL) theBOOL;
{
CWInternetAddress *anInternetAddress;
NSData *aData;
if (!theBOOL && !([theLine length] > 6))
{
return [NSData data];
}
if (theBOOL)
{
aData = theLine;
}
else
{
aData = [theLine subdataFromIndex: 6];
}
anInternetAddress = [[CWInternetAddress alloc] initWithString:
[CWMIMEUtility decodeHeader: aData charset: [theMessage defaultCharset]]
];
[theMessage setFrom: anInternetAddress];
RELEASE(anInternetAddress);
return aData;
}
//
// This method is used to parse the In-Reply-To: header value.
//
+ (NSData *)parseInReplyTo:(NSData *)rawLine inMessage:(CWMessage *)message quick:(BOOL)quick
{
NSData *result = nil;
if (quick) {
result = rawLine;
} else if ([rawLine length] > 13) {
result = [rawLine subdataFromIndex: 13];
} else {
return [NSData data];
}
// We check for lame headers like that:
//
// In-Reply-To: <4575197F.7020602@de.ibm.com> (Markus Deuling's message of "Tue, 05 Dec 2006 08:02:23 +0100")
// In-Reply-To: <MABBJIJNAFCGBJJJOEBHEEFGKIAA.reldred@viablelinks.com>; from reldred@viablelinks.com on Wed, Mar 26, 2003 at 11:23:37AM -0800
//
NSInteger x = [result indexOfCharacter: ';'];
NSInteger y = [result indexOfCharacter: ' '];
if (x > 0 && x < y) {
result = [result subdataToIndex: x];
} else if (y > 0) {
result = [result subdataToIndex: y];
}
result = [result unwrap];
[message setInReplyTo: [result asciiString]];
return result;
}
//
//
//
+ (NSData *)parseMessageID:(NSData *)rawLine inMessage:(CWMessage *)message quick:(BOOL)quick
{
if (!quick && !([rawLine length] > 12)) {
return [NSData data];
}
NSData *result = nil;
if (quick) {
result = rawLine;
} else {
result = [rawLine subdataFromIndex: 12];
}
result = [result unwrap];
[message setMessageID: [[result dataByTrimmingWhiteSpaces] asciiString]];
return result;
}
//
//
//
+ (void) parseMIMEVersion: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([theLine length] > 14)
{
[theMessage setMIMEVersion: [[theLine subdataFromIndex: 14] asciiString]];
}
}
//
//
//
+ (NSData *) parseReferences:(NSData *)rawLine inMessage:(CWMessage *)message quick:(BOOL) quick
{
NSData *result = nil;
if (quick) {
result = rawLine;
} else if ([rawLine length] > 12) {
result = [rawLine subdataFromIndex: 12];
}
if (result && [result length]) {
NSString *line = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
NSArray<NSString*> *wrappedRefs =
[line componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSMutableArray<NSString*> *references =
[[NSMutableArray alloc] initWithCapacity: wrappedRefs.count];
for (NSString *wrappedRef in wrappedRefs) {
NSMutableString *ref = [wrappedRef stringByTrimmingWhiteSpaces].mutableCopy;
if ([ref isEqualToString:@""]) {
continue;
}
[ref unwrap];
[references addObject:ref];
}
[message setReferences: references];
return result;
}
return [NSData data];
}
//
//
//
+ (void) parseReplyTo: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([theLine length] > 10)
{
CWInternetAddress *anInternetAddress;
NSMutableArray *aMutableArray;
NSData *aData;
unsigned char *bytes;
NSUInteger i, len, s_len, x, y;
BOOL b;
aMutableArray = [[NSMutableArray alloc] init];
aData = [theLine subdataFromIndex: 10];
bytes = (unsigned char*)[aData bytes];
len = [aData length];
b = NO; x = 0;
for (i = 0; i < len; i++)
{
if (*bytes == '"')
{
b = !b;
}
if (*bytes == ',' || i == len-1)
{
if (b)
{
bytes++;
continue;
}
y = i;
// We strip the trailing comma for all but the last entries.
s_len = y-x;
if (i == len-1) s_len++;
anInternetAddress = [[CWInternetAddress alloc]
initWithString: [CWMIMEUtility decodeHeader: [[aData subdataWithRange: NSMakeRange(x, s_len)] dataByTrimmingWhiteSpaces]
charset: [theMessage defaultCharset]]];
[aMutableArray addObject: anInternetAddress];
RELEASE(anInternetAddress);
x = y+1;
}
bytes++;
}
if ([aMutableArray count])
{
[theMessage setReplyTo: aMutableArray];
}
RELEASE(aMutableArray);
}
}
//
//
//
+ (void) parseResentFrom: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([theLine length] > 13)
{
CWInternetAddress *anInternetAddress;
anInternetAddress = [[CWInternetAddress alloc] initWithString: [CWMIMEUtility decodeHeader:
[theLine subdataFromIndex: 13]
charset: [theMessage defaultCharset]]];
[theMessage setResentFrom: anInternetAddress];
RELEASE(anInternetAddress);
}
}
//
//
//
+ (void) parseStatus: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([theLine length] > 8)
{
NSData *aData;
aData = [theLine subdataFromIndex: 8];
[[theMessage flags] addFlagsFromData: aData format: PantomimeFormatMbox];
[theMessage addHeader: @"Status" withValue: [aData asciiString]];
}
}
//
//
//
+ (void) parseXStatus: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
if ([theLine length] > 10)
{
NSData *aData;
aData = [theLine subdataFromIndex: 10];
[[theMessage flags] addFlagsFromData: aData format: PantomimeFormatMbox];
[theMessage addHeader: @"X-Status" withValue: [aData asciiString]];
}
}
//
//
//
+ (NSData *) parseSubject: (NSData *) theLine
inMessage: (CWMessage *) theMessage
quick: (BOOL) theBOOL
{
NSData *aData;
if (theBOOL)
{
aData = theLine;
}
else if ([theLine length] > 9)
{
aData = [[theLine subdataFromIndex: 8] dataByTrimmingWhiteSpaces];
}
else
{
return [NSData data];
}
[theMessage setSubject: [CWMIMEUtility decodeHeader: aData charset: [theMessage defaultCharset]]];
return aData;
}
//
//
//
+ (void) parseUnknownHeader: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
NSData *aName, *aValue;
NSRange range;
range = [theLine rangeOfCString: ":"];
if (range.location != NSNotFound)
{
aName = [theLine subdataWithRange: NSMakeRange(0, range.location)];
// we keep only the headers that have a value
if (([theLine length]-range.location-1) > 0)
{
aValue = [theLine subdataWithRange: NSMakeRange(range.location + 2, [theLine length]-range.location-2)];
[theMessage addHeader: [aName asciiString] withValue: [aValue asciiString]];
}
}
}
//
//
//
+ (void) parseOrganization: (NSData *) theLine
inMessage: (CWMessage *) theMessage
{
NSString *organization;
if ([theLine length] > 14)
{
organization = [CWMIMEUtility decodeHeader: [[theLine subdataFromIndex: 13] dataByTrimmingWhiteSpaces]
charset: [theMessage defaultCharset]];
}
else
{
organization = @"";
}
[theMessage setOrganization: organization];
}
@end
// MARK: - private methods
@implementation CWParser (Private)
+ (id)_parameterValueUsingLine:(NSData *)inData
range:(NSRange)range
decode:(BOOL)decode
charset:(NSString *)charset;
{
NSUInteger len = [inData length];
// Look for the first occurrence of '=' before the end of the line within
// our range. That marks the beginning of the value. If we don't find one,
// we set the beggining right after the end of the key tag.
// (- 1 because we need to start the search *before* the "=" in "name=" to be able to find
// the "=")
NSUInteger searchStart = NSMaxRange(range) - 1;
NSUInteger endOfLine = len - NSMaxRange(range);
NSRange r1 = [inData rangeOfCString: "="
options: 0
range: NSMakeRange(searchStart, endOfLine)];
NSUInteger value_start = 0;
NSUInteger value_end = 0;
if (r1.location != NSNotFound){
value_start = r1.location + r1.length;
} else {
value_start = range.location + range.length;
}
// The parameter can be quoted or not like this (for example, with a charset):
// charset="us-ascii"
// charset = "us-ascii"
// charset=us-ascii
// charset = us-ascii
// It can be terminated by ';' or end of line.
// Look the the first occurrence of ';' or newline/end of line.
// That marks the end of this key value pair.
// If we don't find one, we set it to the end of the line.
r1 = [inData firstSemicolonOrNewlineInRange:NSMakeRange(NSMaxRange(range),
len - NSMaxRange(range))];
if (r1.location != NSNotFound) {
value_end = r1.location - 1;
} else {
value_end = len - 1;
}
// We now have a range that should contain our value.
// Build a NSRange out of it.
if (value_end - value_start + 1 > 0) {
r1 = NSMakeRange(value_start, value_end - value_start + 1);
} else {
r1 = NSMakeRange(value_start, 0);
}
NSMutableData *resultData = [[NSMutableData alloc] initWithData:
[[[inData subdataWithRange: r1] dataByTrimmingWhiteSpaces]
dataFromQuotedData]
];
// VERY IMPORTANT:
// We check if something was encoded using RFC2231. We need to adjust
// value_end if we find a multi-line parameter. We also proceed
// with data substitution while we loop for parameter values unfolding.
BOOL is_rfc2231 = NO;
NSRange r2;
BOOL has_charset = NO;
if ([inData characterAtIndex: NSMaxRange(range)] == '*') {
is_rfc2231 = YES;
if ([inData characterAtIndex: NSMaxRange(range) + 1] == '0') {
// We consider parameter value continuations (Section 3. of the RFC)
// in order the set the appropriate end boundary.
// We check if we have a charset, in case of a multiline value.
if ([inData characterAtIndex: NSMaxRange(range) + 2] == '*') {
has_charset = YES;
}
r1.location = range.location;
r1.length = range.length;
NSUInteger parameters_count = 1;
while (YES) {
r1 = [inData rangeOfCString: [[NSString stringWithFormat: @"%s*%li",
[[inData subdataWithRange: range] cString],
(unsigned long)parameters_count] UTF8String]
options: 0
range: NSMakeRange(NSMaxRange(r1), len-NSMaxRange(r1))];
parameters_count++;
if (r1.location == NSNotFound) {
break;
}
value_start = NSMaxRange(r1) + 1;
while ([inData characterAtIndex: value_start] == '*' || [inData characterAtIndex: value_start] == '=') {
value_start++;
}
NSRange r2 = [inData firstSemicolonOrNewlineInRange:NSMakeRange(NSMaxRange(r1),
len - NSMaxRange(r1))];
if (r2.location != NSNotFound) {
value_end = r2.location;
} else {
value_end = len;
}
[resultData appendData:
[[[inData subdataWithRange: NSMakeRange(value_start, value_end - value_start)]
dataFromSemicolonTerminatedData]
dataFromQuotedData]];
}
} else if ([inData characterAtIndex: NSMaxRange(range) + 1] == '=') {
// Example: "title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A"
has_charset = YES;
}
}
char *charsetAndLanguageDelimiter = "'";
// Find first delimiter
r1 = [resultData rangeOfCString: charsetAndLanguageDelimiter];
if (is_rfc2231 && has_charset && r1.location != NSNotFound) {
// Find second delimiter
r2 = [resultData rangeOfCString: charsetAndLanguageDelimiter
options: 0
range:
NSMakeRange(NSMaxRange(r1), [resultData length] - NSMaxRange(r1))
];
// We intentionally ignore the language. We do not have a use case for it.
// It would be inbetween r1 and r2 here though.
// Extract charset (ignore language if any)
NSData *charsetData = [resultData subdataToIndex: r1.location];
// Strip charset, language and delimiters from our data
[resultData replaceBytesInRange: NSMakeRange(0, NSMaxRange(r2))
withBytes: NULL
length: 0];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Suppress warning. We need to be able to pass the specified charset
if (decode) {
NSString *result = [[NSString alloc] initWithData: resultData
encoding: NSASCIIStringEncoding];
if (has_charset) {
return [result stringByReplacingPercentEscapesUsingEncoding:
[NSString encodingForCharset: charsetData]];
} else {
return result;
}
}
#pragma clang diagnostic pop
} else {
if (decode) {
return [CWMIMEUtility decodeHeader: resultData charset: charset];
}
}
return resultData;
}
@end