diff --git a/ChangeLog b/ChangeLog index 6cc3acb9b..3cd0f84b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2002-03-06 Richard Frith-Macdonald + + * Source/Additions/GNUmakefile: build subproject. + * Source/Additions/Makefile.preamble: set include flags. + * Source/Additions/GSXML.m: moved implementation here. + * Source/Additions/GSMime.m: moved implementation here. + * Source/GNUmakefile: build subproject. + 2002-03-06 Richard Frith-Macdonald * GNUmakefile: build SSL aggregate project diff --git a/Source/Additions/GNUmakefile b/Source/Additions/GNUmakefile new file mode 100644 index 000000000..5cb2289bd --- /dev/null +++ b/Source/Additions/GNUmakefile @@ -0,0 +1,40 @@ +# +# src makefile for the GNUstep Base Library +# +# Copyright (C) 1997 Free Software Foundation, Inc. +# +# Written by: Scott Christley +# +# This file is part of the GNUstep Base Library. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. +# + +GNUSTEP_MAKEFILES = $(GNUSTEP_SYSTEM_ROOT)/Makefiles + +include $(GNUSTEP_MAKEFILES)/common.make + +SUBPROJECT_NAME=Additions + +Additions_OBJC_FILES =\ + GSMime.m \ + GSXML.m + + +-include Makefile.preamble + +include $(GNUSTEP_MAKEFILES)/subproject.make + +-include Makefile.postamble diff --git a/Source/Additions/GSMime.m b/Source/Additions/GSMime.m new file mode 100644 index 000000000..378ee0017 --- /dev/null +++ b/Source/Additions/GSMime.m @@ -0,0 +1,2450 @@ +/** Implementation for GSMIME + + Copyright (C) 2000,2001 Free Software Foundation, Inc. + + Written by: Richard frith-Macdonald + Date: October 2000 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + The MIME parsing system + + Mime Parser +

+ 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. +

+

+ Eventually the goal is to center round three classes - +

+ + document + + A container for the actual data (and headers) of a mime/http document. + parser + + 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. + + unparser + + An object to take a mime/http document and produce a data object + suitable for transmission. + + +
+ $Date$ $Revision$ +*/ + +#include +#include +#include +#include + +static NSCharacterSet *specials = nil; + +/* + * Name - decodebase64() + * Purpose - Convert 4 bytes in base64 encoding to 3 bytes raw data. + */ +static void +decodebase64(unsigned char *dst, const char *src) +{ + 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); +} + +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* +decodeWord(unsigned char *dst, unsigned char *src, unsigned char *end, WE enc) +{ + int c; + + if (enc == WE_QUOTED) + { + while (*src && (src != end)) + { + if (*src == '=') + { + src++; + if (*src == '\0') + { + break; + } + if (('\n' == *src) || ('\r' == *src)) + { + break; + } + c = isdigit(*src) ? (*src - '0') : (*src - 55); + c <<= 4; + src++; + if (*src == '\0') + { + break; + } + c += isdigit(*src) ? (*src - '0') : (*src - 55); + *dst = c; + } + else if (*src == '_') + { + *dst = '\040'; + } + else + { + *dst = *src; + } + dst++; + src++; + } + *dst = '\0'; + return dst; + } + else if (enc == WE_BASE64) + { + unsigned char buf[4]; + unsigned pos = 0; + + 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) + { + unsigned i; + + for (i = pos; i < 4; i++) + buf[i] = '\0'; + pos--; + } + decodebase64(dst, buf); + dst += pos; + *dst = '\0'; + return dst; + } + else + { + NSLog(@"Unsupported encoding type"); + return end; + } +} + +static NSStringEncoding +parseCharacterSet(NSString *token) +{ + if ([token compare: @"us-ascii"] == NSOrderedSame) + return NSASCIIStringEncoding; + if ([token compare: @"iso-8859-1"] == NSOrderedSame) + return NSISOLatin1StringEncoding; + + return NSASCIIStringEncoding; +} + +/** + * The most rudimentary context ... this is used for decoding plain + * text and binary dat (ie data which is not really decoded at all) + * 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); +} + +/** + * Sets the current value of the 'atEnd' flag. + */ +- (void) setAtEnd: (BOOL)flag +{ + atEnd = flag; +} +@end + +@interface GSMimeBase64DecoderContext : GSMimeCodingContext +{ +@public + unsigned char buf[4]; + unsigned pos; +} +@end +@implementation GSMimeBase64DecoderContext +@end + +@interface GSMimeQuotedDecoderContext : GSMimeCodingContext +{ +@public + unsigned char buf[4]; + unsigned pos; +} +@end +@implementation GSMimeQuotedDecoderContext +@end + +@interface GSMimeChunkedDecoderContext : GSMimeCodingContext +{ +@public + unsigned char buf[8]; + unsigned pos; + 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; + unsigned size; // Size of buffer required. + 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 + + + +@interface GSMimeParser (Private) +- (BOOL) _decodeBody: (NSData*)data; +- (NSString*) _decodeHeader; +- (BOOL) _unfoldHeader; +@end + +/** + *

+ * This class provides support for parsing MIME messages + * into GSMimeDocument objects. Each parser object maintains + * an associated document into which data is stored. + *

+ *

+ * You supply the document to be parsed as one or more data + * 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 + * document. + *

+ *

+ * On completion of parsing a valid document, the + * document method returns the resulting parsed document. + *

+ */ +@implementation GSMimeParser + +/** + * 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. + *

+ * The default implementation supports the following transfer + * encodings specified in either a transfer-encoding + * of content-transfer-encoding header - + *

+ * + * base64 + * quoted-printable + * binary (no coding actually performed) + * 7bit (no coding actually performed) + * 8bit (no coding actually performed) + * chunked (for HTTP/1.1) + * + */ +- (GSMimeCodingContext*) contextFor: (NSDictionary*)info +{ + NSString *name; + NSString *value; + + if (info == nil) + { + return AUTORELEASE([GSMimeCodingContext new]); + } + + name = [info objectForKey: @"Name"]; + if ([name isEqualToString: @"content-transfer-encoding"] == YES + || [name isEqualToString: @"transfer-encoding"] == YES) + { + value = [info objectForKey: @"Value"]; + if ([value length] == 0) + { + NSLog(@"Bad value for %@ header - assume binary encoding", name); + return AUTORELEASE([GSMimeCodingContext new]); + } + if ([value isEqualToString: @"base64"] == YES) + { + return AUTORELEASE([GSMimeBase64DecoderContext new]); + } + else if ([value isEqualToString: @"quoted-printable"] == YES) + { + return AUTORELEASE([GSMimeQuotedDecoderContext new]); + } + else if ([value isEqualToString: @"binary"] == YES) + { + return AUTORELEASE([GSMimeCodingContext new]); + } + else if ([value characterAtIndex: 0] == '7') + { + return AUTORELEASE([GSMimeCodingContext new]); + } + else if ([value characterAtIndex: 0] == '8') + { + return AUTORELEASE([GSMimeCodingContext new]); + } + else if ([value isEqualToString: @"chunked"] == YES) + { + return AUTORELEASE([GSMimeChunkedDecoderContext new]); + } + } + + 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. + */ +- (NSData*) data +{ + return data; +} + +- (void) dealloc +{ + RELEASE(data); + RELEASE(child); + RELEASE(context); + RELEASE(boundary); + RELEASE(document); + [super dealloc]; +} + +/** + *

+ * 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. + *

+ *

+ * 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. + *

+ *

+ * You may override this method in order to implement + * additional coding schemes. + *

+ */ +- (BOOL) decodeData: (NSData*)sData + fromRange: (NSRange)aRange + intoData: (NSMutableData*)dData + withContext: (GSMimeCodingContext*)con +{ + unsigned size = [dData length]; + unsigned len = [sData length]; + unsigned char *beg; + unsigned char *dst; + const char *src; + const char *end; + Class ccls; + + if (dData == nil || [con isKindOfClass: [GSMimeCodingContext class]] == NO) + { + [NSException raise: NSInvalidArgumentException + format: @"Bad destination data for decode"]; + } + GS_RANGE_CHECK(aRange, len); + + /* + * Get pointers into source data buffer. + */ + src = (const char *)[sData bytes]; + src += aRange.location; + end = src + aRange.length; + + ccls = [con class]; + if (ccls == [GSMimeBase64DecoderContext class]) + { + GSMimeBase64DecoderContext *ctxt; + + ctxt = (GSMimeBase64DecoderContext*)con; + + /* + * 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 == '=') + { + [ctxt setAtEnd: YES]; + cc = -1; + } + else if (cc == '-') + { + [ctxt setAtEnd: YES]; + break; + } + else + { + cc = -1; /* ignore */ + } + + if (cc >= 0) + { + ctxt->buf[ctxt->pos++] = cc; + if (ctxt->pos == 4) + { + ctxt->pos = 0; + decodebase64(dst, ctxt->buf); + dst += 3; + } + } + } + + /* + * Odd characters at end of decoded data need to be added separately. + */ + if ([ctxt atEnd] == YES && ctxt->pos > 0) + { + unsigned len = ctxt->pos - 1;; + + while (ctxt->pos < 4) + { + ctxt->buf[ctxt->pos++] = '\0'; + } + ctxt->pos = 0; + decodebase64(dst, ctxt->buf); + size += len; + } + [dData setLength: size + dst - beg]; + } + else if (ccls == [GSMimeQuotedDecoderContext class]) + { + GSMimeQuotedDecoderContext *ctxt; + + ctxt = (GSMimeQuotedDecoderContext*)con; + + /* + * 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 (ctxt->pos > 0) + { + if ((*src == '\n') || (*src == '\r')) + { + ctxt->pos = 0; + } + else + { + ctxt->buf[ctxt->pos++] = *src; + if (ctxt->pos == 3) + { + int c; + int val; + + ctxt->pos = 0; + c = ctxt->buf[1]; + val = isdigit(c) ? (c - '0') : (c - 55); + val *= 0x10; + c = ctxt->buf[2]; + val += isdigit(c) ? (c - '0') : (c - 55); + *dst++ = val; + } + } + } + else if (*src == '=') + { + ctxt->buf[ctxt->pos++] = '='; + } + else + { + *dst++ = *src; + } + src++; + } + [dData setLength: size + dst - beg]; + } + else if (ccls == [GSMimeChunkedDecoderContext class]) + { + GSMimeChunkedDecoderContext *ctxt; + const char *footers = src; + + ctxt = (GSMimeChunkedDecoderContext*)con; + + beg = 0; + /* + * Make sure buffer is big enough, and set up output pointers. + */ + [dData setLength: ctxt->size]; + dst = (unsigned char*)[dData mutableBytes]; + dst = dst + size; + beg = dst; + + while ([ctxt atEnd] == NO && src < end) + { + 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) + { + int val = 0; + int index; + + 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; + /* + * If we have read a chunk already, make sure that our + * destination size is updated correctly before growing + * the buffer for another chunk. + */ + size += (dst - beg); + ctxt->size = size + val; + [dData setLength: ctxt->size]; + dst = (unsigned char*)[dData mutableBytes]; + dst += size; + 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. + * otherwise, what we actually want it to read footers. + */ + 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') + { + [ctxt setAtEnd: YES]; + } + 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]; + if ([ctxt atEnd] == YES) + { + NSMutableData *old; + + /* + * Pretend we are back parsing the original headers ... + */ + old = data; + data = ctxt->data; + bytes = (unsigned char*)[data mutableBytes]; + dataEnd = [data length]; + inBody = NO; + + /* + * Duplicate the normal header parsing process for our footers. + */ + while (inBody == NO) + { + if ([self _unfoldHeader] == NO) + { + break; + } + if (inBody == NO) + { + NSString *header; + + header = [self _decodeHeader]; + if (header == nil) + { + break; + } + if ([self parseHeader: header] == NO) + { + break; + } + } + } + + /* + * restore original data. + */ + ctxt->data = data; + data = old; + bytes = (unsigned char*)[data mutableBytes]; + dataEnd = [data length]; + inBody = YES; + } + } + /* + * Correct size of output buffer. + */ + [dData setLength: size + dst - beg]; + } + else + { + /* + * Assume binary (no) decoding required. + */ + [dData setLength: size + (end - src)]; + dst = (unsigned char*)[dData mutableBytes]; + memcpy(&dst[size], src, (end - src)); + } + + /* + * A nil data item as input represents end of data. + */ + if (sData == nil) + { + [con setAtEnd: YES]; + } + + return YES; +} + +- (NSString*) description +{ + NSMutableString *desc; + + desc = [NSMutableString stringWithFormat: @"GSMimeParser <%0x> -\n", self]; + [desc appendString: [document description]]; + return desc; +} + +/** + * Returns the object into which raw mime data is being parsed. + */ +- (GSMimeDocument*) document +{ + return document; +} + +/** + * Returns YES if the document parsing is known to be completed. + */ +- (BOOL) isComplete +{ + return complete; +} + +/** + * Returns YES if all the document headers have been parsed but + * the document body parsing may not yet be complete. + */ +- (BOOL) isInBody +{ + return inBody; +} + +/** + * Returns YES if parsing of the document headers has not yet + * been completed. + */ +- (BOOL) isInHeaders +{ + if (inBody == YES) + return NO; + if (complete == YES) + return NO; + return YES; +} + +- (id) init +{ + self = [super init]; + if (self != nil) + { + data = [[NSMutableData alloc] init]; + document = [[GSMimeDocument alloc] init]; + } + return self; +} + +/** + *

+ * This method is called repeatedly to pass raw mime data into + * the parser. It returns YES as long as it wants + * more data to complete parsing of a document, and NO + * if parsing is complete, either due to having reached the end of + * a document or due to an error. + *

+ *

+ * 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. + *

+ */ +- (BOOL) parse: (NSData*)d +{ + unsigned l = [d length]; + + if (complete == YES) + { + return NO; /* Already completely parsed! */ + } + if (l > 0) + { + NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'", l, l, l, [d bytes]); + if (inBody == NO) + { + [data appendBytes: [d bytes] length: [d length]]; + bytes = (unsigned char*)[data mutableBytes]; + dataEnd = [data length]; + + while (inBody == NO) + { + if ([self _unfoldHeader] == NO) + { + return YES; /* Needs more data to fill line. */ + } + if (inBody == NO) + { + NSString *header; + + header = [self _decodeHeader]; + if (header == nil) + { + return NO; /* Couldn't handle words. */ + } + if ([self parseHeader: header] == NO) + { + return NO; /* Header not parsed properly. */ + } + NSDebugMLLog(@"GSMime", @"Parsed header '%@'", header); + } + else + { + NSDebugMLLog(@"GSMime", @"Parsed end of headers"); + } + } + /* + * All headers have been parsed, so we empty our internal buffer + * (which we will now use to store decoded data) and place unused + * information back in the incoming data object to act as input. + */ + d = AUTORELEASE([data copy]); + [data setLength: 0]; + + /* + * If we have finished parsing the headers, we may have http + * continuation header(s), in which case, we must start parsing + * headers again. + */ + if (inBody == YES) + { + NSDictionary *info; + + info = [[document headersNamed: @"http"] lastObject]; + if (info != nil) + { + NSString *val; + + val = [info objectForKey: NSHTTPPropertyStatusCodeKey]; + if (val != nil) + { + int v = [val intValue]; + + if (v >= 100 && v < 200) + { + /* + * This is an intermediary response ... so we have + * to restart the parsing operation! + */ + NSDebugMLLog(@"GSMime", @"Parsed http continuation"); + inBody = NO; + } + } + } + } + } + + if ([d length] > 0) + { + if (inBody == YES) + { + /* + * We can't just re-call -parse: ... + * that would lead to recursion. + */ + return [self _decodeBody: d]; + } + else + { + return [self parse: d]; + } + } + + return YES; /* Want more data for body */ + } + else + { + BOOL result; + + if (inBody == YES) + { + result = [self _decodeBody: d]; + } + else + { + /* + * If still parsing headers, add CR-LF sequences to terminate + * the headers. + */ + result = [self parse: [NSData dataWithBytes: @"\r\n\r\n" length: 4]]; + } + inBody = NO; + complete = YES; /* Finished parsing */ + return result; + } +} + +/** + *

+ * This method is called to parse a header line for the + * current document, split its contents into an info + * dictionary, and add that information to the document. + *

+ *

+ * The standard implementation of this method scans basic + * information and then calls -scanHeader:named:into: + * to complete the parsing of the header. + *

+ *

+ * This method also performs consistency checks on headers scanned + * so it is recommended that it is not overridden, but that + * subclasses override -scanHeader:named:into: to + * implement custom scanning. + *

+ *

+ * As a special case, for HTTP support, this method also parses + * lines in the format of HTTP responses as if they were headers + * named http. The resulting header info dictionary + * contains - + *

+ * + * HttpMajorVersion + * The first part of the version number + * HttpMinorVersion + * The second part of the version number + * NSHTTPPropertyServerHTTPVersionKey + * The full HTTP protocol version number + * NSHTTPPropertyStatusCodeKey + * The HTTP status code + * NSHTTPPropertyStatusReasonKey + * The text message (if any) after the status code + * + */ +- (BOOL) parseHeader: (NSString*)aHeader +{ + NSScanner *scanner = [NSScanner scannerWithString: aHeader]; + NSString *name; + NSString *value; + NSMutableDictionary *info; + NSCharacterSet *skip; + unsigned count; + + info = [NSMutableDictionary dictionary]; + + /* + * Store the raw header string in the info dictionary. + */ + [info setObject: [scanner string] forKey: @"RawHeader"]; + + /* + * Special case - permit web response status line to act like a header. + */ + if ([scanner scanString: @"HTTP" intoString: &name] == NO + || [scanner scanString: @"/" intoString: 0] == NO) + { + 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; + } + } + + /* + * Store the Raw header name and a lowercase version too. + */ + name = [name stringByTrimmingTailSpaces]; + [info setObject: name forKey: @"BaseName"]; + name = [name lowercaseString]; + [info setObject: name forKey: @"Name"]; + + skip = RETAIN([scanner charactersToBeSkipped]); + [scanner setCharactersToBeSkipped: nil]; + [scanner scanCharactersFromSet: skip intoString: 0]; + [scanner setCharactersToBeSkipped: skip]; + RELEASE(skip); + + /* + * Set remainder of header as a base value. + */ + [info setObject: [[scanner string] substringFromIndex: [scanner scanLocation]] + forKey: @"BaseValue"]; + + /* + * Break header fields out into info dictionary. + */ + if ([self scanHeader: scanner named: name into: info] == NO) + { + return NO; + } + + /* + * Check validity of broken-out header fields. + */ + if ([name isEqualToString: @"mime-version"] == YES) + { + int majv = 0; + int minv = 0; + + value = [info objectForKey: @"BaseValue"]; + if ([value length] == 0) + { + NSLog(@"Missing value for mime-version header"); + return NO; + } + if (sscanf([value lossyCString], "%d.%d", &majv, &minv) != 2) + { + NSLog(@"Bad value for mime-version header"); + return NO; + } + [document deleteHeaderNamed: name]; // Should be unique + } + else if ([name isEqualToString: @"content-type"] == YES) + { + NSString *type; + NSString *subtype; + BOOL supported = NO; + + DESTROY(boundary); + type = [info objectForKey: @"Type"]; + if ([type length] == 0) + { + NSLog(@"Missing Mime content-type"); + return NO; + } + subtype = [info objectForKey: @"SubType"]; + + if ([type isEqualToString: @"text"] == YES) + { + if (subtype == nil) + subtype = @"plain"; + } + else if ([type isEqualToString: @"application"] == YES) + { + if (subtype == nil) + subtype = @"octet-stream"; + } + else if ([type isEqualToString: @"multipart"] == YES) + { + NSDictionary *par = [info objectForKey: @"Parameters"]; + NSString *tmp = [par objectForKey: @"boundary"]; + + supported = YES; + if (tmp != nil) + { + unsigned int l = [tmp cStringLength] + 2; + unsigned char *b = NSZoneMalloc(NSDefaultMallocZone(), l + 1); + + b[0] = '-'; + b[1] = '-'; + [tmp getCString: &b[2]]; + ASSIGN(boundary, [NSData dataWithBytesNoCopy: b length: l]); + } + else + { + NSLog(@"multipart message without boundary"); + return NO; + } + } + + [document deleteHeaderNamed: name]; // Should be unique + } + + /* + * Ensure that info dictionary is immutable by making a copy + * of all keys and objects and placing them in a new dictionary. + */ + count = [info count]; + { + id keys[count]; + id objects[count]; + unsigned index; + + [[info allKeys] getObjects: keys]; + for (index = 0; index < count; index++) + { + keys[index] = [keys[index] copy]; + objects[index] = [[info objectForKey: keys[index]] copy]; + } + info = [NSDictionary dictionaryWithObjects: objects + forKeys: keys + count: count]; + for (index = 0; index < count; index++) + { + RELEASE(objects[index]); + RELEASE(keys[index]); + } + } + + return [document addHeader: info]; +} + +/** + * Returns YES if the parser is expecting to read mime headers, + * Returns NO is the parser has already been passed all the + * data containing headers, and is now waiting for the body of + * the mime message (or has been passed all data). + */ +- (BOOL) parsedHeaders +{ + return inBody; +} + +/** + *

+ * This method is called to parse a header line and split its + * contents into an info dictionary. + *

+ *

+ * On entry, the dictionary is already partially filled, + * the name argument is a lowercase representation of the + * header name, and the scanner is set to a scan location + * immediately after the colon in the header string. + *

+ *

+ * If the header is parsed successfully, the method should + * return YES, otherwise NO. + *

+ *

+ * You should not call this method directly yourself, but may + * override it to support parsing of new headers. + *

+ *

+ * You should be aware of the parsing that the standard + * implementation performs, and that needs to be + * done for certain headers in order to permit the parser to + * work generally - + *

+ * + * content-disposition + * + * + * Parameters + * + * A dictionary containing parameters as key-value pairs + * in lowercase + * + * Value + * + * The content disposition (excluding parameters) as a + * lowercase string. + * + * + * + * content-type + * + * + * Parameters + * + * A dictionary containing parameters as key-value pairs + * in lowercase. + * + * SubType + * The MIME subtype lowercase + * Type + * The MIME type lowercase + * value + * The full MIME type (xxx/yyy) in lowercase + * + * + * content-transfer-encoding + * + * + * Value + * The transfer encoding type in lowercase + * + * + * http + * + * + * HttpVersion + * The HTTP protocol version number + * HttpMajorVersion + * The first component of the version number + * HttpMinorVersion + * The second component of the version number + * HttpStatus + * The response status value (numeric code) + * Value + * The text message (if any) + * + * + * transfer-encoding + * + * + * Value + * The transfer encoding type in lowercase + * + * + * + */ +- (BOOL) scanHeader: (NSScanner*)scanner + named: (NSString*)name + into: (NSMutableDictionary*)info +{ + NSString *value = nil; + NSMutableDictionary *parameters = nil; + + /* + * Now see if we are interested in any of it. + */ + if ([name isEqualToString: @"http"] == YES) + { + int major; + int minor; + int status; + unsigned count; + NSArray *hdrs; + + if ([scanner scanInt: &major] == NO || major < 0) + { + NSLog(@"Bad value for http major version"); + return NO; + } + if ([scanner scanString: @"." intoString: 0] == NO) + { + NSLog(@"Bad format for http version"); + return NO; + } + if ([scanner scanInt: &minor] == NO || minor < 0) + { + NSLog(@"Bad value for http minor version"); + return NO; + } + if ([scanner scanInt: &status] == NO || status < 0) + { + NSLog(@"Bad value for http status"); + return NO; + } + [info setObject: [NSString stringWithFormat: @"%d", minor] + forKey: @"HttpMinorVersion"]; + [info setObject: [NSString stringWithFormat: @"%d.%d", major, minor] + forKey: @"HttpVersion"]; + [info setObject: [NSString stringWithFormat: @"%d", major] + forKey: NSHTTPPropertyServerHTTPVersionKey]; + [info setObject: [NSString stringWithFormat: @"%d", status] + forKey: NSHTTPPropertyStatusCodeKey]; + [self scanPastSpace: scanner]; + value = [[scanner string] substringFromIndex: [scanner scanLocation]]; + [info setObject: value + forKey: NSHTTPPropertyStatusReasonKey]; + value = nil; + /* + * Get rid of preceeding headers in case this is a continuation. + */ + hdrs = [document allHeaders]; + for (count = 0; count < [hdrs count]; count++) + { + NSDictionary *h = [hdrs objectAtIndex: count]; + + [document deleteHeader: [h objectForKey: @"RawHeader"]]; + } + } + else if ([name isEqualToString: @"content-transfer-encoding"] == YES + || [name isEqualToString: @"transfer-encoding"] == YES) + { + value = [self scanToken: scanner]; + if ([value length] == 0) + { + NSLog(@"Bad value for content-transfer-encoding header"); + return NO; + } + value = [value lowercaseString]; + } + else if ([name isEqualToString: @"content-type"] == YES) + { + NSString *type; + NSString *subtype = nil; + + type = [self scanToken: scanner]; + if ([type length] == 0) + { + NSLog(@"Invalid Mime content-type"); + return NO; + } + type = [type lowercaseString]; + [info setObject: type forKey: @"Type"]; + if ([scanner scanString: @"/" intoString: 0] == YES) + { + subtype = [self scanToken: scanner]; + if ([subtype length] == 0) + { + NSLog(@"Invalid Mime content-type (subtype)"); + return NO; + } + subtype = [subtype lowercaseString]; + [info setObject: subtype forKey: @"SubType"]; + value = [NSString stringWithFormat: @"%@/%@", type, subtype]; + } + else + { + value = type; + } + + while ([scanner scanString: @";" intoString: 0] == YES) + { + NSString *paramName; + + paramName = [self scanToken: scanner]; + if ([paramName length] == 0) + { + NSLog(@"Invalid Mime content-type (parameter name)"); + return NO; + } + if ([scanner scanString: @"=" intoString: 0] == YES) + { + NSString *paramValue; + + paramValue = [self scanToken: scanner]; + if (paramValue == nil) + { + paramValue = @""; + } + if (parameters == nil) + { + parameters = [NSMutableDictionary dictionary]; + } + paramName = [paramName lowercaseString]; + [parameters setObject: paramValue forKey: paramName]; + } + else + { + NSLog(@"Ignoring Mime content-type parameter (%@)", paramName); + } + } + } + else if ([name isEqualToString: @"content-disposition"] == YES) + { + value = [self scanToken: scanner]; + value = [value lowercaseString]; + /* + * Concatenate slash separated parts of field. + */ + while ([scanner scanString: @"/" intoString: 0] == YES) + { + NSString *sub = [self scanToken: scanner]; + + if ([sub length] > 0) + { + sub = [sub lowercaseString]; + value = [NSString stringWithFormat: @"%@/%@", value, sub]; + } + } + + /* + * Expect anything else to be 'name=value' parameters. + */ + while ([scanner scanString: @";" intoString: 0] == YES) + { + NSString *paramName; + + paramName = [self scanToken: scanner]; + if ([paramName length] == 0) + { + NSLog(@"Invalid Mime content-type (parameter name)"); + return NO; + } + if ([scanner scanString: @"=" intoString: 0] == YES) + { + NSString *paramValue; + + paramValue = [self scanToken: scanner]; + if (paramValue == nil) + { + paramValue = @""; + } + if (parameters == nil) + { + parameters = [NSMutableDictionary dictionary]; + } + paramName = [paramName lowercaseString]; + [parameters setObject: paramValue forKey: paramName]; + } + else + { + NSLog(@"Ignoring Mime content-disposition parameter (%@)", + paramName); + } + } + } + + if (value != nil) + { + [info setObject: value forKey: @"Value"]; + } + if (parameters != nil) + { + [info setObject: parameters forKey: @"Parameters"]; + } + return YES; +} + +/** + * 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]; + scanned = [scanner scanCharactersFromSet: skip intoString: 0]; + [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 +{ + unsigned location; + unichar c; + + [self scanPastSpace: scanner]; + + /* + * Now return token delimiter (may be whitespace) + */ + location = [scanner scanLocation]; + c = [[scanner string] characterAtIndex: location]; + + if ([specials characterIsMember: c] == YES) + { + [scanner setScanLocation: location + 1]; + return [NSString stringWithCharacters: &c length: 1]; + } + 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. + * + * Leading whitespace is ignored. + * Backslash escapes in quoted text are converted + * + */ +- (NSString*) scanToken: (NSScanner*)scanner +{ + if ([scanner scanString: @"\"" intoString: 0] == YES) // Quoted + { + NSString *string = [scanner string]; + unsigned length = [string length]; + unsigned start = [scanner scanLocation]; + 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] == '\\') + { + r.location++; + r.length = length - r.location; + } + else + { + done = YES; + } + } + [scanner setScanLocation: r.length + 1]; + length = r.location - start; + if (length == 0) + { + return nil; + } + else + { + unichar buf[length]; + unichar *src = buf; + unichar *dst = buf; + + [string getCharacters: buf range: NSMakeRange(start, length)]; + while (src < &buf[length]) + { + if (*src == '\\') + { + src++; + } + *dst++ = *src++; + } + return [NSString stringWithCharacters: buf length: dst - buf]; + } + } + else // Token + { + NSCharacterSet *skip; + NSString *value; + + /* + * Move past white space. + */ + skip = RETAIN([scanner charactersToBeSkipped]); + [scanner setCharactersToBeSkipped: nil]; + [scanner scanCharactersFromSet: skip intoString: 0]; + [scanner setCharactersToBeSkipped: skip]; + RELEASE(skip); + + /* + * Scan value terminated by any special character. + */ + if ([scanner scanUpToCharactersFromSet: specials + intoString: &value] == NO) + { + return nil; + } + return value; + } +} + +@end + +@implementation GSMimeParser (Private) +/* + * 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. + */ +- (NSString*) _decodeHeader +{ + NSStringEncoding charset; + WE encoding; + unsigned char c; + unsigned char *src, *dst, *beg; + NSMutableString *hdr = [NSMutableString string]; + CREATE_AUTORELEASE_POOL(arp); + + /* + * Remove any leading or trailing space - there shouldn't be any. + */ + while (lineStart < lineEnd && isspace(bytes[lineStart])) + { + lineStart++; + } + while (lineEnd > lineStart && isspace(bytes[lineEnd-1])) + { + lineEnd--; + } + + /* + * Perform quoted text substitution. + */ + bytes[lineEnd] = '\0'; + dst = src = beg = &bytes[lineStart]; + while (*src != 0) + { + if ((src[0] == '=') && (src[1] == '?')) + { + unsigned char *tmp; + + if (dst > beg) + { + NSData *d = [NSData dataWithBytes: beg length: dst - beg]; + NSString *s; + + s = [[NSString alloc] initWithData: d + encoding: NSASCIIStringEncoding]; + [hdr appendString: s]; + RELEASE(s); + dst = beg; + } + + if (src[3] == '\0') + { + dst[0] = '='; + dst[1] = '?'; + dst[2] = '\0'; + NSLog(@"Bad encoded word - character set missing"); + break; + } + + src += 2; + tmp = src; + src = (unsigned char*)strchr((char*)src, '?'); + if (src == 0) + { + NSLog(@"Bad encoded word - character set terminator missing"); + break; + } + *src = '\0'; + charset = parseCharacterSet([NSString stringWithCString: tmp]); + src++; + if (*src == 0) + { + NSLog(@"Bad encoded word - content type missing"); + break; + } + c = tolower(*src); + if (c == 'b') + { + encoding = WE_BASE64; + } + else if (c == 'q') + { + encoding = WE_QUOTED; + } + else + { + NSLog(@"Bad encoded word - content type unknown"); + break; + } + src = (unsigned char*)strchr((char*)src, '?'); + if (src == 0) + { + NSLog(@"Bad encoded word - content type terminator missing"); + break; + } + src++; + if (*src == 0) + { + NSLog(@"Bad encoded word - data missing"); + break; + } + tmp = (unsigned char*)strchr((char*)src, '?'); + if (tmp == 0) + { + NSLog(@"Bad encoded word - data terminator missing"); + break; + } + dst = decodeWord(dst, src, tmp, encoding); + tmp++; + if (*tmp != '=') + { + NSLog(@"Bad encoded word - encoded word terminator missing"); + break; + } + src = tmp; + if (dst > beg) + { + NSData *d = [NSData dataWithBytes: beg length: dst - beg]; + NSString *s; + + s = [[NSString alloc] initWithData: d + encoding: charset]; + [hdr appendString: s]; + RELEASE(s); + dst = beg; + } + } + else + { + *dst++ = *src; + } + src++; + } + if (dst > beg) + { + NSData *d = [NSData dataWithBytes: beg length: dst - beg]; + NSString *s; + + s = [[NSString alloc] initWithData: d + encoding: NSASCIIStringEncoding]; + [hdr appendString: s]; + RELEASE(s); + dst = beg; + } + RELEASE(arp); + return hdr; +} + +- (BOOL) _decodeBody: (NSData*)d +{ + unsigned l = [d length]; + BOOL result = NO; + + rawBodyLength += l; + + if (context == nil) + { + NSDictionary *hdr; + + expect = 0; + /* + * Check for expected content length. + */ + hdr = [document headerNamed: @"content-length"]; + if (hdr != nil) + { + expect = [[hdr objectForKey: @"BaseValue"] intValue]; + } + + /* + * Set up context for decoding data. + */ + hdr = [document headerNamed: @"transfer-encoding"]; + if (hdr == nil) + { + hdr = [document headerNamed: @"content-transfer-encoding"]; + } + else if ([[hdr objectForKey: @"Value"] isEqual: @"chunked"] == YES) + { + /* + * Chunked transfer encoding overrides any content length spec. + */ + expect = 0; + } + context = [self contextFor: hdr]; + RETAIN(context); + NSDebugMLLog(@"GSMime", @"Parse body expects %u bytes", expect); + } + + NSDebugMLLog(@"GSMime", @"Parse %u bytes - '%*.*s'", l, l, l, [d bytes]); + + if ([context atEnd] == YES) + { + inBody = NO; + complete = YES; + if ([d length] > 0) + { + NSLog(@"Additional data (%*.*s) ignored after parse complete", + [d length], [d length], [d bytes]); + } + result = YES; /* Nothing more to do */ + } + else if (boundary == nil) + { + NSDictionary *typeInfo; + NSString *type; + + typeInfo = [document headerNamed: @"content-type"]; + type = [typeInfo objectForKey: @"Type"]; + if ([type isEqualToString: @"multipart"] == YES) + { + NSLog(@"multipart decode attempt without boundary"); + inBody = NO; + complete = YES; + result = NO; + } + else + { + [self decodeData: d + fromRange: NSMakeRange(0, [d length]) + intoData: data + withContext: context]; + + if ([context atEnd] == YES + || (expect > 0 && rawBodyLength >= expect)) + { + inBody = NO; + complete = YES; + + NSDebugMLLog(@"GSMime", @"Parse body complete"); + /* + * If no content type is supplied, we assume text. + */ + if (type == nil || [type isEqualToString: @"text"] == YES) + { + NSDictionary *params; + NSString *charset; + NSStringEncoding stringEncoding; + NSString *string; + + /* + * Assume that content type is best represented as NSString. + */ + params = [typeInfo objectForKey: @"Parameters"]; + charset = [params objectForKey: @"charset"]; + stringEncoding = parseCharacterSet(charset); + string = [[NSString alloc] initWithData: data + encoding: stringEncoding]; + [document setContent: string]; + RELEASE(string); + } + else + { + /* + * Assume that any non-text content type is best + * represented as NSData. + */ + [document setContent: AUTORELEASE([data copy])]; + } + } + result = YES; + } + } + else + { + unsigned int bLength = [boundary length]; + unsigned char *bBytes = (unsigned char*)[boundary bytes]; + unsigned char bInit = bBytes[0]; + BOOL done = NO; + + [data appendBytes: [d bytes] length: [d length]]; + bytes = (unsigned char*)[data mutableBytes]; + dataEnd = [data length]; + + while (done == NO) + { + /* + * Search our data for the next boundary. + */ + while (dataEnd - lineStart >= bLength) + { + if (bytes[lineStart] == bInit + && memcmp(&bytes[lineStart], bBytes, bLength) == 0) + { + if (lineStart == 0 || bytes[lineStart-1] == '\r' + || bytes[lineStart-1] == '\n') + { + lineEnd = lineStart + bLength; + break; + } + } + lineStart++; + } + if (dataEnd - lineStart < bLength) + { + done = YES; /* Needs more data. */ + } + else if (child == nil) + { + /* + * Found boundary at the start of the first section. + * Set sectionStart to point immediately after boundary. + */ + lineStart += bLength; + sectionStart = lineStart; + child = [GSMimeParser new]; + } + else + { + NSData *d; + BOOL endedFinalPart = NO; + + /* + * 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. + */ + if (bytes[sectionStart] == '-' && sectionStart < dataEnd + && bytes[sectionStart+1] == '-') + { + sectionStart += 2; + endedFinalPart = YES; + } + if (bytes[sectionStart] == '\r') + sectionStart++; + if (bytes[sectionStart] == '\n') + sectionStart++; + + /* + * Create data object for this section and pass it to the + * child parser to deal with. + */ + d = [NSData dataWithBytes: &bytes[sectionStart] + length: lineStart - sectionStart]; + if ([child parse: d] == YES || [child parse: nil] == YES) + { + NSMutableArray *a; + GSMimeDocument *doc; + + /* + * Store the document produced by the child, and + * create a new parser for the next section. + */ + a = [document content]; + if (a == nil) + { + a = [NSMutableArray new]; + [document setContent: a]; + RELEASE(a); + } + doc = [child document]; + if (doc != nil) + { + [a addObject: doc]; + } + RELEASE(child); + child = [GSMimeParser new]; + } + else + { + /* + * Section failed to decode properly! + */ + NSLog(@"Failed to decode section of multipart"); + RELEASE(child); + child = [GSMimeParser new]; + } + + /* + * Update parser data. + */ + lineStart += bLength; + sectionStart = lineStart; + memcpy(bytes, &bytes[sectionStart], dataEnd - sectionStart); + dataEnd -= sectionStart; + [data setLength: dataEnd]; + bytes = (unsigned char*)[data mutableBytes]; + lineStart -= sectionStart; + sectionStart = 0; + } + } + /* + * Check to see if we have reached content length. + */ + if (expect > 0 && rawBodyLength >= expect) + { + complete = YES; + inBody = NO; + } + result = YES; + } + return result; +} + +- (BOOL) _unfoldHeader +{ + char c; + BOOL unwrappingComplete = NO; + + lineStart = lineEnd; + NSDebugMLLog(@"GSMime", @"entry: input:%u dataEnd:%u lineStart:%u '%*.*s'", + input, dataEnd, lineStart, dataEnd - input, dataEnd - input, &bytes[input]); + /* + * RFC822 lets header fields break across lines, with continuation + * lines beginning with whitespace. This is called folding - and the + * first thing we need to do is unfold any folded lines into a single + * unfolded line (lineStart to lineEnd). + */ + while (input < dataEnd && unwrappingComplete == NO) + { + unsigned pos = input; + + if ((c = bytes[pos]) != '\r' && c != '\n') + { + while (pos < dataEnd && (c = bytes[pos]) != '\r' && c != '\n') + { + pos++; + } + if (pos == dataEnd) + { + break; /* need more data */ + } + pos++; + if (c == '\r' && pos < dataEnd && bytes[pos] == '\n') + { + pos++; + } + if (pos == dataEnd) + { + break; /* need more data */ + } + /* + * Copy data up to end of line, and skip past end. + */ + while (input < dataEnd && (c = bytes[input]) != '\r' && c != '\n') + { + bytes[lineEnd++] = bytes[input++]; + } + } + + /* + * Eat a newline that is part of a cr-lf sequence. + */ + input++; + if (c == '\r' && input < dataEnd && bytes[input] == '\n') + { + input++; + } + + /* + * See if we have a wrapped line. + */ + if ((c = bytes[input]) == '\r' || c == '\n' || isspace(c) == 0) + { + unwrappingComplete = YES; + bytes[lineEnd] = '\0'; + /* + * If this is a zero-length line, we have reached the end of + * the headers. + */ + if (lineEnd == lineStart) + { + unsigned lengthRemaining; + + /* + * Overwrite the header data with the body, ready to start + * parsing the body data. + */ + lengthRemaining = dataEnd - input; + if (lengthRemaining > 0) + { + memcpy(bytes, &bytes[input], lengthRemaining); + } + dataEnd = lengthRemaining; + [data setLength: lengthRemaining]; + bytes = (unsigned char*)[data mutableBytes]; + sectionStart = 0; + lineStart = 0; + lineEnd = 0; + input = 0; + inBody = YES; + } + } + } + NSDebugMLLog(@"GSMime", @"exit: inBody:%d unwrappingComplete: %d " + @"input:%u dataEnd:%u lineStart:%u '%*.*s'", inBody, unwrappingComplete, + input, dataEnd, lineStart, dataEnd - input, dataEnd - input, &bytes[input]); + return unwrappingComplete; +} +@end + + + +/** + *

+ * 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. + *

+ *

+ * The class keeps track of all the document headers, and provides + * methods for modifying the headers that apply to a document and + * for looking at the header structures, by providing an info + * dictionary containing the various parts of a header. + *

+ *

+ * The common dictionary keys used for elements provided for + * all headers are - + *

+ * + * RawHeader + * This is the unmodified text of the header + * + * BaseName + * This is the header name. + * + * BaseValue + * This is the text after the header name and colon. + * + * Name + * This is a lowercase representation of the header name. + * + * Value + * This is the value of the header (normally lower case). + * It may only be a small subset of the information in the header + * with other information being split into separate fields + * depending on the type of header. + * + * + */ +@implementation GSMimeDocument + ++ (void) initialize +{ + if (self == [GSMimeDocument class]) + { + NSMutableCharacterSet *m = [[NSMutableCharacterSet alloc] init]; + + [m formUnionWithCharacterSet: + [NSCharacterSet characterSetWithCharactersInString: + @"()<>@,;:/[]?=\"\\"]]; + [m formUnionWithCharacterSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [m formUnionWithCharacterSet: + [NSCharacterSet controlCharacterSet]]; + [m formUnionWithCharacterSet: + [NSCharacterSet illegalCharacterSet]]; + specials = [m copy]; + } +} + +/** + * Create an empty MIME document. + */ ++ (GSMimeDocument*) mimeDocument +{ + return AUTORELEASE([[self alloc] init]); +} + +/** + *

+ * 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. + *

+ */ +- (BOOL) addHeader: (NSDictionary*)info +{ + NSString *name = [info objectForKey: @"Name"]; + + if (name == nil) + { + NSLog(@"addHeader: supplied with header info without 'Name' field"); + return NO; + } + + info = [info copy]; + [headers addObject: info]; + RELEASE(info); + return YES; +} + +/** + *

+ * This method returns an array containing NSDictionary objects + * representing the headers associated with the document. + *

+ *

+ * The order of the headers in the array is the order of the + * headers in the document. + *

+ */ +- (NSArray*) allHeaders +{ + return [NSArray arrayWithArray: headers]; +} + +/** + * This returns the content data of the document in the + * appropriate format for the type of data - + * + * text + * an NSString object + * binary + * an NSData object + * multipart + * an NSArray object containing GSMimeDocument objects + * + */ +- (id) content +{ + return content; +} + +- (id) copyWithZone: (NSZone*)z +{ + return RETAIN(self); +} + +- (void) dealloc +{ + RELEASE(headers); + RELEASE(content); + [super dealloc]; +} + +/** + * This method removes all occurrances of headers whose raw data + * exactly matches the supplied string. + */ +- (void) deleteHeader: (NSString*)aHeader +{ + unsigned count = [headers count]; + + while (count-- > 0) + { + NSDictionary *info = [headers objectAtIndex: count]; + + if ([aHeader isEqualToString: [info objectForKey: @"RawHeader"]] == YES) + { + [headers removeObjectAtIndex: count]; + } + } +} + +/** + * This method removes all occurrances of headers whose name + * exactly matches the supplied string. + */ +- (void) deleteHeaderNamed: (NSString*)name +{ + unsigned count = [headers count]; + + name = [name lowercaseString]; + while (count-- > 0) + { + NSDictionary *info = [headers objectAtIndex: count]; + + if ([name isEqualToString: [info objectForKey: @"Name"]] == YES) + { + [headers removeObjectAtIndex: count]; + } + } +} + +- (NSString*) description +{ + NSMutableString *desc; + NSDictionary *locale; + + desc = [NSMutableString stringWithFormat: @"GSMimeDocument <%0x> -\n", self]; + locale = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; + [desc appendString: [headers descriptionWithLocale: locale]]; + [desc appendFormat: @"\nDocument content -\n%@", content]; + return desc; +} + +/** + * This method returns the info dictionary for the first header + * whose name equals the supplied argument. + */ +- (NSDictionary*) headerNamed: (NSString*)name +{ + unsigned count = [headers count]; + unsigned index; + + name = [name lowercaseString]; + for (index = 0; index < count; index++) + { + NSDictionary *info = [headers objectAtIndex: index]; + NSString *other = [info objectForKey: @"Name"]; + + if ([name isEqualToString: other] == YES) + { + return info; + } + } + return nil; +} + +/** + * This method returns an array of info dictionaries for all headers + * whose names equal the supplied argument. + */ +- (NSArray*) headersNamed: (NSString*)name +{ + unsigned count = [headers count]; + unsigned index; + NSMutableArray *array; + + name = [name lowercaseString]; + array = [NSMutableArray array]; + for (index = 0; index < count; index++) + { + NSDictionary *info = [headers objectAtIndex: index]; + NSString *other = [info objectForKey: @"Name"]; + + if ([name isEqualToString: other] == YES) + { + [array addObject: info]; + } + } + return array; +} + +- (id) init +{ + if ((self = [super init]) != nil) + { + headers = [NSMutableArray new]; + } + return self; +} + +/** + * Sets a new value for the content of the document. + */ +- (BOOL) setContent: (id)newContent +{ + ASSIGN(content, newContent); + return YES; +} + +/** + * 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. + */ +- (BOOL) setHeader: (NSDictionary*)info +{ + NSString *name = [info objectForKey: @"Name"]; + unsigned count = [headers count]; + + if (name == nil) + { + NSLog(@"setHeader: supplied with header info without 'Name' field"); + return NO; + } + + /* + * Remove any existing headers with this name. + */ + while (count-- > 0) + { + NSDictionary *tmp = [headers objectAtIndex: count]; + + if ([name isEqualToString: [tmp objectForKey: @"Name"]] == YES) + { + [headers removeObjectAtIndex: count]; + } + } + + return [self addHeader: info]; +} + +@end + diff --git a/Source/Additions/GSXML.m b/Source/Additions/GSXML.m new file mode 100644 index 000000000..83d676a4c --- /dev/null +++ b/Source/Additions/GSXML.m @@ -0,0 +1,3012 @@ +/** Implementation for GSXML classes + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + + Written by: Michael Pakhantsov on behalf of + Brainstorm computer solutions. + Date: Jule 2000 + + Integration/updates/rewrites by: Richard Frith-Macdonald + Date: Sep2000,Dec2001/Jan2002 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + The XML and HTML parsing system + + The XML and HTML parsing system +

+ The GNUstep XML parser is a collection Objective-C classes + wrapping the C XML parsing library (libxml). +

+

+ The underlying C library handles high performance parsing, while + the ObjectiveC classes provide ease of use/integration. +

+
+*/ + +#include + +#ifdef HAVE_LIBXML + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libxml headers */ +#include +#include +#include +#include +#include +#include +#include + +extern int xmlDoValidityCheckingDefaultValue; +extern int xmlGetWarningsDefaultValue; + +/* + * optimization + * + */ +static Class NSString_class; +static IMP usImp; +static SEL usSel; + +inline static NSString* +UTF8Str(const char *bytes) +{ + return (*usImp)(NSString_class, usSel, bytes); +} + +inline static NSString* +UTF8StrLen(const char *bytes, unsigned length) +{ + char *buf = NSZoneMalloc(NSDefaultMallocZone(), length+1); + NSString *str; + + memcpy(buf, bytes, length); + buf[length] = '\0'; + str = UTF8Str(buf); + NSZoneFree(NSDefaultMallocZone(), buf); + return str; +} + +static BOOL cacheDone = NO; + +static void +setupCache() +{ + if (cacheDone == NO) + { + cacheDone = YES; + NSString_class = [NSString class]; + usSel = @selector(stringWithUTF8String:); + usImp = [NSString_class methodForSelector: usSel]; + } +} + +static xmlParserInputPtr +loadEntityFunction(const char *url, const char *eid, xmlParserCtxtPtr *ctxt); + +/* Internal interfaces */ + +@interface GSXMLNamespace (GSPrivate) +- (void) _native: (BOOL)value; +@end + +@interface GSXMLNode (GSPrivate) +- (void) _native: (BOOL)value; +@end + +@interface GSXMLParser (Private) +- (BOOL) _initLibXML; +- (void) _parseChunk: (NSData*)data; +@end + +@interface GSSAXHandler (Private) +- (BOOL) _initLibXML; +- (void) _setParser: (GSXMLParser*)value; +@end + + +@implementation GSXMLDocument : NSObject + +/** + * Return document created using raw libxml data. + * The resulting document does not 'own' the data, and will not free it. + */ ++ (GSXMLDocument*) documentFrom: (void*)data +{ + return AUTORELEASE([[self alloc] initFrom: data]); +} + +/** + * Create a new document with the specified version. + * + * id d = [GSXMLDocument documentWithVersion: @"1.0"]; + * + * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; + * [[d root] setProp: @"version" value: @"0.9"]; + * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; + * [n1 makeComment: @" this is a comment "]; + * [n1 makePI: @"pi1" content: @"this is a process instruction"]; + * [n1 makeChildWithNamespace: nil name: @"key" content: @"Year Of Birth"]; + * [n1 makeChildWithNamespace: nil name: @"integer" content: @"65"]; + * [n1 makeChildWithnamespace: nil name: @"key" content: @"Pets Names"]; + * + */ ++ (GSXMLDocument*) documentWithVersion: (NSString*)version +{ + return AUTORELEASE([[self alloc] initWithVersion: version]); +} + ++ (void) initialize +{ + if (cacheDone == NO) + setupCache(); +} + +- (id) copyWithZone: (NSZone*)z +{ + return RETAIN(self); +} + +- (void) dealloc +{ + if ((native) && lib != NULL) + { + xmlFreeDoc(lib); + } + [super dealloc]; +} + +/** + * Returns a string representation of the document (ie the XML) + * or nil if the document does not have reasonable contents. + */ +- (NSString*) description +{ + NSString *string = nil; + xmlChar *buf = NULL; + int length; + + xmlDocDumpMemory(lib, &buf, &length); + + if (buf != 0 && length > 0) + { + string = [NSString_class stringWithCString: buf length: length]; + xmlFree(buf); + } + return string; +} + +/** + * Returns the name of the encoding for this document. + */ +- (NSString*) encoding +{ + return [NSString_class stringWithCString: ((xmlDocPtr)(lib))->encoding]; +} + +- (unsigned) hash +{ + return (unsigned)lib; +} + +- (id) init +{ + NSLog(@"GSXMLDocument: calling -init is not legal"); + RELEASE(self); + return nil; +} + +/** + * + * Initialise a new document object using raw libxml data. + * The resulting document does not 'own' the data, and will not free it. + */ +- (id) initFrom: (void*)data +{ + self = [super init]; + if (self != nil) + { + if (data == NULL) + { + NSLog(@"%@ - no data for initialization", + NSStringFromClass([self class])); + DESTROY(self); + return nil; + } + lib = data; + native = NO; + } + return self; +} + +/** + * Initialise a new document with the specified version.
+ * Generates the raw data and passes it to -initFrom: to + * perform basic initialisation, then takes ownership of + * of the underlying data so it will be freed when this + * object is deallocated. + */ +- (id) initWithVersion: (NSString*)version +{ + void *data = xmlNewDoc([version lossyCString]); + + if (data == 0) + { + NSLog(@"Can't create GSXMLDocument object"); + DESTROY(self); + } + else if ((self = [self initFrom: data]) != nil) + { + native = YES; + } + return self; +} + +- (BOOL) isEqualTo: (id)other +{ + if ([other isKindOfClass: [self class]] == YES + && [other lib] == lib) + { + return YES; + } + else + { + return NO; + } +} + +/** + * Returns a pointer to the raw libxml data used by this document. + */ +- (void*) lib +{ + return lib; +} + +/** + * Creates a new node within the document. + * + * GSXMLNode *n1, *n2; + * GSXMLDocument *d; + * + * d = [GSXMLDocument documentWithVersion: @"1.0"]; + * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; + * [[d root] setProp: @"version" value: @"0.9"]; + * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; + * + */ +- (GSXMLNode*) makeNodeWithNamespace: (GSXMLNamespace*)ns + name: (NSString*)name + content: (NSString*)content +{ + return [GSXMLNode nodeFrom: + xmlNewDocNode(lib, [ns lib], [name lossyCString], + [content lossyCString])]; +} + +/** + * Returns the root node of the document. + */ +- (GSXMLNode*) root +{ + return [GSXMLNode nodeFrom: xmlDocGetRootElement(lib)]; +} + +/** + * Sets the root node of the document. This takes ownership of the + * underlying data in the supplied node.
+ * returns the old root of the document (or nil). + */ +- (GSXMLNode*) setRoot: (GSXMLNode*)node +{ + void *nodeLib = [node lib]; + void *oldRoot = xmlDocSetRootElement(lib, nodeLib); + + [node _native: NO]; + return oldRoot == NULL ? nil : [GSXMLNode nodeFrom: nodeLib]; +} + +/** + * Returns the version string for this document. + */ +- (NSString*) version +{ + return [NSString_class stringWithCString: ((xmlDocPtr)(lib))->version]; +} + +/** + * Uses the -description method to produce a string representation of + * the document and writes that to filename. + */ +- (BOOL) writeToFile: (NSString*)filename atomically: (BOOL)useAuxilliaryFile +{ + NSString *s = [self description]; + + if (s == nil) + { + return NO; + } + return [s writeToFile: filename atomically: useAuxilliaryFile]; +} + +/** + * Uses the -description method to produce a string representation of + * the document and writes that to url. + */ +- (BOOL) writeToURL: (NSURL*)url atomically: (BOOL)useAuxilliaryFile +{ + NSString *s = [self description]; + + if (s == nil) + { + return NO; + } + return [s writeToURL: url atomically: useAuxilliaryFile]; +} + +@end + +@implementation GSXMLNamespace : NSObject + +static NSMapTable *nsNames = 0; + +/** + * Return the string representation of the specified numeric type. + */ ++ (NSString*) descriptionFromType: (int)type +{ + NSString *desc = (NSString*)NSMapGet(nsNames, (void*)[self type]); + + return desc; +} + ++ (void) initialize +{ + if (self == [GSXMLNamespace class]) + { + if (cacheDone == NO) + setupCache(); + nsNames = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonRetainedObjectMapValueCallBacks, 0); + NSMapInsert(nsNames, + (void*)XML_LOCAL_NAMESPACE, (void*)@"XML_LOCAL_NAMESPACE"); + } +} + +/** + * Create a namespace from raw libxml data + */ ++ (GSXMLNamespace*) namespaceFrom: (void*)data +{ + return AUTORELEASE([[self alloc] initFrom: data]); +} + +/** + * Creates a new Namespace.
+ * This method will refuse to create a namespace + * with the same prefix as an existing one present + * in this node. + * + * .... + * GSXMLNamespace *ns1, *ns2; + * GSXMLNode *node1, *node2; + * NSString *prefix = @"mac-os-property"; + * NSString *href = @"http://www.gnustep.org/some/location"; + * + * ns = [GSXMLNamespace namespaceWithNode: nil + * href: href + * prefix: prefix]; + * node1 = [GSXMLNode nodeWithNamespace: ns name: @"node1"]; + * + * node2 = [GSXMLNode nodeWithNamespace: nil name: @"node2"]; + * ns2 = [GSXMLNamespace namespaceWithNode: node2 + * href: href + * prefix: prefix]; + * + * Result: + * + * node1 <mac-os-property:node1/> + * node2 <node2 xmlns="mac-os-property"/> + * + */ ++ (GSXMLNamespace*) namespaceWithNode: (GSXMLNode*)node + href: (NSString*)href + prefix: (NSString*)prefix +{ + return AUTORELEASE([[self alloc] initWithNode: node + href: href + prefix: prefix]); +} + +/** + * Return the numeric constant value for the namespace + * type named. This method is inefficient, so the returned + * value should be saved for re-use later. The possible + * values are - + * + * XML_LOCAL_NAMESPACE + * + */ ++ (int) typeFromDescription: (NSString*)desc +{ + NSMapEnumerator enumerator; + NSString *val; + int key; + + enumerator = NSEnumerateMapTable(nsNames); + while (NSNextMapEnumeratorPair(&enumerator, (void**)&key, (void**)&val)) + { + if ([desc isEqual: val] == YES) + { + return key; + } + } + return -1; +} + +- (id) copyWithZone: (NSZone*)z +{ + return RETAIN(self); +} + +- (void) dealloc +{ + if (native == YES && lib != NULL) + { + xmlFreeNs(lib); + lib = NULL; + } + [super dealloc]; +} + +- (unsigned) hash +{ + return (unsigned)lib; +} + +/** + * Returns the namespace reference + */ +- (NSString*) href +{ + return UTF8Str(((xmlNsPtr)(lib))->href); +} + +- (id) init +{ + NSLog(@"GSXMLNamespace: calling -init is not legal"); + RELEASE(self); + return nil; +} + +/** + * Initialise a new namespace object using raw libxml data. + * The resulting namespace does not 'own' the data, and will not free it. + */ +- (id) initFrom: (void*)data +{ + self = [super init]; + if (self != nil) + { + if (data == NULL) + { + NSLog(@"%@ - no data for initialization", + NSStringFromClass([self class])); + DESTROY(self); + return nil; + } + lib = data; + native = NO; + } + return self; +} + +/** + * Creation of a new Namespace. This function will refuse to create + * a namespace with a similar prefix than an existing one present on + * this node. + */ +- (id) initWithNode: (GSXMLNode*)node + href: (NSString*)href + prefix: (NSString*)prefix +{ + void *data; + + if (node != nil) + { + data = xmlNewNs((xmlNodePtr)[node lib], [href lossyCString], + [prefix lossyCString]); + if (data == NULL) + { + NSLog(@"Can't create GSXMLNamespace object"); + RELEASE(self); + return nil; + } + self = [self initFrom: data]; + } + else + { + data = xmlNewNs(NULL, [href lossyCString], [prefix lossyCString]); + if (data == NULL) + { + NSLog(@"Can't create GSXMLNamespace object"); + RELEASE(self); + return nil; + } + self = [self initFrom: data]; + if (self != nil) + { + native = YES; + } + } + return self; +} + +- (BOOL) isEqualTo: (id)other +{ + if ([other isKindOfClass: [self class]] == YES && [other lib] == lib) + return YES; + else + return NO; +} + +/** + * return the raw libxml data. + */ +- (void*) lib +{ + return lib; +} + +/** + * return the next namespace. + */ +- (GSXMLNamespace*) next +{ + if (((xmlNsPtr)(lib))->next != NULL) + { + return [GSXMLNamespace namespaceFrom: ((xmlNsPtr)(lib))->next]; + } + else + { + return nil; + } +} + +/** + * Return the namespace prefix. + */ +- (NSString*) prefix +{ + return UTF8Str(((xmlNsPtr)(lib))->prefix); +} + +/** + * Return type of namespace + */ +- (int) type +{ + return (int)((xmlNsPtr)(lib))->type; +} + +/** + * Return string representation of the type of the namespace. + */ +- (NSString*) typeDescription +{ + NSString *desc = (NSString*)NSMapGet(nsNames, (void*)[self type]); + + if (desc == nil) + { + desc = @"Unknown namespace type"; + } + return desc; +} + +@end + + +@implementation GSXMLNamespace (GSPrivate) +- (void) _native: (BOOL)value +{ + NSAssert(native != value, NSInternalInconsistencyException); + native = value; +} +@end + +@implementation GSXMLNode: NSObject + +static NSMapTable *nodeNames = 0; + +/** + * Return the string constant value for the node type given. + */ ++ (NSString*) descriptionFromType: (int)type +{ + NSString *desc = (NSString*)NSMapGet(nodeNames, (void*)[self type]); + + return desc; +} + ++ (void) initialize +{ + if (self == [GSXMLNode class]) + { + if (cacheDone == NO) + setupCache(); + nodeNames = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonRetainedObjectMapValueCallBacks, 0); + NSMapInsert(nodeNames, + (void*)XML_ELEMENT_NODE, (void*)@"XML_ELEMENT_NODE"); + NSMapInsert(nodeNames, + (void*)XML_ATTRIBUTE_NODE, (void*)@"XML_ATTRIBUTE_NODE"); + NSMapInsert(nodeNames, + (void*)XML_TEXT_NODE, (void*)@"XML_TEXT_NODE"); + NSMapInsert(nodeNames, + (void*)XML_CDATA_SECTION_NODE, (void*)@"XML_CDATA_SECTION_NODE"); + NSMapInsert(nodeNames, + (void*)XML_ENTITY_REF_NODE, (void*)@"XML_ENTITY_REF_NODE"); + NSMapInsert(nodeNames, + (void*)XML_ENTITY_NODE, (void*)@"XML_ENTITY_NODE"); + NSMapInsert(nodeNames, + (void*)XML_PI_NODE, (void*)@"XML_PI_NODE"); + NSMapInsert(nodeNames, + (void*)XML_COMMENT_NODE, (void*)@"XML_COMMENT_NODE"); + NSMapInsert(nodeNames, + (void*)XML_DOCUMENT_NODE, (void*)@"XML_DOCUMENT_NODE"); + NSMapInsert(nodeNames, + (void*)XML_DOCUMENT_TYPE_NODE, (void*)@"XML_DOCUMENT_TYPE_NODE"); + NSMapInsert(nodeNames, + (void*)XML_DOCUMENT_FRAG_NODE, (void*)@"XML_DOCUMENT_FRAG_NODE"); + NSMapInsert(nodeNames, + (void*)XML_NOTATION_NODE, (void*)@"XML_NOTATION_NODE"); + NSMapInsert(nodeNames, + (void*)XML_HTML_DOCUMENT_NODE, (void*)@"XML_HTML_DOCUMENT_NODE"); + NSMapInsert(nodeNames, + (void*)XML_DTD_NODE, (void*)@"XML_DTD_NODE"); + NSMapInsert(nodeNames, + (void*)XML_ELEMENT_DECL, (void*)@"XML_ELEMENT_DECL"); + NSMapInsert(nodeNames, + (void*)XML_ATTRIBUTE_DECL, (void*)@"XML_ATTRIBUTE_DECL"); + NSMapInsert(nodeNames, + (void*)XML_ENTITY_DECL, (void*)@"XML_ENTITY_DECL"); + } +} + +/** + * Create node from raw libxml data. + */ ++ (GSXMLNode*) nodeFrom: (void*)data +{ + return AUTORELEASE([[self alloc] initFrom: data]); +} + +/** + * Creation of a new Node. This function will refuse to create a Node + * with a similar prefix than an existing one present on this node. + * + * ... + * GSXMLNamespace *ns1; + * GSXMLNode *node1, *node2; + * NSString *prefix = @"mac-os-property"; + * NSString *href = @"http://www.gnustep.org/some/location"; + * + * ns = [GSXMLNamespace namespaceWithNode: nil + * href: href + * prefix: prefix]; + * node1 = [GSXMLNode nodeWithNamespace: ns name: @"node1"]; + * node2 = [GSXMLNode nodeWithNamespace: nil name: @"node2"]; + * ... + * + */ ++ (GSXMLNode*) nodeWithNamespace: (GSXMLNamespace*) ns name: (NSString*) name +{ + return AUTORELEASE([[self alloc] initWithNamespace: ns name: name]); +} + ++ (int) typeFromDescription: (NSString*)desc +{ + NSMapEnumerator enumerator; + NSString *val; + int key; + + enumerator = NSEnumerateMapTable(nodeNames); + while (NSNextMapEnumeratorPair(&enumerator, (void**)&key, (void**)&val)) + { + if ([desc isEqual: val] == YES) + { + return key; + } + } + return -1; +} + +/** + * Return the children of this node + * + * - (GSXMLNode*) nextElement: (GSXMLNode*)node + * { + * while (node != nil) + * { + * if ([node type] == XML_ELEMENT_NODE) + * { + * return node; + * } + * if ([node children] != nil) + * { + * node = [self nextElement: [node children]]; + * } + * else + * { + * node = [node next]; + * } + * } + * return node; + * } + * + */ +- (GSXMLNode*) children +{ + if (((xmlNodePtr)(lib))->children != NULL) + { + return [GSXMLNode nodeFrom: ((xmlNodePtr)(lib))->children]; + } + else + { + return nil; + } +} + +- (id) copyWithZone: (NSZone*)z +{ + return RETAIN(self); +} + +/* + * Return node content. + */ +- (NSString*) content +{ + if (lib != NULL && ((xmlNodePtr)lib)->content!=NULL) + { + return UTF8Str(((xmlNodePtr)lib)->content); + } + else + { + return nil; + } +} + +- (void) dealloc +{ + if (native == YES && lib != NULL) + { + xmlFreeNode(lib); + } + [super dealloc]; +} + +/** + * Return the document which owns this node. + */ +- (GSXMLDocument*) doc +{ + if (((xmlNodePtr)(lib))->doc != NULL) + { + return [GSXMLDocument documentFrom: ((xmlNodePtr)(lib))->doc]; + } + else + { + return nil; + } +} + +- (unsigned) hash +{ + return (unsigned)lib; +} + +/** + * Initialise from raw libxml data + */ +- (id) initFrom: (void*)data +{ + self = [super init]; + if (self != nil) + { + if (data == NULL) + { + NSLog(@"%@ - no data for initialization", + NSStringFromClass([self class])); + DESTROY(self); + return nil; + } + lib = data; + native = NO; + } + return self; +} + +/** + * initialisae node + */ +- (id) initWithNamespace: (GSXMLNamespace*) ns name: (NSString*) name +{ + self = [super init]; + if (self != nil) + { + if (ns != nil) + { + [ns _native: NO]; + lib = xmlNewNode((xmlNsPtr)[ns lib], [name lossyCString]); + } + else + { + lib = xmlNewNode(NULL, [name lossyCString]); + } + if (lib == NULL) + { + NSLog(@"Can't create GSXMLNode object"); + return nil; + } + + native = YES; + } + return self; +} + +- (BOOL) isEqualTo: (id)other +{ + if ([other isKindOfClass: [self class]] == YES + && [other lib] == lib) + { + return YES; + } + else + { + return NO; + } +} + +/** + * Return raw libxml data + */ +- (void*) lib +{ + return lib; +} + +/** + *

+ * Creation of a new child element, added at the end of + * parent children list. + * ns and content parameters are optional (may be nil). + * If content is non nil, a child list containing the + * TEXTs and ENTITY_REFs node will be created. + * Return previous node. + *

+ * + * + * GSXMLNode *n1, *n2; + * GSXMLDocument *d, *d1; + * + * d = [GSXMLDocument documentWithVersion: @"1.0"]; + * [d setRoot: [d makeNodeWithNamespace: nil + * name: @"plist" + * content: nil]]; + * [[d root] setProp: @"version" value: @"0.9"]; + * n1 = [[d root] makeChildWithNamespace: nil + * name: @"dict" + * content: nil]; + * [n1 makeChildWithNamespace: nil name: @"key" content: @"Year Of Birth"]; + * [n1 makeChildWithNamespace: nil name: @"integer" content: @"65"]; + * + * [n1 makeChildWithNamespace: nil name: @"key" content: @"Pets Names"]; + * [n1 makeChildWithNamespace: nil name: @"array" content: nil]; + * + * + */ +- (GSXMLNode*) makeChildWithNamespace: (GSXMLNamespace*)ns + name: (NSString*)name + content: (NSString*)content +{ + return [GSXMLNode nodeFrom: + xmlNewChild(lib, [ns lib], [name lossyCString], [content lossyCString])]; +} + +/** + * Creation of a new comment element, added at the end of + * parent children list. + * + * d = [GSXMLDocument documentWithVersion: @"1.0"]; + * + * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; + * [[d root] setProp: @"version" value: @"0.9"]; + * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; + * [n1 makeComment: @" this is a comment "]; + * + */ +- (GSXMLNode*) makeComment: (NSString*)content +{ + return [GSXMLNode nodeFrom: xmlAddChild((xmlNodePtr)lib, + xmlNewComment([content lossyCString]))]; +} + +/** + * Creation of a new process instruction element, + * added at the end of parent children list. + * + * d = [GSXMLDocument documentWithVersion: @"1.0"]; + * + * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; + * [[d root] setProp: @"version" value: @"0.9"]; + * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; + * [n1 makeComment: @" this is a comment "]; + * [n1 makePI: @"pi1" content: @"this is a process instruction"]; + * + */ +- (GSXMLNode*) makePI: (NSString*)name content: (NSString*)content +{ + return [GSXMLNode nodeFrom: + xmlAddChild((xmlNodePtr)lib, xmlNewPI([name lossyCString], + [content lossyCString]))]; +} + +/** + * Return the node-name + */ +- (NSString*) name +{ + if (lib != NULL && ((xmlNodePtr)lib)->name!=NULL) + { + return UTF8Str(((xmlNodePtr)lib)->name); + } + else + { + return nil; + } +} + +/** + * return the next node at this level. + */ +- (GSXMLNode*) next +{ + if (((xmlNodePtr)(lib))->next != NULL) + { + return [GSXMLNode nodeFrom: ((xmlNodePtr)(lib))->next]; + } + else + { + return nil; + } +} + +/** + * Return the namespace of the node. + */ +- (GSXMLNamespace*) ns +{ + if (lib != NULL && ((xmlNodePtr)(lib))->ns != NULL) + { + return [GSXMLNamespace namespaceFrom: ((xmlNodePtr)(lib))->ns]; + } + else + { + return nil; + } +} + +/** + * Return namespace definitions for the node + */ +- (GSXMLNamespace*) nsDef +{ + if (lib != NULL && ((xmlNodePtr)lib)->nsDef != NULL) + { + return [GSXMLNamespace namespaceFrom: ((xmlNodePtr)lib)->nsDef]; + } + else + { + return nil; + } +} + +/** + * Return the parent of this node. + */ +- (GSXMLNode*) parent +{ + if (((xmlNodePtr)(lib))->parent != NULL) + { + return [GSXMLNode nodeFrom: ((xmlNodePtr)(lib))->parent]; + } + else + { + return nil; + } +} + +/** + * Return the previous node at this level. + */ +- (GSXMLNode*) prev +{ + if (((xmlNodePtr)(lib))->prev != NULL) + { + return [GSXMLNode nodeFrom: ((xmlNodePtr)(lib))->prev]; + } + else + { + return nil; + } +} + +/** + * Return the properties of the node. + * + * + * GSXMLNode *n1; + * GSXMLAttribute *a; + * + * n1 = [GSXMLNode nodeWithNamespace: nil name: nodeName]; + * [n1 setProp: @"prop1" value: @"value1"]; + * [n1 setProp: @"prop2" value: @"value2"]; + * [n1 setProp: @"prop3" value: @"value3"]; + * + * a = [n1 properties]; + * NSLog(@"n1 property name - %@ value - %@", [a name], [a value]); + * while ((a = [a next]) != nil) + * { + * NSLog(@"n1 property name - %@ value - %@", [a name], [a value]); + * } + * + * + */ +- (GSXMLNode*) properties +{ + if (((xmlNodePtr)(lib))->properties != NULL) + { + return [GSXMLAttribute attributeFrom: ((xmlNodePtr)(lib))->properties]; + } + else + { + return nil; + } +} + +/** + * Return attributes and values as a dictionary. + * + * + * GSXMLNode *n1; + * NSMutableDictionary *prop; + * NSEnumerator *e; + * id key; + * + * prop = [n1 propertiesAsDictionary]; + * e = [prop keyEnumerator]; + * while ((key = [e nextObject]) != nil) + * { + * NSLog(@"property name - %@ value - %@", key, + * [prop objectForKey: key]); + * } + * +*/ +- (NSMutableDictionary*) propertiesAsDictionary +{ + return [self propertiesAsDictionaryWithKeyTransformationSel: NULL]; +} + +/** + *

+ * Return attributes and values as a dictionary, but applies + * the specified selector to each key before adding the + * key and value to the dictionary. The selector must be a + * method of NSString taking no arguments and returning an + * object suitable for use as a dictionary key. + *

+ *

+ * This method exists for the use of GSWeb ... it is probably + * not of much use elsewhere. + *

+ */ +- (NSMutableDictionary*) propertiesAsDictionaryWithKeyTransformationSel: + (SEL)keyTransformSel +{ + xmlAttrPtr prop; + NSMutableDictionary *d = [NSMutableDictionary dictionary]; + + prop = ((xmlNodePtr)(lib))->properties; + + while (prop != NULL) + { + const void *name = prop->name; + NSString *key = UTF8Str(name); + + if (keyTransformSel != 0) + { + key = [key performSelector: keyTransformSel]; + } + if (prop->children != NULL) + { + const void *content = prop->children->content; + + [d setObject: UTF8Str(content) forKey: key]; + } + else + { + [d setObject: @"" forKey: key]; + } + prop = prop->next; + } + + return d; +} + +/** + * Set (or reset) an attribute carried by a node. + * + * id n1 = [GSXMLNode nodeWithNamespace: nil name: nodeName]; + * [n1 setProp: @"prop1" value: @"value1"]; + * [n1 setProp: @"prop2" value: @"value2"]; + * [n1 setProp: @"prop3" value: @"value3"]; + * + */ +- (GSXMLAttribute*) setProp: (NSString*)name value: (NSString*)value +{ + return [GSXMLAttribute attributeFrom: + xmlSetProp(lib, [name lossyCString], [value lossyCString])]; +} + +/** + * Return node-type. + */ +- (int) type +{ + return (int)((xmlNodePtr)(lib))->type; +} + +/** + * Return node type as a string. + */ +- (NSString*) typeDescription +{ + NSString *desc = (NSString*)NSMapGet(nodeNames, (void*)[self type]); + + if (desc == nil) + { + desc = @"Unknown node type"; + } + return desc; +} + +@end + +@implementation GSXMLNode (GSPrivate) +- (void) _native: (BOOL)value +{ + NSAssert(native != value, NSInternalInconsistencyException); + native = value; +} +@end + + + +@implementation GSXMLAttribute : GSXMLNode + +static NSMapTable *attrNames = 0; + +/** + * Create attribute from underlying libxml data ... you probably don't need + * to use this yourself. + */ ++ (GSXMLAttribute*) attributeFrom: (void*)data +{ + return AUTORELEASE([[self alloc] initFrom: data]); +} + +/** + * Create a new property carried by a node. + */ ++ (GSXMLAttribute*) attributeWithNode: (GSXMLNode*)node + name: (NSString*)name + value: (NSString*)value +{ + return AUTORELEASE([[self alloc] initWithNode: node name: name value: value]); +} + +/** + * Return the string constant value for the attribute + * type given. + */ ++ (NSString*) descriptionFromType: (int)type +{ + NSString *desc = (NSString*)NSMapGet(attrNames, (void*)[self type]); + + return desc; +} + ++ (void) initialize +{ + if (self == [GSXMLAttribute class]) + { + if (cacheDone == NO) + setupCache(); + attrNames = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonRetainedObjectMapValueCallBacks, 0); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_CDATA, (void*)@"XML_ATTRIBUTE_CDATA"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_ID, (void*)@"XML_ATTRIBUTE_ID"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_IDREF, (void*)@"XML_ATTRIBUTE_IDREF"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_IDREFS, (void*)@"XML_ATTRIBUTE_IDREFS"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_ENTITY, (void*)@"XML_ATTRIBUTE_ENTITY"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_ENTITIES, (void*)@"XML_ATTRIBUTE_ENTITIES"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_NMTOKEN, (void*)@"XML_ATTRIBUTE_NMTOKEN"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_NMTOKENS, (void*)@"XML_ATTRIBUTE_NMTOKENS"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_ENUMERATION, (void*)@"XML_ATTRIBUTE_ENUMERATION"); + NSMapInsert(attrNames, + (void*)XML_ATTRIBUTE_NOTATION, (void*)@"XML_ATTRIBUTE_NOTATION"); + } +} + +/** + *

+ * Return the numeric constant value for the attribute + * type named. This method is inefficient, so the returned + * value should be saved for re-use later. The possible + * values are - + *

+ * + * XML_ATTRIBUTE_CDATA + * XML_ATTRIBUTE_ID + * XML_ATTRIBUTE_IDREF + * XML_ATTRIBUTE_IDREFS + * XML_ATTRIBUTE_ENTITY + * XML_ATTRIBUTE_ENTITIES + * XML_ATTRIBUTE_NMTOKEN + * XML_ATTRIBUTE_NMTOKENS + * XML_ATTRIBUTE_ENUMERATION + * XML_ATTRIBUTE_NOTATION + * + */ ++ (int) typeFromDescription: (NSString*)desc +{ + NSMapEnumerator enumerator; + NSString *val; + int key; + + enumerator = NSEnumerateMapTable(attrNames); + while (NSNextMapEnumeratorPair(&enumerator, (void**)&key, (void**)&val)) + { + if ([desc isEqual: val] == YES) + { + return key; + } + } + return -1; +} + +- (id) copyWithZone: (NSZone*)z +{ + return RETAIN(self); +} + +- (id) init +{ + NSLog(@"GSXMLNode: calling -init is not legal"); + RELEASE(self); + return nil; +} + +/** + * Generates the raw data for an attribute node and calls -initFrom: + * to initialise this instance. + */ +- (id) initWithNode: (GSXMLNode*)node + name: (NSString*)name + value: (NSString*)value +{ + void *data = (void*)xmlNewProp((xmlNodePtr)[node lib], [name lossyCString], + [value lossyCString]); + + self = [self initFrom: data]; + if (self != nil) + { + native = YES; + } + return self; +} + +/** + * Returns underlying raw data associated with this node. + */ +- (void*) lib +{ + return lib; +} + +/** + * Return the attribute-name + */ +- (NSString*) name +{ + return[NSString_class stringWithCString: ((xmlAttrPtr)(lib))->name]; +} + +/** + * Return next attribute. + * + * id a = [node properties]; + * NSLog(@"n1 property name - %@ value - %@", [a name], [a value]); + * while ((a = [a next]) != nil) + * { + * NSLog(@"n1 property name - %@ value - %@", [a name], [a value]); + * } + * + */ +- (GSXMLAttribute*) next +{ + if (((xmlAttrPtr)(lib))->next != NULL) + { + return [GSXMLAttribute attributeFrom: ((xmlAttrPtr)(lib))->next]; + } + else + { + return nil; + } +} + +/** + * Return the namespace of this attribute. + */ +- (GSXMLNamespace*) ns +{ + return [GSXMLNamespace namespaceFrom: ((xmlAttrPtr)(lib))->ns]; +} + +/** + * Return previous attribute. + */ +- (GSXMLAttribute*) prev +{ + if (((xmlAttrPtr)(lib))->prev != NULL) + { + return [GSXMLAttribute attributeFrom: ((xmlAttrPtr)(lib))->prev]; + } + else + { + return nil; + } +} + +/** + * Return the numeric type code for this attribute. + */ +- (int) type +{ + return (int)((xmlAttrPtr)(lib))->atype; +} + +/** + * Return the string type code for this attribute. + */ +- (NSString*) typeDescription +{ + NSString *desc = (NSString*)NSMapGet(attrNames, (void*)[self type]); + + if (desc == nil) + { + desc = @"Unknown attribute type"; + } + return desc; +} + +/** + * Return a value of this attribute. + */ +- (NSString*) value +{ + if (((xmlNodePtr)lib)->children != NULL + && ((xmlNodePtr)lib)->children->content != NULL) + { + return UTF8Str(((xmlNodePtr)(lib))->children->content); + } + return nil; +} + +@end + + +/** + *

+ * The XML parser object is the pivotal part of parsing an XML + * document - it will either build a tree representing the + * document (if initialized without a GSSAXHandler), or will + * cooperate with a GSSAXHandler object to provide parsing + * without the overhead of building a tree. + *

+ *

+ * The parser may be initialized with an input source (in which + * case it will expect to be asked to parse the entire input in + * a single operation), or without. If it is initialised without + * an input source, incremental parsing can be done by feeding + * successive parts of the XML document into the parser as + * NSData objects. + *

+ */ +@implementation GSXMLParser : NSObject + +static NSString *endMarker = @"At end of incremental parse"; + ++ (void) initialize +{ + if (cacheDone == NO) + setupCache(); + xmlSetExternalEntityLoader((xmlExternalEntityLoader)loadEntityFunction); +} + +/** + *

+ * This method controls the loading of external entities into + * the system. If it returns an empty string, the entity is not + * loaded. If it returns a filename, the entity is loaded from + * that file. If it returns nil, the default entity loading + * mechanism is used. + *

+ *

+ * The default entity loading mechanism is to construct a file + * name from the locationURL, by replacing all path separators + * with underscores, then attempt to locate that file in the DTDs + * resource directory of the main bundle, and all the standard + * system locations. + *

+ *

+ * As a special case, the default loader examines the publicID + * and if it is a GNUstep DTD, the loader constructs a special + * name from the ID (by replacing dots with underscores and + * spaces with hyphens) and looks for a file with that name + * and a '.dtd' extension in the GNUstep bundles. + *

+ *

+ * NB. This method will only be called if there is no SAX + * handler in use, or if the corresponding method in the + * SAX handler returns nil. + *

+ */ ++ (NSString*) loadEntity: (NSString*)publicId + at: (NSString*)location +{ + return nil; +} + +/** + * Creation of a new Parser (for incremental parsing) + * by calling -initWithSAXHandler: + */ ++ (GSXMLParser*) parser +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: nil]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withContentsOfFile: + * + * GSXMLParser *p = [GSXMLParser parserWithContentsOfFile: @"macos.xml"]; + * + * if ([p parse]) + * { + * [[p doc] dump]; + * } + * else + * { + * printf("error parse file\n"); + * } + * + */ ++ (GSXMLParser*) parserWithContentsOfFile: (NSString*)path +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: nil + withContentsOfFile: path]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withContentsOfURL: + */ ++ (GSXMLParser*) parserWithContentsOfURL: (NSURL*)url +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: nil + withContentsOfURL: url]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withData: + */ ++ (GSXMLParser*) parserWithData: (NSData*)data +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: nil + withData: data]); +} + +/** + *

+ * Creation of a new Parser by calling -initWithSAXHandler: + *

+ *

+ * If the handler object supplied is nil, the parser will build + * a tree representing the parsed file rather than attempting + * to get the handler to deal with the parsed elements and entities. + *

+ */ ++ (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: handler]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withContentsOfFile: + * + * CREATE_AUTORELEASE_POOL(arp); + * GSSAXHandler *h = [GSDebugSAXHandler handler]; + * GSXMLParser *p = [GSXMLParser parserWithSAXHandler: h + * withContentsOfFile: @"macos.xml"]; + * if ([p parse]) + * { + * printf("ok\n"); + * } + * RELEASE(arp); + * + */ ++ (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler + withContentsOfFile: (NSString*)path +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: handler + withContentsOfFile: path]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withContentsOfURL: + */ ++ (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler + withContentsOfURL: (NSURL*)url +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: handler + withContentsOfURL: url]); +} + +/** + * Creation of a new Parser by calling + * -initWithSAXHandler:withData: + */ ++ (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler + withData: (NSData*)data +{ + return AUTORELEASE([[self alloc] initWithSAXHandler: handler + withData: data]); +} + +/** + * Return the name of the string encoding (for XML) to use for the + * specified OpenStep encoding. + */ ++ (NSString*) xmlEncodingStringForStringEncoding: (NSStringEncoding)encoding +{ + NSString *xmlEncodingString = nil; + + switch (encoding) + { + case NSUnicodeStringEncoding: + NSLog(@"NSUnicodeStringEncoding not supported for XML");//?? + break; + case NSNEXTSTEPStringEncoding: + NSLog(@"NSNEXTSTEPStringEncoding not supported for XML");//?? + break; + case NSJapaneseEUCStringEncoding: + xmlEncodingString = @"EUC-JP"; + break; + case NSShiftJISStringEncoding: + xmlEncodingString = @"Shift-JIS"; + break; + case NSISO2022JPStringEncoding: + xmlEncodingString = @"ISO-2022-JP"; + break; + case NSUTF8StringEncoding: + xmlEncodingString = @"UTF-8"; + break; + case NSWindowsCP1251StringEncoding: + NSLog(@"NSWindowsCP1251StringEncoding not supported for XML");//?? + break; + case NSWindowsCP1252StringEncoding: + NSLog(@"NSWindowsCP1252StringEncoding not supported for XML");//?? + break; + case NSWindowsCP1253StringEncoding: + NSLog(@"NSWindowsCP1253StringEncoding not supported for XML");//?? + break; + case NSWindowsCP1254StringEncoding: + NSLog(@"NSWindowsCP1254StringEncoding not supported for XML");//?? + break; + case NSWindowsCP1250StringEncoding: + NSLog(@"NSWindowsCP1250StringEncoding not supported for XML");//?? + break; + case NSISOLatin1StringEncoding: + xmlEncodingString = @"ISO-8859-1"; + break; + case NSISOLatin2StringEncoding: + xmlEncodingString = @"ISO-8859-2"; + break; + case NSSymbolStringEncoding: + NSLog(@"NSSymbolStringEncoding not supported for XML");//?? + break; + case NSISOCyrillicStringEncoding: + NSLog(@"NSISOCyrillicStringEncoding not supported for XML");//?? + break; + case NSNonLossyASCIIStringEncoding: + case NSASCIIStringEncoding: + case GSUndefinedEncoding: + default: + xmlEncodingString = nil; + break; + } + return xmlEncodingString; +} + +- (void) dealloc +{ + RELEASE(src); + RELEASE(saxHandler); + if (lib != NULL) + { + xmlFreeDoc(((xmlParserCtxtPtr)lib)->myDoc); + xmlFreeParserCtxt(lib); + } + [super dealloc]; +} + +/** + * Sets whether the document needs to be validated. + */ +- (BOOL) doValidityChecking: (BOOL)yesno +{ + int oldVal; + int newVal = (yesno == YES) ? 1 : 0; + + xmlGetFeature((xmlParserCtxtPtr)lib, "validate", (void*)&oldVal); + xmlSetFeature((xmlParserCtxtPtr)lib, "validate", (void*)&newVal); + return (oldVal == 1) ? YES : NO; +} + +/** + * Return the document produced as a result of parsing data. + */ +- (GSXMLDocument*) doc +{ + return [GSXMLDocument documentFrom: ((xmlParserCtxtPtr)lib)->myDoc]; +} + +/** + * Return error code for last parse operation. + */ +- (int) errNo +{ + return ((xmlParserCtxtPtr)lib)->errNo; +} + +/** + * Sets whether warnings are generated. + */ +- (BOOL) getWarnings: (BOOL)yesno +{ + return !(xmlGetWarningsDefaultValue = yesno); +} + +/** + *

+ * Initialisation of a new Parser with SAX handler (if not nil). + *

+ *

+ * If the handler object supplied is nil, the parser will build + * a tree representing the parsed file rather than attempting + * to get the handler to deal with the parsed elements and entities. + *

+ *

+ * The source for the parsing process is not specified - so + * parsing must be done incrementally by feeding data to the + * parser. + *

+ */ +- (id) initWithSAXHandler: (GSSAXHandler*)handler +{ + if (handler != nil && [handler isKindOfClass: [GSSAXHandler class]] == NO) + { + NSLog(@"Bad GSSAXHandler object passed to GSXMLParser initialiser"); + RELEASE(self); + return nil; + } + saxHandler = RETAIN(handler); + [saxHandler _setParser: self]; + if ([self _initLibXML] == NO) + { + RELEASE(self); + return nil; + } + return self; +} + +/** + *

+ * Initialisation of a new Parser with SAX handler (if not nil) + * by calling -initWithSAXHandler: + *

+ *

+ * Sets the input source for the parser to be the specified file - + * so parsing of the entire file will be performed rather than + * incremental parsing. + *

+ */ +- (id) initWithSAXHandler: (GSSAXHandler*)handler + withContentsOfFile: (NSString*)path +{ + self = [self initWithSAXHandler: handler]; + if (self != nil) + { + if (path == nil || [path isKindOfClass: [NSString class]] == NO) + { + NSLog(@"Bad file path passed to initialize GSXMLParser"); + RELEASE(self); + return nil; + } + src = [path copy]; + } + return self; +} + +/** + *

+ * Initialisation of a new Parser with SAX handler (if not nil) + * by calling -initWithSAXHandler: + *

+ *

+ * Sets the input source for the parser to be the specified URL - + * so parsing of the entire document will be performed rather than + * incremental parsing. + *

+ */ +- (id) initWithSAXHandler: (GSSAXHandler*)handler + withContentsOfURL: (NSURL*)url +{ + self = [self initWithSAXHandler: handler]; + if (self != nil) + { + if (url == nil || [url isKindOfClass: [NSURL class]] == NO) + { + NSLog(@"Bad NSURL passed to initialize GSXMLParser"); + RELEASE(self); + return nil; + } + src = [url copy]; + } + return self; +} + +/** + *

+ * Initialisation of a new Parser with SAX handler (if not nil) + * by calling -initWithSAXHandler: + *

+ *

+ * Sets the input source for the parser to be the specified data + * object (which must contain an XML document), so parsing of the + * entire document will be performed rather than incremental parsing. + *

+ */ +- (id) initWithSAXHandler: (GSSAXHandler*)handler + withData: (NSData*)data +{ + self = [self initWithSAXHandler: handler]; + if (self != nil) + { + if (data == nil || [data isKindOfClass: [NSData class]] == NO) + { + NSLog(@"Bad NSData passed to initialize GSXMLParser"); + RELEASE(self); + return nil; + } + src = [data copy]; + } + return self; +} + +/** + * Set and return the previous value for blank text nodes support. + * ignorableWhitespace() are only generated when running + * the parser in validating mode and when the current element + * doesn't allow CDATA or mixed content. + */ +- (BOOL) keepBlanks: (BOOL)yesno +{ + int oldVal; + int newVal = (yesno == YES) ? 1 : 0; + + xmlGetFeature((xmlParserCtxtPtr)lib, "keep blanks", (void*)&oldVal); + xmlSetFeature((xmlParserCtxtPtr)lib, "keep blanks", (void*)&newVal); + return (oldVal == 1) ? YES : NO; +} + +/** + * Parse source. Return YES if parsed, otherwise NO. + * This method should be called once to parse the entire document. + * + * GSXMLParser *p = [GSXMLParser parserWithContentsOfFile:@"macos.xml"]; + * + * if ([p parse]) + * { + * [[p doc] dump]; + * } + * else + * { + * printf("error parse file\n"); + * } + * + */ +- (BOOL) parse +{ + id tmp; + + if (src == endMarker) + { + NSLog(@"GSXMLParser -parse called on object that is already parsed"); + return NO; + } + if (src == nil) + { + NSLog(@"GSXMLParser -parse called on object with no source"); + return NO; + } + + if ([src isKindOfClass: [NSData class]]) + { + } + else if ([src isKindOfClass: NSString_class]) + { + NSData *data = [NSData dataWithContentsOfFile: src]; + + if (data == nil) + { + NSLog(@"File to parse (%@) is not readable", src); + return NO; + } + ASSIGN(src, data); + } + else if ([src isKindOfClass: [NSURL class]]) + { + NSData *data = [src resourceDataUsingCache: YES]; + + if (data == nil) + { + NSLog(@"URL to parse (%@) is not readable", src); + return NO; + } + ASSIGN(src, data); + } + else + { + NSLog(@"source for [-parse] must be NSString, NSData or NSURL type"); + return NO; + } + + tmp = RETAIN(src); + ASSIGN(src, endMarker); + [self _parseChunk: tmp]; + [self _parseChunk: nil]; + RELEASE(tmp); + + if (((xmlParserCtxtPtr)lib)->wellFormed) + return YES; + else + return NO; +} + +/** + *

+ * Pass data to the parser for incremental parsing. This method + * should be called many times, with each call passing another + * block of data from the same document. After the whole of the + * document has been parsed, the method should be called with + * an empty or nil data object to indicate end of parsing. + * On this final call, the return value indicates whether the + * document was valid or not. + *

+ * + * GSXMLParser *p = [GSXMLParser parserWithSAXHandler: nil source: nil]; + * + * while ((data = getMoreData()) != nil) + * { + * if ([p parse: data] == NO) + * { + * NSLog(@"parse error"); + * } + * } + * // Do something with document parsed + * [p parse: nil]; // Completed parsing of document. + * + */ +- (BOOL) parse: (NSData*)data +{ + if (src == endMarker) + { + NSLog(@"GSXMLParser -parse: called on object that is fully parsed"); + return NO; + } + if (src != nil) + { + NSLog(@"XMLParser -parse: called for parser not initialised with nil"); + return NO; + } + + if (data == nil || [data length] == 0) + { + /* + * At end of incremental parse. + */ + if (lib != NULL) + { + xmlParseChunk(lib, 0, 0, 1); + src = endMarker; + if (((xmlParserCtxtPtr)lib)->wellFormed) + return YES; + else + return NO; + } + else + { + NSLog(@"GSXMLParser -parse: terminated with no data"); + return NO; + } + } + else + { + [self _parseChunk: data]; + return YES; + } +} + +/** + * Set and return the previous value for entity support. + * Initially the parser always keeps entity references instead + * of substituting entity values in the output. + */ +- (BOOL) substituteEntities: (BOOL)yesno +{ + int oldVal; + int newVal = (yesno == YES) ? 1 : 0; + + xmlGetFeature((xmlParserCtxtPtr)lib, "substitute entities", (void*)&oldVal); + xmlSetFeature((xmlParserCtxtPtr)lib, "substitute entities", (void*)&newVal); + return (oldVal == 1) ? YES : NO; +} + +/* + * Private methods - internal use only. + */ + +- (BOOL) _initLibXML +{ + lib = (void*)xmlCreatePushParserCtxt([saxHandler lib], NULL, 0, 0, "."); + if (lib == NULL) + { + NSLog(@"Failed to create libxml parser context"); + return NO; + } + else + { + /* + * Put saxHandler address in _private member, so we can retrieve + * the GSXMLHandler to use in our SAX C Functions. + */ + ((xmlParserCtxtPtr)lib)->_private=saxHandler; + } + return YES; +} + +- (void) _parseChunk: (NSData*)data +{ + // nil data allowed + xmlParseChunk(lib, [data bytes], [data length], 0); +} + +@end + +/** + * The GSHTMLParser class is a simple subclass of GSXMLParser which should + * parse reasonably well formed HTML documents. If you wish to parse XHTML + * documents, you should use GSXMLParser ... the GSHTMLParser class is for + * older 'legacy' documents. + */ +@implementation GSHTMLParser + +- (BOOL) _initLibXML +{ + lib = (void*)htmlCreatePushParserCtxt([saxHandler lib], NULL, 0, 0, ".", + XML_CHAR_ENCODING_NONE); + if (lib == NULL) + { + NSLog(@"Failed to create libxml parser context"); + return NO; + } + else + { + /* + * Put saxHandler address in _private member, so we can retrieve + * the GSXMLHandler to use in our SAX C Functions. + */ + ((htmlParserCtxtPtr)lib)->_private = saxHandler; + } + return YES; +} + +- (void) _parseChunk: (NSData*)data +{ + htmlParseChunk(lib, [data bytes], [data length], 0); +} + +@end + +/** + *

XML SAX Handler.

+ *

+ * GSSAXHandler is a callback-based interface to the XML parser + * that operates in a similar (though not identical) manner to + * SAX. + *

+ *

+ * Each GSSAXHandler object is associated with a GSXMLParser + * object. As parsing progresses, the mathods of the GSSAXHandler + * are invoked by the parser, so the handler is able to deal + * with the elements and entities being parsed. + *

+ *

+ * The callback methods in the GSSAXHandler class do nothing - it + * is intended that you subclass GSSAXHandler and override them. + *

+ */ +@implementation GSSAXHandler : NSObject + ++ (void) initialize +{ + if (cacheDone == NO) + setupCache(); +} + +/* + * The context is a xmlParserCtxtPtr or htmlParserCtxtPtr. + * Its _private member contains the address of our Sax Handler Object. + * We can use a (xmlParserCtxtPtr) cast because xmlParserCtxt and + * htmlParserCtxt are the same structure (and will remain, cf libxml author). + */ +#define HANDLER (GSSAXHandler*)(((xmlParserCtxtPtr)ctx)->_private) + +static xmlParserInputPtr +loadEntityFunction(const char *url, const char *eid, xmlParserCtxtPtr *ctx) +{ + extern xmlParserInputPtr xmlNewInputFromFile(); + NSString *file; + xmlParserInputPtr ret = 0; + NSString *entityId; + NSString *location; + NSArray *components; + NSMutableString *local; + unsigned count; + unsigned index; + + NSCAssert(ctx, @"No Context"); + if (eid == 0 || url == 0) + return 0; + + entityId = UTF8Str(eid); + location = UTF8Str(url); + components = [location pathComponents]; + local = [NSMutableString string]; + + /* + * Build a local filename by replacing path separator characters with + * something else. + */ + count = [components count]; + if (count > 0) + { + count--; + for (index = 0; index < count; index++) + { + [local appendString: [components objectAtIndex: index]]; + [local appendString: @"_"]; + } + [local appendString: [components objectAtIndex: index]]; + } + + /* + * Now ask the SAXHandler callback for the name of a local file + */ + file = [HANDLER loadEntity: entityId at: location]; + if (file == nil) + { + file = [GSXMLParser loadEntity: entityId at: location]; + } + + if (file == nil) + { + /* + * Special case - GNUstep DTDs - should be installed in the GNUstep + * system bundle - so we look for them there. + */ + if ([entityId hasPrefix: @"-//GNUstep//DTD "] == YES) + { + NSCharacterSet *ws = [NSCharacterSet whitespaceCharacterSet]; + NSMutableString *name; + NSString *found; + unsigned len; + NSRange r; + + /* + * Extract the relevent DTD name + */ + name = AUTORELEASE([entityId mutableCopy]); + r = NSMakeRange(0, 16); + [name deleteCharactersInRange: r]; + len = [name length]; + r = [name rangeOfString: @"/" options: NSLiteralSearch]; + if (r.length > 0) + { + r.length = len - r.location; + [name deleteCharactersInRange: r]; + len = [name length]; + } + + /* + * Convert dots to underscores. + */ + r = [name rangeOfString: @"." options: NSLiteralSearch]; + while (r.length > 0) + { + [name replaceCharactersInRange: r withString: @"_"]; + r.location++; + r.length = len - r.location; + r = [name rangeOfString: @"." + options: NSLiteralSearch + range: r]; + } + + /* + * Convert whitespace to hyphens. + */ + r = [name rangeOfCharacterFromSet: ws options: NSLiteralSearch]; + while (r.length > 0) + { + [name replaceCharactersInRange: r withString: @"-"]; + r.location++; + r.length = len - r.location; + r = [name rangeOfCharacterFromSet: ws + options: NSLiteralSearch + range: r]; + } + + found = [NSBundle pathForGNUstepResource: name + ofType: @"dtd" + inDirectory: @"DTDs"]; + if (found == nil) + { + NSLog(@"unable to find GNUstep DTD - '%@' for '%s'", name, eid); + } + else + { + file = found; + } + } + + /* + * DTD not found - so we look for it in standard locations. + */ + if (file == nil) + { + file = [[NSBundle mainBundle] pathForResource: local + ofType: @"" + inDirectory: @"DTDs"]; + if (file == nil) + { + file = [NSBundle pathForGNUstepResource: local + ofType: @"" + inDirectory: @"DTDs"]; + } + } + } + + if ([file length] > 0) + { + ret = xmlNewInputFromFile(ctx, [file fileSystemRepresentation]); + } + else + { + NSLog(@"don't know how to load entity '%s' id '%s'", url, eid); + } + return ret; +} + +static void +startDocumentFunction(void *ctx) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER startDocument]; +} + +static void +endDocumentFunction(void *ctx) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER endDocument]; +} + +static int +isStandaloneFunction(void *ctx) +{ + NSCAssert(ctx,@"No Context"); + return [HANDLER isStandalone]; +} + +static int +hasInternalSubsetFunction(void *ctx) +{ + int has; + + NSCAssert(ctx,@"No Context"); + has = [HANDLER hasInternalSubset]; + if (has < 0) + has = (*xmlDefaultSAXHandler.hasInternalSubset)(ctx); + return has; +} + +static int +hasExternalSubsetFunction(void *ctx) +{ + int has; + + NSCAssert(ctx,@"No Context"); + has = [HANDLER hasExternalSubset]; + if (has < 0) + has = (*xmlDefaultSAXHandler.hasExternalSubset)(ctx); + return has; +} + +static void +internalSubsetFunction(void *ctx, const char *name, + const xmlChar *ExternalID, const xmlChar *SystemID) +{ + NSCAssert(ctx,@"No Context"); + if ([HANDLER internalSubset: UTF8Str(name) + externalID: UTF8Str(ExternalID) + systemID: UTF8Str(SystemID)] == NO) + (*xmlDefaultSAXHandler.internalSubset)(ctx, name, ExternalID, SystemID); +} + +static void +externalSubsetFunction(void *ctx, const char *name, + const xmlChar *ExternalID, const xmlChar *SystemID) +{ + NSCAssert(ctx,@"No Context"); + if ([HANDLER externalSubset: UTF8Str(name) + externalID: UTF8Str(ExternalID) + systemID: UTF8Str(SystemID)] == NO) + (*xmlDefaultSAXHandler.externalSubset)(ctx, name, ExternalID, SystemID); +} + +static xmlEntityPtr +getEntityFunction(void *ctx, const char *name) +{ + NSCAssert(ctx,@"No Context"); + return [HANDLER getEntity: UTF8Str(name)]; +} + +static xmlEntityPtr +getParameterEntityFunction(void *ctx, const char *name) +{ + NSCAssert(ctx,@"No Context"); + return [HANDLER getParameterEntity: UTF8Str(name)]; +} + +static void +entityDeclFunction(void *ctx, const char *name, int type, + const char *publicId, const char *systemId, char *content) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER entityDecl: UTF8Str(name) + type: type + public: UTF8Str(publicId) + system: UTF8Str(systemId) + content: UTF8Str(content)]; +} + +static void +attributeDeclFunction(void *ctx, const char *elem, const char *name, + int type, int def, const char *defaultValue, xmlEnumerationPtr tree) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER attributeDecl: UTF8Str(elem) + name: UTF8Str(name) + type: type + typeDefValue: def + defaultValue: UTF8Str(defaultValue)]; +} + +static void +elementDeclFunction(void *ctx, const char *name, int type, + xmlElementContentPtr content) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER elementDecl: UTF8Str(name) + type: type]; + +} + +static void +notationDeclFunction(void *ctx, const char *name, + const char *publicId, const char *systemId) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER notationDecl: UTF8Str(name) + public: UTF8Str(publicId) + system: UTF8Str(systemId)]; +} + +static void +unparsedEntityDeclFunction(void *ctx, const char *name, + const char *publicId, const char *systemId, const char *notationName) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER unparsedEntityDecl: UTF8Str(name) + public: UTF8Str(publicId) + system: UTF8Str(systemId) + notationName: UTF8Str(notationName)]; +} + +static void +startElementFunction(void *ctx, const char *name, const char **atts) +{ + int i; + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSString *key, *obj; + NSCAssert(ctx,@"No Context"); + + if (atts != NULL) + { + for (i = 0; (atts[i] != NULL); i++) + { + key = [NSString_class stringWithCString: atts[i++]]; + obj = [NSString_class stringWithCString: atts[i]]; + [dict setObject: obj forKey: key]; + } + } + [HANDLER startElement: UTF8Str(name) + attributes: dict]; +} + +static void +endElementFunction(void *ctx, const char *name) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER endElement: UTF8Str(name)]; +} + +static void +charactersFunction(void *ctx, const char *ch, int len) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER characters: UTF8StrLen(ch, len)]; +} + +static void +referenceFunction(void *ctx, const char *name) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER reference: UTF8Str(name)]; +} + +static void +ignorableWhitespaceFunction(void *ctx, const char *ch, int len) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER ignoreWhitespace: UTF8StrLen(ch, len)]; +} + +static void +processInstructionFunction(void *ctx, const char *target, const char *data) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER processInstruction: UTF8Str(target) + data: UTF8Str(data)]; +} + +static void +cdataBlockFunction(void *ctx, const char *value, int len) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER cdataBlock: UTF8StrLen(value, len)]; +} + +static void +commentFunction(void *ctx, const char *value) +{ + NSCAssert(ctx,@"No Context"); + [HANDLER comment: UTF8Str(value)]; +} + +static void +warningFunction(void *ctx, const char *msg, ...) +{ + char allMsg[2048]; + va_list args; + int lineNumber=-1; + int colNumber=-1; + + va_start(args, msg); + vsprintf(allMsg, msg, args); + va_end(args); + + NSCAssert(ctx,@"No Context"); + lineNumber=getLineNumber(ctx); + colNumber=getColumnNumber(ctx); + [HANDLER warning: UTF8Str(allMsg) + colNumber:colNumber + lineNumber:lineNumber]; +} + +static void +errorFunction(void *ctx, const char *msg, ...) +{ + char allMsg[2048]; + va_list args; + int lineNumber=-1; + int colNumber=-1; + + va_start(args, msg); + vsprintf(allMsg, msg, args); + va_end(args); + NSCAssert(ctx,@"No Context"); + lineNumber=getLineNumber(ctx); + colNumber=getColumnNumber(ctx); + [HANDLER error: UTF8Str(allMsg) + colNumber:colNumber + lineNumber:lineNumber]; +} + +static void +fatalErrorFunction(void *ctx, const char *msg, ...) +{ + char allMsg[2048]; + va_list args; + int lineNumber=-1; + int colNumber=-1; + + va_start(args, msg); + vsprintf(allMsg, msg, args); + va_end(args); + NSCAssert(ctx, @"No Context"); + lineNumber=getLineNumber(ctx); + colNumber=getColumnNumber(ctx); + [HANDLER fatalError: UTF8Str(allMsg) + colNumber:colNumber + lineNumber:lineNumber]; +} + +#undef HANDLER + +/** + * Create a new SAX handler. + */ ++ (GSSAXHandler*) handler +{ + return AUTORELEASE([[self alloc] init]); +} + +- (id) init +{ + NSAssert(lib == 0, @"Already created lib"); + self = [super init]; + if (self != nil) + { + if ([self _initLibXML] == NO) + { + NSLog(@"GSSAXHandler: out of memory\n"); + RELEASE(self); + return nil; + } + } + return self; +} + +/** + * Return pointer to xmlSAXHandler structure. + */ +- (void*) lib +{ + return lib; +} + +/** + * Return the parser object with which this handler is + * associated. This may occasionally be useful. + */ +- (GSXMLParser*) parser +{ + return parser; +} + +- (void) dealloc +{ + if (parser == nil && lib != NULL) + { + free(lib); + } + [super dealloc]; +} + +/** + * Called when the document starts being processed. + */ +- (void) startDocument +{ +} + +/** + * Called when the document end has been detected. + */ +- (void) endDocument +{ +} + +/** + * Called to detemrine if the document is standalone. + */ +- (int) isStandalone +{ + return 1; +} + +/** + * Called when an opening tag has been processed. + */ +- (void) startElement: (NSString*)elementName + attributes: (NSMutableDictionary*)elementAttributes +{ +} + +/** + * Called when a closing tag has been processed. + */ +- (void) endElement: (NSString*) elementName +{ +} + +/** + * Handle an attribute that has been read by the parser. + */ +- (void) attribute: (NSString*) name value: (NSString*)value +{ +} + +/** + * Receiving some chars from the parser. + */ +- (void) characters: (NSString*) name +{ +} + +/** + * Receiving some ignorable whitespaces from the parser. + */ +- (void) ignoreWhitespace: (NSString*) ch +{ +} + +/** + * A processing instruction has been parsed. + */ +- (void) processInstruction: (NSString*)targetName data: (NSString*)PIdata +{ +} + +/** + * A comment has been parsed. + */ +- (void) comment: (NSString*) value +{ +} + +/** + * Called when a pcdata block has been parsed. + */ +- (void) cdataBlock: (NSString*)value +{ +} + +/** + * Called to return the filenmae from which an entity should be loaded. + */ +- (NSString*) loadEntity: (NSString*)publicId + at: (NSString*)location +{ + return nil; +} + +/** + * An old global namespace has been parsed. + */ +- (void) namespaceDecl: (NSString*)name + href: (NSString*)href + prefix: (NSString*)prefix +{ +} + +/** + * What to do when a notation declaration has been parsed. + */ +- (void) notationDecl: (NSString*)name + public: (NSString*)publicId + system: (NSString*)systemId +{ +} + +/** + * An entity definition has been parsed. + */ +- (void) entityDecl: (NSString*)name + type: (int)type + public: (NSString*)publicId + system: (NSString*)systemId + content: (NSString*)content +{ +} + +/** + * An attribute definition has been parsed. + */ +- (void) attributeDecl: (NSString*)nameElement + nameAttribute: (NSString*)name + entityType: (int)type + typeDefValue: (int)defType + defaultValue: (NSString*)value +{ +} + +/** + * An element definition has been parsed. + */ +- (void) elementDecl: (NSString*)name + type: (int)type +{ +} + +/** + * What to do when an unparsed entity declaration is parsed. + */ +- (void) unparsedEntityDecl: (NSString*)name + publicEntity: (NSString*)publicId + systemEntity: (NSString*)systemId + notationName: (NSString*)notation +{ +} + +/** + * Called when an entity reference is detected. + */ +- (void) reference: (NSString*) name +{ +} + +/** + * An old global namespace has been parsed. + */ +- (void) globalNamespace: (NSString*)name + href: (NSString*)href + prefix: (NSString*)prefix +{ +} + +/** + * Called when a warning message needs to be output. + */ +- (void) warning: (NSString*)e +{ +} + +/** + * Called when an error message needs to be output. + */ +- (void) error: (NSString*)e +{ +} + +/** + * Called when a fatal error message needs to be output. + */ +- (void) fatalError: (NSString*)e +{ +} + +/** + * Called when a warning message needs to be output. + */ +- (void) warning: (NSString*)e + colNumber: (int)colNumber + lineNumber: (int)lineNumber +{ + [self warning:e]; +} + +/** + * Called when an error message needs to be output. + */ +- (void) error: (NSString*)e + colNumber: (int)colNumber + lineNumber: (int)lineNumber +{ + [self error:e]; +} + +/** + * Called when a fatal error message needs to be output. + */ +- (void) fatalError: (NSString*)e + colNumber: (int)colNumber + lineNumber: (int)lineNumber +{ + [self fatalError:e]; +} + +/** + * Called to find out whether there is an internal subset. + */ +- (int) hasInternalSubset +{ + return 0; +} + +/** + * Called to find out whether there is an internal subset. + */ +- (BOOL) internalSubset: (NSString*)name + externalID: (NSString*)externalID + systemID: (NSString*)systemID +{ + return NO; +} + +/** + * Called to find out whether there is an external subset. + */ +- (int) hasExternalSubset +{ + return 0; +} + +/** + * Called to find out whether there is an external subset. + */ +- (BOOL) externalSubset: (NSString*)name + externalID: (NSString*)externalID + ystemID: (NSString*)systemID +{ + return NO; +} + +/** + * get an entity by name + */ +- (void*) getEntity: (NSString*)name +{ + return 0; +} + +/** + * get a aparameter entity by name + */ +- (void*) getParameterEntity: (NSString*)name +{ + return 0; +} + +/* + * Private methods - internal use only. + */ +- (BOOL) _initLibXML +{ + lib = (xmlSAXHandler*)malloc(sizeof(xmlSAXHandler)); + if (lib == NULL) + return NO; + else + { + memcpy(lib, &xmlDefaultSAXHandler, sizeof(htmlSAXHandler)); + +#define LIB ((xmlSAXHandlerPtr)lib) + LIB->internalSubset = (void*) internalSubsetFunction; + LIB->externalSubset = (void*) externalSubsetFunction; + LIB->isStandalone = (void*) isStandaloneFunction; + LIB->hasInternalSubset = (void*) hasInternalSubsetFunction; + LIB->hasExternalSubset = (void*) hasExternalSubsetFunction; + LIB->getEntity = (void*) getEntityFunction; + LIB->entityDecl = (void*) entityDeclFunction; + LIB->notationDecl = (void*) notationDeclFunction; + LIB->attributeDecl = (void*) attributeDeclFunction; + LIB->elementDecl = (void*) elementDeclFunction; + LIB->unparsedEntityDecl = (void*) unparsedEntityDeclFunction; + LIB->startDocument = (void*) startDocumentFunction; + LIB->endDocument = (void*) endDocumentFunction; + LIB->startElement = (void*) startElementFunction; + LIB->endElement = (void*) endElementFunction; + LIB->reference = (void*) referenceFunction; + LIB->characters = (void*) charactersFunction; + LIB->ignorableWhitespace = (void*) ignorableWhitespaceFunction; + LIB->processingInstruction = (void*) processInstructionFunction; + LIB->comment = (void*) commentFunction; + LIB->warning = (void*) warningFunction; + LIB->error = (void*) errorFunction; + LIB->fatalError = (void*) fatalErrorFunction; + LIB->getParameterEntity = (void*) getParameterEntityFunction; + LIB->cdataBlock = (void*) cdataBlockFunction; +#undef LIB + return YES; + } +} + +- (void) _setParser: (GSXMLParser*)value +{ + parser = value; +} +@end + +@implementation GSHTMLSAXHandler +- (BOOL) _initLibXML +{ + lib = (xmlSAXHandler*)malloc(sizeof(htmlSAXHandler)); + if (lib == NULL) + return NO; + else + { + memcpy(lib, &xmlDefaultSAXHandler, sizeof(htmlSAXHandler)); + +#define LIB ((htmlSAXHandlerPtr)lib) + LIB->internalSubset = (void*) internalSubsetFunction; + LIB->externalSubset = (void*) externalSubsetFunction; + LIB->isStandalone = (void*) isStandaloneFunction; + LIB->hasInternalSubset = (void*) hasInternalSubsetFunction; + LIB->hasExternalSubset = (void*) hasExternalSubsetFunction; + LIB->getEntity = (void*) getEntityFunction; + LIB->entityDecl = (void*) entityDeclFunction; + LIB->notationDecl = (void*) notationDeclFunction; + LIB->attributeDecl = (void*) attributeDeclFunction; + LIB->elementDecl = (void*) elementDeclFunction; + LIB->unparsedEntityDecl = (void*) unparsedEntityDeclFunction; + LIB->startDocument = (void*) startDocumentFunction; + LIB->endDocument = (void*) endDocumentFunction; + LIB->startElement = (void*) startElementFunction; + LIB->endElement = (void*) endElementFunction; + LIB->reference = (void*) referenceFunction; + LIB->characters = (void*) charactersFunction; + LIB->ignorableWhitespace = (void*) ignorableWhitespaceFunction; + LIB->processingInstruction = (void*) processInstructionFunction; + LIB->comment = (void*) commentFunction; + LIB->warning = (void*) warningFunction; + LIB->error = (void*) errorFunction; + LIB->fatalError = (void*) fatalErrorFunction; + LIB->getParameterEntity = (void*) getParameterEntityFunction; + LIB->cdataBlock = (void*) cdataBlockFunction; +#undef LIB + return YES; + } +} +@end + +#else + +#include +#include + +/* + * Build dummy implementations of the classes if libxml is not available + */ +@interface GSXMLDummy : NSObject +@end +@interface GSXMLAttribute : GSXMLDummy +@end +@interface GSXMLDocument : GSXMLDummy +@end +@interface GSXMLHandler : GSXMLDummy +@end +@interface GSXMLNamespace : GSXMLDummy +@end +@interface GSXMLNode : GSXMLDummy +@end +@interface GSSAXHandler : GSXMLDummy +@end +@interface GSXMLParser : GSXMLDummy +@end +@implementation GSXMLDummy ++ (id) allocWithZone: (NSZone*)z +{ + NSLog(@"Not built with libxml ... %@ unusable in %@", + NSStringFromClass(self), NSStringFromSelector(_cmd)); + return nil; +} ++ (void) forwardInvocation: (NSInvocation*)anInvocation +{ + NSLog(@"Not built with libxml ... %@ unusable in %@", + NSStringFromClass([self class]), + NSStringFromSelector([anInvocation selector])); + return; +} +- (id) init +{ + NSLog(@"Not built with libxml ... %@ unusable in %@", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)); + RELEASE(self); + return nil; +} +- (id) initWithCoder: (NSCoder*)aCoder +{ + NSLog(@"Not built with libxml ... %@ unusable in %@", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)); + RELEASE(self); + return nil; +} +@end +@implementation GSXMLAttribute +@end +@implementation GSXMLDocument +@end +@implementation GSXMLHandler +@end +@implementation GSXMLNamespace +@end +@implementation GSXMLNode +@end +@implementation GSSAXHandler +@end +@implementation GSXMLParser +@end +#endif + diff --git a/Source/Additions/Makefile.preamble b/Source/Additions/Makefile.preamble new file mode 100644 index 000000000..8b515699a --- /dev/null +++ b/Source/Additions/Makefile.preamble @@ -0,0 +1,56 @@ +# +# Makefile.preamble +# +# Copyright (C) 1997 Free Software Foundation, Inc. +# +# Author: Scott Christley +# +# This file is part of the GNUstep Base Library. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 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 +# Library General Public License for more details. +# +# If you are interested in a warranty or support for this source code, +# contact Scott Christley at scottc@net-community.com +# +# You should have received a copy of the GNU Library General Public +# License along with this library; see the file COPYING.LIB. +# If not, write to the Free Software Foundation, +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# Makefile.preamble +# +# Project specific makefile variables +# +# Do not put any Makefile rules in this file, instead they should +# be put into Makefile.postamble. +# + +# +# Flags dealing with compiling and linking +# + +# Additional flags to pass to the preprocessor +ADDITIONAL_CPPFLAGS = $(DEFS) $(CONFIG_SYSTEM_DEFS) -Wall + +# Additional flags to pass to the Objective-C compiler +ADDITIONAL_OBJCFLAGS = + +# Additional flags to pass to the C compiler +ADDITIONAL_CFLAGS = + +# Additional include directories the compiler should search +ADDITIONAL_INCLUDE_DIRS = -I../../Headers/gnustep -I../../Headers \ + -I../$(GNUSTEP_TARGET_DIR) + +# Additional LDFLAGS to pass to the linker +ADDITIONAL_LDFLAGS = + diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 1e24a62f9..f41118ba6 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -45,6 +45,9 @@ else LIBRARY_NAME=libgnustep-base endif +libgnustep-base_SUBPROJECTS=Additions +libgnustep-baseadd_SUBPROJECTS=Additions + # GNUSTEP_INSTALL_PREFIX must be defined here and not in config.h because # the installing person may set it on the `make' command line. @@ -219,10 +222,6 @@ NSZone.m \ externs.m \ objc-load.m -ifneq ($(add),yes) -BASE_MFILES += GSMime.m GSXML.m -endif - ifeq ($(WITH_FFI),libffi) GNU_MFILES += cifframe.m BASE_MFILES += GSFFIInvocation.m @@ -355,10 +354,6 @@ libgnustep-base_C_FILES = $(GNU_CFILES) libgnustep-base_OBJC_FILES = $(GNU_MFILES) \ $(BASE_MFILES) $(NSVALUE_MFILES) $(NSNUMBER_MFILES) -ifeq ($(add),yes) -libgnustep-baseadd_OBJC_FILES = GSMime.m GSXML.m -endif - # Extra DLL exports file libgnustep-base_DLL_DEF = libgnustep-base.def diff --git a/Source/Makefile.preamble b/Source/Makefile.preamble index a4dd85e60..43a031a9d 100644 --- a/Source/Makefile.preamble +++ b/Source/Makefile.preamble @@ -68,10 +68,6 @@ libgnustep-base_LIBRARIES_DEPEND_UPON += $(CONFIG_SYSTEM_LIBS) \ -flat_namespace endif -ifeq ($(add),yes) -libgnustep-base_LIBRARIES_DEPEND_UPON += -lgnustep-baseadd -endif - # Flags dealing with installing and uninstalling #