/** Interface for NSPropertyList for GNUstep Copyright (C) 2003,2004 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Fred Kiefer 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. */ #include "config.h" #include #include "GNUstepBase/preface.h" #include "GNUstepBase/GSMime.h" #include "Foundation/NSArray.h" #include "Foundation/NSAutoreleasePool.h" #include "Foundation/NSByteOrder.h" #include "Foundation/NSCalendarDate.h" #include "Foundation/NSCharacterSet.h" #include "Foundation/NSData.h" #include "Foundation/NSDictionary.h" #include "Foundation/NSException.h" #include "Foundation/NSPropertyList.h" #include "Foundation/NSSerialization.h" #include "Foundation/NSString.h" #include "Foundation/NSTimeZone.h" #include "Foundation/NSUserDefaults.h" #include "Foundation/NSValue.h" #include "Foundation/NSDebug.h" #include "GNUstepBase/Unicode.h" #include "GSPrivate.h" extern BOOL GSScanDouble(unichar*, unsigned, double*); #ifndef UINT_MAX # define UINT_MAX 4294967295U #endif @class GSString; @class GSMutableString; @class GSMutableArray; @class GSMutableDictionary; @interface GSBinaryPLParser : NSObject { NSPropertyListMutabilityOptions mutability; const unsigned char *_bytes; NSData *data; unsigned offset_size; // Number of bytes per table entry unsigned index_size; // Number of bytes per table entry unsigned table_start; // Start address of object table unsigned table_len; // Length of object table } - (id) initWithData: (NSData*)plData mutability: (NSPropertyListMutabilityOptions)m; - (id) rootObject; - (id) objectAtIndex: (unsigned)index; @end @interface BinaryPLGenerator : NSObject { NSMutableData *dest; NSMutableArray *objectList; NSMutableArray *objectsToDoList; id root; // Number of bytes per object table index unsigned int index_size; // Number of bytes per object table entry unsigned int offset_size; unsigned int table_start; unsigned int table_size; unsigned int *table; } + (void) serializePropertyList: (id)aPropertyList intoData: (NSMutableData *)destination; - (id) initWithPropertyList: (id)aPropertyList intoData: (NSMutableData *)destination; - (void) generate; - (void) storeObject: (id)object; - (void) cleanup; @end /* * Cache classes and method implementations for speed. */ static Class NSDataClass; static Class NSStringClass; static Class NSMutableStringClass; static Class GSStringClass; static Class GSMutableStringClass; static Class plArray; static id (*plAdd)(id, SEL, id) = 0; static Class plDictionary; static id (*plSet)(id, SEL, id, id) = 0; #define IS_BIT_SET(a,i) ((((a) & (1<<(i)))) > 0) static unsigned const char *hexdigitsBitmapRep = NULL; #define GS_IS_HEXDIGIT(X) IS_BIT_SET(hexdigitsBitmapRep[(X)/8], (X) % 8) static void setupHexdigits(void) { if (hexdigitsBitmapRep == NULL) { NSCharacterSet *hexdigits; NSData *bitmap; hexdigits = [NSCharacterSet characterSetWithCharactersInString: @"0123456789abcdefABCDEF"]; bitmap = RETAIN([hexdigits bitmapRepresentation]); hexdigitsBitmapRep = [bitmap bytes]; } } static NSCharacterSet *quotables = nil; static NSCharacterSet *oldQuotables = nil; static NSCharacterSet *xmlQuotables = nil; static unsigned const char *quotablesBitmapRep = NULL; #define GS_IS_QUOTABLE(X) IS_BIT_SET(quotablesBitmapRep[(X)/8], (X) % 8) static void setupQuotables(void) { if (quotablesBitmapRep == NULL) { NSMutableCharacterSet *s; NSData *bitmap; s = [[NSCharacterSet characterSetWithCharactersInString: @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" @"abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^"] mutableCopy]; [s invert]; quotables = [s copy]; RELEASE(s); bitmap = RETAIN([quotables bitmapRepresentation]); quotablesBitmapRep = [bitmap bytes]; s = [[NSCharacterSet characterSetWithCharactersInString: @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" @"abcdefghijklmnopqrstuvwxyz$./_"] mutableCopy]; [s invert]; oldQuotables = [s copy]; RELEASE(s); s = [[NSCharacterSet characterSetWithCharactersInString: @"&<>'\\\""] mutableCopy]; [s addCharactersInRange: NSMakeRange(0x0001, 0x001f)]; [s removeCharactersInRange: NSMakeRange(0x0009, 0x0002)]; [s removeCharactersInRange: NSMakeRange(0x000D, 0x0001)]; [s addCharactersInRange: NSMakeRange(0xD800, 0x07FF)]; [s addCharactersInRange: NSMakeRange(0xFFFE, 0x0002)]; xmlQuotables = [s copy]; RELEASE(s); } } static unsigned const char *whitespaceBitmapRep = NULL; #define GS_IS_WHITESPACE(X) IS_BIT_SET(whitespaceBitmapRep[(X)/8], (X) % 8) static void setupWhitespace(void) { if (whitespaceBitmapRep == NULL) { NSCharacterSet *whitespace; NSData *bitmap; /* We can not use whitespaceAndNewlineCharacterSet here as this would lead to a recursion, as this also reads in a property list. whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; */ whitespace = [NSCharacterSet characterSetWithCharactersInString: @" \t\r\n\f\b"]; bitmap = RETAIN([whitespace bitmapRepresentation]); whitespaceBitmapRep = [bitmap bytes]; } } #ifdef HAVE_LIBXML #include "GNUstepBase/GSXML.h" static int XML_ELEMENT_NODE; #endif #define inrange(ch,min,max) ((ch)>=(min) && (ch)<=(max)) #define char2num(ch) \ inrange(ch,'0','9') \ ? ((ch)-0x30) \ : (inrange(ch,'a','f') \ ? ((ch)-0x57) : ((ch)-0x37)) typedef struct { const unsigned char *ptr; unsigned end; unsigned pos; unsigned lin; NSString *err; NSPropertyListMutabilityOptions opt; BOOL key; BOOL old; } pldata; /* * Property list parsing - skip whitespace keeping count of lines and * regarding objective-c style comments as whitespace. * Returns YES if there is any non-whitespace text remaining. */ static BOOL skipSpace(pldata *pld) { unsigned char c; while (pld->pos < pld->end) { c = pld->ptr[pld->pos]; if (GS_IS_WHITESPACE(c) == NO) { if (c == '/' && pld->pos < pld->end - 1) { /* * Check for comments beginning '/' followed by '/' or '*' */ if (pld->ptr[pld->pos + 1] == '/') { pld->pos += 2; while (pld->pos < pld->end) { c = pld->ptr[pld->pos]; if (c == '\n') { break; } pld->pos++; } if (pld->pos >= pld->end) { pld->err = @"reached end of string in comment"; return NO; } } else if (pld->ptr[pld->pos + 1] == '*') { pld->pos += 2; while (pld->pos < pld->end) { c = pld->ptr[pld->pos]; if (c == '\n') { pld->lin++; } else if (c == '*' && pld->pos < pld->end - 1 && pld->ptr[pld->pos+1] == '/') { pld->pos++; /* Skip past '*' */ break; } pld->pos++; } if (pld->pos >= pld->end) { pld->err = @"reached end of string in comment"; return NO; } } else { return YES; } } else { return YES; } } if (c == '\n') { pld->lin++; } pld->pos++; } pld->err = @"reached end of string"; return NO; } static inline id parseQuotedString(pldata* pld) { unsigned start = ++pld->pos; unsigned escaped = 0; unsigned shrink = 0; BOOL hex = NO; NSString *obj; while (pld->pos < pld->end) { unsigned char c = pld->ptr[pld->pos]; if (escaped) { if (escaped == 1 && c >= '0' && c <= '7') { escaped = 2; hex = NO; } else if (escaped == 1 && (c == 'u' || c == 'U')) { escaped = 2; hex = YES; } else if (escaped > 1) { if (hex && GS_IS_HEXDIGIT(c)) { shrink++; escaped++; if (escaped == 6) { escaped = 0; } } else if (c >= '0' && c <= '7') { shrink++; escaped++; if (escaped == 4) { escaped = 0; } } else { pld->pos--; escaped = 0; } } else { escaped = 0; } } else { if (c == '\\') { escaped = 1; shrink++; } else if (c == '"') { break; } } if (c == '\n') pld->lin++; pld->pos++; } if (pld->pos >= pld->end) { pld->err = @"reached end of string while parsing quoted string"; return nil; } if (pld->pos - start - shrink == 0) { obj = @""; } else { unsigned length; unichar *chars; unichar *temp = NULL; unsigned int temp_length = 0; unsigned j; unsigned k; if (!GSToUnicode(&temp, &temp_length, &pld->ptr[start], pld->pos - start, NSUTF8StringEncoding, NSDefaultMallocZone(), 0)) { pld->err = @"invalid utf8 data while parsing quoted string"; return nil; } length = temp_length - shrink; chars = NSZoneMalloc(NSDefaultMallocZone(), sizeof(unichar) * length); escaped = 0; hex = NO; for (j = 0, k = 0; j < temp_length; j++) { unichar c = temp[j]; if (escaped) { if (escaped == 1 && c >= '0' && c <= '7') { chars[k] = c - '0'; hex = NO; escaped++; } else if (escaped == 1 && (c == 'u' || c == 'U')) { chars[k] = 0; hex = YES; escaped++; } else if (escaped > 1) { if (hex && GS_IS_HEXDIGIT(c)) { chars[k] <<= 4; chars[k] |= char2num(c); escaped++; if (escaped == 6) { escaped = 0; k++; } } else if (c >= '0' && c <= '7') { chars[k] <<= 3; chars[k] |= (c - '0'); escaped++; if (escaped == 4) { escaped = 0; k++; } } else { escaped = 0; j--; k++; } } else { escaped = 0; switch (c) { case 'a' : chars[k] = '\a'; break; case 'b' : chars[k] = '\b'; break; case 't' : chars[k] = '\t'; break; case 'r' : chars[k] = '\r'; break; case 'n' : chars[k] = '\n'; break; case 'v' : chars[k] = '\v'; break; case 'f' : chars[k] = '\f'; break; default : chars[k] = c; break; } k++; } } else { chars[k] = c; if (c == '\\') { escaped = 1; } else { k++; } } } NSZoneFree(NSDefaultMallocZone(), temp); length = k; if (pld->key == NO && pld->opt == NSPropertyListMutableContainersAndLeaves) { obj = [GSMutableString alloc]; obj = [obj initWithCharactersNoCopy: chars length: length freeWhenDone: YES]; } else { obj = [GSMutableString alloc]; obj = [obj initWithCharactersNoCopy: chars length: length freeWhenDone: YES]; } } pld->pos++; return obj; } static inline id parseUnquotedString(pldata *pld) { unsigned start = pld->pos; unsigned i; unsigned length; id obj; unichar *chars; while (pld->pos < pld->end) { if (GS_IS_QUOTABLE(pld->ptr[pld->pos]) == YES) break; pld->pos++; } length = pld->pos - start; chars = NSZoneMalloc(NSDefaultMallocZone(), sizeof(unichar) * length); for (i = 0; i < length; i++) { chars[i] = pld->ptr[start + i]; } if (pld->key == NO && pld->opt == NSPropertyListMutableContainersAndLeaves) { obj = [GSMutableString alloc]; obj = [obj initWithCharactersNoCopy: chars length: length freeWhenDone: YES]; } else { obj = [GSMutableString alloc]; obj = [obj initWithCharactersNoCopy: chars length: length freeWhenDone: YES]; } return obj; } static id parsePlItem(pldata* pld) { id result = nil; BOOL start = (pld->pos == 0 ? YES : NO); if (skipSpace(pld) == NO) { return nil; } switch (pld->ptr[pld->pos]) { case '{': { NSMutableDictionary *dict; dict = [[plDictionary allocWithZone: NSDefaultMallocZone()] initWithCapacity: 0]; pld->pos++; while (skipSpace(pld) == YES && pld->ptr[pld->pos] != '}') { id key; id val; pld->key = YES; key = parsePlItem(pld); pld->key = NO; if (key == nil) { return nil; } if (skipSpace(pld) == NO) { RELEASE(key); RELEASE(dict); return nil; } if (pld->ptr[pld->pos] != '=') { pld->err = @"unexpected character (wanted '=')"; RELEASE(key); RELEASE(dict); return nil; } pld->pos++; val = parsePlItem(pld); if (val == nil) { RELEASE(key); RELEASE(dict); return nil; } if (skipSpace(pld) == NO) { RELEASE(key); RELEASE(val); RELEASE(dict); return nil; } if (pld->ptr[pld->pos] == ';') { pld->pos++; } else if (pld->ptr[pld->pos] != '}') { pld->err = @"unexpected character (wanted ';' or '}')"; RELEASE(key); RELEASE(val); RELEASE(dict); return nil; } (*plSet)(dict, @selector(setObject:forKey:), val, key); RELEASE(key); RELEASE(val); } if (pld->pos >= pld->end) { pld->err = @"unexpected end of string when parsing dictionary"; RELEASE(dict); return nil; } pld->pos++; result = dict; if (pld->opt == NSPropertyListImmutable) { [result makeImmutableCopyOnFail: NO]; } } break; case '(': { NSMutableArray *array; array = [[plArray allocWithZone: NSDefaultMallocZone()] initWithCapacity: 0]; pld->pos++; while (skipSpace(pld) == YES && pld->ptr[pld->pos] != ')') { id val; val = parsePlItem(pld); if (val == nil) { RELEASE(array); return nil; } if (skipSpace(pld) == NO) { RELEASE(val); RELEASE(array); return nil; } if (pld->ptr[pld->pos] == ',') { pld->pos++; } else if (pld->ptr[pld->pos] != ')') { pld->err = @"unexpected character (wanted ',' or ')')"; RELEASE(val); RELEASE(array); return nil; } (*plAdd)(array, @selector(addObject:), val); RELEASE(val); } if (pld->pos >= pld->end) { pld->err = @"unexpected end of string when parsing array"; RELEASE(array); return nil; } pld->pos++; result = array; if (pld->opt == NSPropertyListImmutable) { [result makeImmutableCopyOnFail: NO]; } } break; case '<': pld->pos++; if (pld->pos < pld->end && pld->ptr[pld->pos] == '*') { const unsigned char *ptr; unsigned min; unsigned len = 0; unsigned i; pld->old = NO; pld->pos++; min = pld->pos; ptr = &(pld->ptr[min]); while (pld->pos < pld->end && pld->ptr[pld->pos] != '>') { pld->pos++; } len = pld->pos - min; if (len > 1) { unsigned char type = *ptr++; len--; if (type == 'I') { char buf[len+1]; for (i = 0; i < len; i++) buf[i] = (char)ptr[i]; buf[len] = '\0'; result = [[NSNumber alloc] initWithLong: atol(buf)]; } else if (type == 'B') { if (ptr[0] == 'Y') { result = [[NSNumber alloc] initWithBool: YES]; } else if (ptr[0] == 'N') { result = [[NSNumber alloc] initWithBool: NO]; } else { pld->err = @"bad value for bool"; return nil; } } else if (type == 'D') { unichar buf[len]; unsigned i; NSString *str; for (i = 0; i < len; i++) buf[i] = ptr[i]; str = [[NSString alloc] initWithCharacters: buf length: len]; result = [[NSCalendarDate alloc] initWithString: str calendarFormat: @"%Y-%m-%d %H:%M:%S %z"]; RELEASE(str); } else if (type == 'R') { unichar buf[len]; double d = 0.0; for (i = 0; i < len; i++) buf[i] = ptr[i]; GSScanDouble(buf, len, &d); result = [[NSNumber alloc] initWithDouble: d]; } else { pld->err = @"unrecognized type code after '<*'"; return nil; } } else { pld->err = @"missing type code after '<*'"; return nil; } if (pld->pos >= pld->end) { pld->err = @"unexpected end of string when parsing data"; return nil; } if (pld->ptr[pld->pos] != '>') { pld->err = @"unexpected character (wanted '>')"; return nil; } pld->pos++; } else { NSMutableData *data; unsigned max = pld->end - 1; unsigned char buf[BUFSIZ]; unsigned len = 0; data = [[NSMutableData alloc] initWithCapacity: 0]; skipSpace(pld); while (pld->pos < max && GS_IS_HEXDIGIT(pld->ptr[pld->pos]) && GS_IS_HEXDIGIT(pld->ptr[pld->pos+1])) { unsigned char byte; byte = (char2num(pld->ptr[pld->pos])) << 4; pld->pos++; byte |= char2num(pld->ptr[pld->pos]); pld->pos++; buf[len++] = byte; if (len == sizeof(buf)) { [data appendBytes: buf length: len]; len = 0; } skipSpace(pld); } if (pld->pos >= pld->end) { pld->err = @"unexpected end of string when parsing data"; RELEASE(data); return nil; } if (pld->ptr[pld->pos] != '>') { pld->err = @"unexpected character (wanted '>')"; RELEASE(data); return nil; } if (len > 0) { [data appendBytes: buf length: len]; } pld->pos++; // FIXME ... should be immutable sometimes. result = data; } break; case '"': result = parseQuotedString(pld); break; default: result = parseUnquotedString(pld); break; } if (start == YES && result != nil) { if (skipSpace(pld) == YES) { pld->err = @"extra data after parsed string"; result = nil; // Not at end of string. } } return result; } #ifdef HAVE_LIBXML static GSXMLNode* elementNode(GSXMLNode* node) { while (node != nil) { if ([node type] == XML_ELEMENT_NODE) { break; } node = [node next]; } return node; } static id nodeToObject(GSXMLNode* node, NSPropertyListMutabilityOptions o, NSString **e) { CREATE_AUTORELEASE_POOL(arp); id result = nil; node = elementNode(node); if (node != nil) { NSString *name; NSString *content; GSXMLNode *children; BOOL isKey = NO; name = [node name]; children = [node firstChild]; content = [children content]; children = elementNode(children); isKey = [name isEqualToString: @"key"]; if (isKey == YES || [name isEqualToString: @"string"] == YES) { if (content == nil) { content = @""; } else { NSRange r; r = [content rangeOfString: @"\\"]; if (r.length == 1) { unsigned len = [content length]; unichar buf[len]; unsigned pos = r.location; [content getCharacters: buf]; while (pos < len) { if (++pos < len) { if ((buf[pos] == 'u' || buf[pos] == 'U') && (len >= pos + 4)) { unichar val = 0; unsigned i; BOOL ok = YES; for (i = 1; i < 5; i++) { unichar c = buf[pos + i]; if (c >= '0' && c <= '9') { val = (val << 4) + c - '0'; } else if (c >= 'A' && c <= 'F') { val = (val << 4) + c - 'A' + 10; } else if (c >= 'a' && c <= 'f') { val = (val << 4) + c - 'a' + 10; } else { ok = NO; } } if (ok == YES) { len -= 5; memcpy(&buf[pos], &buf[pos+5], (len - pos) * sizeof(unichar)); buf[pos - 1] = val; } } while (pos < len && buf[pos] != '\\') { pos++; } } } if (isKey == NO && o == NSPropertyListMutableContainersAndLeaves) { content = [NSMutableString stringWithCharacters: buf length: len]; } else { content = [NSString stringWithCharacters: buf length: len]; } } } result = content; } else if ([name isEqualToString: @"true"]) { result = [NSNumber numberWithBool: YES]; } else if ([name isEqualToString: @"false"]) { result = [NSNumber numberWithBool: NO]; } else if ([name isEqualToString: @"integer"]) { if (content == nil) { content = @"0"; } result = [NSNumber numberWithInt: [content intValue]]; } else if ([name isEqualToString: @"real"]) { if (content == nil) { content = @"0.0"; } result = [NSNumber numberWithDouble: [content doubleValue]]; } else if ([name isEqualToString: @"date"]) { if (content == nil) { content = @""; } if ([content hasSuffix: @"Z"] == YES && [content length] == 20) { result = [NSCalendarDate dateWithString: content calendarFormat: @"%Y-%m-%dT%H:%M:%SZ"]; } else { result = [NSCalendarDate dateWithString: content calendarFormat: @"%Y-%m-%d %H:%M:%S %z"]; } } else if ([name isEqualToString: @"data"]) { result = [GSMimeDocument decodeBase64: [content dataUsingEncoding: NSASCIIStringEncoding]]; if (o == NSPropertyListMutableContainersAndLeaves) { result = AUTORELEASE([result mutableCopy]); } } // container class else if ([name isEqualToString: @"array"]) { NSMutableArray *container = [plArray array]; while (children != nil) { id val; val = nodeToObject(children, o, e); [container addObject: val]; children = [children nextElement]; } result = container; if (o == NSPropertyListImmutable) { [result makeImmutableCopyOnFail: NO]; } } else if ([name isEqualToString: @"dict"]) { NSMutableDictionary *container = [plDictionary dictionary]; while (children != nil) { NSString *key; id val; key = nodeToObject(children, o, e); children = [children nextElement]; val = nodeToObject(children, o, e); children = [children nextElement]; [container setObject: val forKey: key]; } result = container; if (o == NSPropertyListImmutable) { [result makeImmutableCopyOnFail: NO]; } } } RETAIN(result); RELEASE(arp); return AUTORELEASE(result); } #endif id GSPropertyListFromStringsFormat(NSString *string) { NSMutableDictionary *dict; pldata _pld; pldata *pld = &_pld; NSData *d; /* * An empty string is a nil property list. */ if ([string length] == 0) { return nil; } d = [string dataUsingEncoding: NSUTF8StringEncoding]; NSCAssert(d, @"Couldn't get utf8 data from string."); _pld.ptr = (unsigned char*)[d bytes]; _pld.pos = 0; _pld.end = [d length]; _pld.err = nil; _pld.lin = 0; _pld.opt = NSPropertyListImmutable; _pld.key = NO; _pld.old = YES; // OpenStep style [NSPropertyListSerialization class]; // initialise dict = [[plDictionary allocWithZone: NSDefaultMallocZone()] initWithCapacity: 0]; while (skipSpace(pld) == YES) { id key; id val; if (pld->ptr[pld->pos] == '"') { key = parseQuotedString(pld); } else { key = parseUnquotedString(pld); } if (key == nil) { DESTROY(dict); break; } if (skipSpace(pld) == NO) { pld->err = @"incomplete final entry (no semicolon?)"; RELEASE(key); DESTROY(dict); break; } if (pld->ptr[pld->pos] == ';') { pld->pos++; (*plSet)(dict, @selector(setObject:forKey:), @"", key); RELEASE(key); } else if (pld->ptr[pld->pos] == '=') { pld->pos++; if (skipSpace(pld) == NO) { RELEASE(key); DESTROY(dict); break; } if (pld->ptr[pld->pos] == '"') { val = parseQuotedString(pld); } else { val = parseUnquotedString(pld); } if (val == nil) { RELEASE(key); DESTROY(dict); break; } if (skipSpace(pld) == NO) { pld->err = @"missing final semicolon"; RELEASE(key); RELEASE(val); DESTROY(dict); break; } (*plSet)(dict, @selector(setObject:forKey:), val, key); RELEASE(key); RELEASE(val); if (pld->ptr[pld->pos] == ';') { pld->pos++; } else { pld->err = @"unexpected character (wanted ';')"; DESTROY(dict); break; } } else { pld->err = @"unexpected character (wanted '=' or ';')"; RELEASE(key); DESTROY(dict); break; } } if (dict == nil && _pld.err != nil) { RELEASE(dict); [NSException raise: NSGenericException format: @"Parse failed at line %d (char %d) - %@", _pld.lin + 1, _pld.pos + 1, _pld.err]; } return AUTORELEASE(dict); } #include static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void encodeBase64(NSData *source, NSMutableData *dest) { int length = [source length]; int enclen = length / 3; int remlen = length - 3 * enclen; int destlen = 4 * ((length + 2) / 3); unsigned char *sBuf; unsigned char *dBuf; int sIndex = 0; int dIndex = [dest length]; [dest setLength: dIndex + destlen]; if (length == 0) { return; } sBuf = (unsigned char*)[source bytes]; dBuf = [dest mutableBytes]; for (sIndex = 0; sIndex < length - 2; sIndex += 3, dIndex += 4) { dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; dBuf[dIndex + 1] = base64[((sBuf[sIndex] << 4) | (sBuf[sIndex + 1] >> 4)) & 0x3f]; dBuf[dIndex + 2] = base64[((sBuf[sIndex + 1] << 2) | (sBuf[sIndex + 2] >> 6)) & 0x3f]; dBuf[dIndex + 3] = base64[sBuf[sIndex + 2] & 0x3f]; } if (remlen == 1) { dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30; dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]]; dBuf[dIndex + 2] = '='; dBuf[dIndex + 3] = '='; } else if (remlen == 2) { dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30; dBuf[dIndex + 1] |= sBuf[sIndex + 1] >> 4; dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]]; dBuf[dIndex + 2] = (sBuf[sIndex + 1] << 2) & 0x3c; dBuf[dIndex + 2] = base64[dBuf[dIndex + 2]]; dBuf[dIndex + 3] = '='; } } static inline void Append(void *bytes, unsigned length, NSMutableData *dst) { [dst appendBytes: bytes length: length]; } /* * Output a string escaped for OpenStep style property lists. * The result is ascii data. */ static void PString(NSString *obj, NSMutableData *output) { unsigned length; if ((length = [obj length]) == 0) { [output appendBytes: "\"\"" length: 2]; } else if ([obj rangeOfCharacterFromSet: oldQuotables].length > 0 || [obj characterAtIndex: 0] == '/') { unichar tmp[length <= 1024 ? length : 0]; unichar *ustring; unichar *from; unichar *end; unsigned char *ptr; int base = [output length]; int len = 0; if (length <= 1024) { ustring = tmp; } else { ustring = NSZoneMalloc(NSDefaultMallocZone(), length*sizeof(unichar)); } end = &ustring[length]; [obj getCharacters: ustring]; for (from = ustring; from < end; from++) { switch (*from) { case '\t': case '\r': case '\n': len++; break; case '\a': case '\b': case '\v': case '\f': case '\\': case '"' : len += 2; break; default: if (*from < 128) { if (isprint(*from) || *from == ' ') { len++; } else { len += 4; } } else { len += 6; } break; } } [output setLength: base + len + 2]; ptr = [output mutableBytes] + base; *ptr++ = '"'; for (from = ustring; from < end; from++) { switch (*from) { case '\t': case '\r': case '\n': *ptr++ = *from; break; case '\a': *ptr++ = '\\'; *ptr++ = 'a'; break; case '\b': *ptr++ = '\\'; *ptr++ = 'b'; break; case '\v': *ptr++ = '\\'; *ptr++ = 'v'; break; case '\f': *ptr++ = '\\'; *ptr++ = 'f'; break; case '\\': *ptr++ = '\\'; *ptr++ = '\\'; break; case '"' : *ptr++ = '\\'; *ptr++ = '"'; break; default: if (*from < 128) { if (isprint(*from) || *from == ' ') { *ptr++ = *from; } else { unichar c = *from; *ptr++ = '\\'; ptr[2] = (c & 7) + '0'; c >>= 3; ptr[1] = (c & 7) + '0'; c >>= 3; ptr[0] = (c & 7) + '0'; ptr += 3; } } else { unichar c = *from; *ptr++ = '\\'; *ptr++ = 'U'; ptr[3] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[2] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[1] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[0] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; ptr += 4; } break; } } *ptr++ = '"'; if (ustring != tmp) { NSZoneFree(NSDefaultMallocZone(), ustring); } } else { NSData *d = [obj dataUsingEncoding: NSASCIIStringEncoding]; [output appendData: d]; } } /* * Output a string escaped for use in xml. * Result is utf8 data. */ static void XString(NSString* obj, NSMutableData *output) { static char *hexdigits = "0123456789ABCDEF"; unsigned end; end = [obj length]; if (end == 0) { return; } if ([obj rangeOfCharacterFromSet: xmlQuotables].length > 0) { unichar *base; unichar *map; unichar c; unsigned len; unsigned rpos; unsigned wpos; base = NSZoneMalloc(NSDefaultMallocZone(), end * sizeof(unichar)); [obj getCharacters: base]; for (len = rpos = 0; rpos < end; rpos++) { c = base[rpos]; switch (c) { case '&': len += 5; break; case '<': case '>': len += 4; break; case '\'': case '"': len += 6; break; default: if ((c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D)) || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD) { len += 6; } else { len++; } break; } } map = NSZoneMalloc(NSDefaultMallocZone(), len * sizeof(unichar)); for (wpos = rpos = 0; rpos < end; rpos++) { c = base[rpos]; switch (c) { case '&': map[wpos++] = '&'; map[wpos++] = 'a'; map[wpos++] = 'm'; map[wpos++] = 'p'; map[wpos++] = ';'; break; case '<': map[wpos++] = '&'; map[wpos++] = 'l'; map[wpos++] = 't'; map[wpos++] = ';'; break; case '>': map[wpos++] = '&'; map[wpos++] = 'g'; map[wpos++] = 't'; map[wpos++] = ';'; break; case '\'': map[wpos++] = '&'; map[wpos++] = 'a'; map[wpos++] = 'p'; map[wpos++] = 'o'; map[wpos++] = 's'; map[wpos++] = ';'; break; case '"': map[wpos++] = '&'; map[wpos++] = 'q'; map[wpos++] = 'u'; map[wpos++] = 'o'; map[wpos++] = 't'; map[wpos++] = ';'; break; default: if ((c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D)) || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD) { map[wpos++] = '\\'; map[wpos++] = 'U'; map[wpos++] = hexdigits[(c>>12) & 0xf]; map[wpos++] = hexdigits[(c>>8) & 0xf]; map[wpos++] = hexdigits[(c>>4) & 0xf]; map[wpos++] = hexdigits[c & 0xf]; } else { map[wpos++] = c; } break; } } NSZoneFree(NSDefaultMallocZone(), base); obj = [[NSString alloc] initWithCharacters: map length: len]; [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]]; RELEASE(obj); } else { [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]]; } } static const char *indentStrings[] = { "", " ", " ", " ", "\t", "\t ", "\t ", "\t ", "\t\t", "\t\t ", "\t\t ", "\t\t ", "\t\t\t", "\t\t\t ", "\t\t\t ", "\t\t\t ", "\t\t\t\t", "\t\t\t\t ", "\t\t\t\t ", "\t\t\t\t ", "\t\t\t\t\t", "\t\t\t\t\t ", "\t\t\t\t\t ", "\t\t\t\t\t ", "\t\t\t\t\t\t" }; /** * obj is the object to be written out
* loc is the locale for formatting (or nil to indicate no formatting)
* lev is the level of indentation to use
* step is the indentation step (0 == 0, 1 = 2, 2 = 4, 3 = 8)
* x is an indicator for xml or old/new openstep property list format
* dest is the output buffer. */ static void OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, NSPropertyListFormat x, NSMutableData *dest) { if ([obj isKindOfClass: [NSString class]]) { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "" length: 8]; XString(obj, dest); [dest appendBytes: "\n" length: 10]; } else { PString(obj, dest); } } else if ([obj isKindOfClass: [NSNumber class]]) { const char *t = [obj objCType]; if (*t == 'c' || *t == 'C') { BOOL val = [obj boolValue]; if (val == YES) { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "\n" length: 8]; } else if (x == NSPropertyListGNUstepFormat) { [dest appendBytes: "<*BY>\n" length: 6]; } else { PString([obj description], dest); } } else { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "\n" length: 9]; } else if (x == NSPropertyListGNUstepFormat) { [dest appendBytes: "<*BN>\n" length: 6]; } else { PString([obj description], dest); } } } else if (strchr("sSiIlLqQ", *t) != 0) { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "" length: 9]; XString([obj stringValue], dest); [dest appendBytes: "\n" length: 11]; } else if (x == NSPropertyListGNUstepFormat) { [dest appendBytes: "<*I" length: 3]; PString([obj stringValue], dest); [dest appendBytes: ">" length: 1]; } else { PString([obj description], dest); } } else { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "" length: 6]; XString([obj stringValue], dest); [dest appendBytes: "\n" length: 8]; } else if (x == NSPropertyListGNUstepFormat) { [dest appendBytes: "<*R" length: 3]; PString([obj stringValue], dest); [dest appendBytes: ">" length: 1]; } else { PString([obj description], dest); } } } else if ([obj isKindOfClass: [NSData class]]) { if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "\n" length: 7]; encodeBase64(obj, dest); [dest appendBytes: "\n" length: 8]; } else { const unsigned char *src; unsigned char *dst; int length; int i; int j; src = [obj bytes]; length = [obj length]; #define num2char(num) ((num) < 0xa ? ((num)+'0') : ((num)+0x57)) j = [dest length]; [dest setLength: j + 2*length+(length > 4 ? (length-1)/4+2 : 2)]; dst = [dest mutableBytes]; dst[j++] = '<'; for (i = 0; i < length; i++, j++) { dst[j++] = num2char((src[i]>>4) & 0x0f); dst[j] = num2char(src[i] & 0x0f); if ((i & 3) == 3 && i < length-1) { /* if we've just finished a 32-bit int, print a space */ dst[++j] = ' '; } } dst[j++] = '>'; } } else if ([obj isKindOfClass: [NSDate class]]) { static NSTimeZone *z = nil; if (z == nil) { z = RETAIN([NSTimeZone timeZoneForSecondsFromGMT: 0]); } if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "" length: 6]; obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%dT%H:%M:%SZ" timeZone: z locale: nil]; obj = [obj dataUsingEncoding: NSASCIIStringEncoding]; [dest appendData: obj]; [dest appendBytes: "\n" length: 8]; } else if (x == NSPropertyListGNUstepFormat) { [dest appendBytes: "<*D" length: 3]; obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" timeZone: z locale: nil]; obj = [obj dataUsingEncoding: NSASCIIStringEncoding]; [dest appendData: obj]; [dest appendBytes: ">" length: 1]; } else { obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" timeZone: z locale: nil]; PString(obj, dest); } } else if ([obj isKindOfClass: [NSArray class]]) { const char *iBaseString; const char *iSizeString; unsigned level = lev; if (level*step < sizeof(indentStrings)/sizeof(id)) { iBaseString = indentStrings[level*step]; } else { iBaseString = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; } level++; if (level*step < sizeof(indentStrings)/sizeof(id)) { iSizeString = indentStrings[level*step]; } else { iSizeString = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; } if (x == NSPropertyListXMLFormat_v1_0) { NSEnumerator *e; [dest appendBytes: "\n" length: 8]; e = [obj objectEnumerator]; while ((obj = [e nextObject])) { [dest appendBytes: iSizeString length: strlen(iSizeString)]; OAppend(obj, loc, level, step, x, dest); } [dest appendBytes: iBaseString length: strlen(iBaseString)]; [dest appendBytes: "\n" length: 9]; } else { unsigned count = [obj count]; unsigned last = count - 1; NSString *plists[count]; unsigned i; if ([obj isProxy] == YES) { for (i = 0; i < count; i++) { plists[i] = [obj objectAtIndex: i]; } } else { [obj getObjects: plists]; } if (loc == nil) { [dest appendBytes: "(" length: 1]; for (i = 0; i < count; i++) { id item = plists[i]; OAppend(item, nil, 0, step, x, dest); if (i != last) { [dest appendBytes: ", " length: 2]; } } [dest appendBytes: ")" length: 1]; } else { [dest appendBytes: "(\n" length: 2]; for (i = 0; i < count; i++) { id item = plists[i]; [dest appendBytes: iSizeString length: strlen(iSizeString)]; OAppend(item, loc, level, step, x, dest); if (i == last) { [dest appendBytes: "\n" length: 1]; } else { [dest appendBytes: ",\n" length: 2]; } } [dest appendBytes: iBaseString length: strlen(iBaseString)]; [dest appendBytes: ")" length: 1]; } } } else if ([obj isKindOfClass: [NSDictionary class]]) { const char *iBaseString; const char *iSizeString; SEL objSel = @selector(objectForKey:); IMP myObj = [obj methodForSelector: objSel]; unsigned i; NSArray *keyArray = [obj allKeys]; unsigned numKeys = [keyArray count]; NSString *plists[numKeys]; NSString *keys[numKeys]; BOOL canCompare = YES; Class lastClass = 0; unsigned level = lev; BOOL isProxy = [obj isProxy]; if (level*step < sizeof(indentStrings)/sizeof(id)) { iBaseString = indentStrings[level*step]; } else { iBaseString = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; } level++; if (level*step < sizeof(indentStrings)/sizeof(id)) { iSizeString = indentStrings[level*step]; } else { iSizeString = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; } if (isProxy == YES) { for (i = 0; i < numKeys; i++) { keys[i] = [keyArray objectAtIndex: i]; } } else { [keyArray getObjects: keys]; } for (i = 0; i < numKeys; i++) { if (GSObjCClass(keys[i]) == lastClass) continue; if ([keys[i] respondsToSelector: @selector(compare:)] == NO) { canCompare = NO; break; } lastClass = GSObjCClass(keys[i]); } if (canCompare == YES) { #define STRIDE_FACTOR 3 unsigned c,d, stride; BOOL found; NSComparisonResult (*comp)(id, SEL, id) = 0; unsigned int count = numKeys; #ifdef GSWARN BOOL badComparison = NO; #endif stride = 1; while (stride <= count) { stride = stride * STRIDE_FACTOR + 1; } lastClass = 0; while (stride > (STRIDE_FACTOR - 1)) { // loop to sort for each value of stride stride = stride / STRIDE_FACTOR; for (c = stride; c < count; c++) { found = NO; if (stride > c) { break; } d = c - stride; while (!found) { id a = keys[d + stride]; id b = keys[d]; Class x; NSComparisonResult r; x = GSObjCClass(a); if (x != lastClass) { lastClass = x; comp = (NSComparisonResult (*)(id, SEL, id)) [a methodForSelector: @selector(compare:)]; } r = (*comp)(a, @selector(compare:), b); if (r < 0) { #ifdef GSWARN if (r != NSOrderedAscending) { badComparison = YES; } #endif keys[d + stride] = b; keys[d] = a; if (stride > d) { break; } d -= stride; } else { #ifdef GSWARN if (r != NSOrderedDescending && r != NSOrderedSame) { badComparison = YES; } #endif found = YES; } } } } #ifdef GSWARN if (badComparison == YES) { NSWarnFLog(@"Detected bad return value from comparison"); } #endif } if (isProxy == YES) { for (i = 0; i < numKeys; i++) { plists[i] = [(NSDictionary*)obj objectForKey: keys[i]]; } } else { for (i = 0; i < numKeys; i++) { plists[i] = (*myObj)(obj, objSel, keys[i]); } } if (x == NSPropertyListXMLFormat_v1_0) { [dest appendBytes: "\n" length: 7]; for (i = 0; i < numKeys; i++) { [dest appendBytes: iSizeString length: strlen(iSizeString)]; [dest appendBytes: "" length: 5]; XString(keys[i], dest); [dest appendBytes: "\n" length: 7]; [dest appendBytes: iSizeString length: strlen(iSizeString)]; OAppend(plists[i], loc, level, step, x, dest); } [dest appendBytes: iBaseString length: strlen(iBaseString)]; [dest appendBytes: "\n" length: 8]; } else if (loc == nil) { [dest appendBytes: "{" length: 1]; for (i = 0; i < numKeys; i++) { OAppend(keys[i], nil, 0, step, x, dest); [dest appendBytes: " = " length: 3]; OAppend(plists[i], nil, 0, step, x, dest); [dest appendBytes: "; " length: 2]; } [dest appendBytes: "}" length: 1]; } else { [dest appendBytes: "{\n" length: 2]; for (i = 0; i < numKeys; i++) { [dest appendBytes: iSizeString length: strlen(iSizeString)]; OAppend(keys[i], loc, level, step, x, dest); [dest appendBytes: " = " length: 3]; OAppend(plists[i], loc, level, step, x, dest); [dest appendBytes: ";\n" length: 2]; } [dest appendBytes: iBaseString length: strlen(iBaseString)]; [dest appendBytes: "}" length: 1]; } } else { NSString *cls; if (obj == nil) { obj = @"(nil)"; cls = @"(nil)"; } else { cls = NSStringFromClass([obj class]); } if (x == NSPropertyListXMLFormat_v1_0) { NSDebugLog(@"Non-property-list class (%@) encoded as string", cls); [dest appendBytes: "" length: 8]; XString([obj description], dest); [dest appendBytes: "" length: 9]; } else { NSDebugLog(@"Non-property-list class (%@) encoded as string", cls); PString([obj description], dest); } } } @implementation NSPropertyListSerialization static BOOL classInitialized = NO; + (void) initialize { if (classInitialized == NO) { classInitialized = YES; #ifdef HAVE_LIBXML /* * Cache XML node information. */ XML_ELEMENT_NODE = [GSXMLNode typeFromDescription: @"XML_ELEMENT_NODE"]; #endif NSStringClass = [NSString class]; NSMutableStringClass = [NSMutableString class]; NSDataClass = [NSData class]; GSStringClass = [GSString class]; GSMutableStringClass = [GSMutableString class]; plArray = [GSMutableArray class]; plAdd = (id (*)(id, SEL, id)) [plArray instanceMethodForSelector: @selector(addObject:)]; plDictionary = [GSMutableDictionary class]; plSet = (id (*)(id, SEL, id, id)) [plDictionary instanceMethodForSelector: @selector(setObject:forKey:)]; setupHexdigits(); setupQuotables(); setupWhitespace(); } } + (NSData*) dataFromPropertyList: (id)aPropertyList format: (NSPropertyListFormat)aFormat errorDescription: (NSString**)anErrorString { NSMutableData *dest; NSDictionary *loc; int step = 2; loc = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; dest = [NSMutableData dataWithCapacity: 1024]; if (aFormat == NSPropertyListXMLFormat_v1_0) { const char *prefix = "\n\n" "\n"; [dest appendBytes: prefix length: strlen(prefix)]; OAppend(aPropertyList, loc, 0, step > 3 ? 3 : step, aFormat, dest); [dest appendBytes: "" length: 8]; } else if (aFormat == NSPropertyListGNUstepBinaryFormat) { [NSSerializer serializePropertyList: aPropertyList intoData: dest]; } else if (aFormat == NSPropertyListBinaryFormat_v1_0) { [BinaryPLGenerator serializePropertyList: aPropertyList intoData: dest]; } else { OAppend(aPropertyList, loc, 0, step > 3 ? 3 : step, aFormat, dest); } return dest; } void GSPropertyListMake(id obj, NSDictionary *loc, BOOL xml, BOOL forDescription, unsigned step, id *str) { NSString *tmp; NSPropertyListFormat style; NSMutableData *dest; if (classInitialized == NO) { [NSPropertyListSerialization class]; } if (*str == nil) { *str = AUTORELEASE([GSMutableString new]); } else if (GSObjCClass(*str) != [GSMutableString class]) { [NSException raise: NSInvalidArgumentException format: @"Illegal object (%@) at argument 0", *str]; } if (forDescription) { style = NSPropertyListOpenStepFormat; } else if (xml == YES) { style = NSPropertyListXMLFormat_v1_0; } else if (GSUserDefaultsFlag(NSWriteOldStylePropertyLists)) { style = NSPropertyListOpenStepFormat; } else { style = NSPropertyListGNUstepFormat; } dest = [NSMutableData dataWithCapacity: 1024]; if (style == NSPropertyListXMLFormat_v1_0) { const char *prefix = "\n\n" "\n"; [dest appendBytes: prefix length: strlen(prefix)]; OAppend(obj, loc, 0, step > 3 ? 3 : step, style, dest); [dest appendBytes: "" length: 8]; } else { OAppend(obj, loc, 0, step > 3 ? 3 : step, style, dest); } tmp = [[NSString alloc] initWithData: dest encoding: NSASCIIStringEncoding]; [*str appendString: tmp]; RELEASE(tmp); } + (BOOL) propertyList: (id)aPropertyList isValidForFormat: (NSPropertyListFormat)aFormat { // FIXME ... need to check properly. switch (aFormat) { case NSPropertyListGNUstepFormat: return YES; case NSPropertyListGNUstepBinaryFormat: return YES; case NSPropertyListOpenStepFormat: return YES; case NSPropertyListXMLFormat_v1_0: return YES; case NSPropertyListBinaryFormat_v1_0: return YES; default: [NSException raise: NSInvalidArgumentException format: @"[%@ +%@]: unsupported format", NSStringFromClass(self), NSStringFromSelector(_cmd)]; return NO; } } + (id) propertyListFromData: (NSData*)data mutabilityOption: (NSPropertyListMutabilityOptions)anOption format: (NSPropertyListFormat*)aFormat errorDescription: (NSString**)anErrorString { NSPropertyListFormat format = 0; NSString *error = nil; id result = nil; const unsigned char *bytes = 0; unsigned int length = 0; if (data == nil) { error = @"nil data argument passed to method"; } else if ([data isKindOfClass: [NSData class]] == NO) { error = @"non-NSData data argument passed to method"; } else if ([data length] == 0) { error = @"empty data argument passed to method"; } else { bytes = [data bytes]; length = [data length]; if (length > 0 && memcmp(bytes, "bplist00", 8) == 0) { format = NSPropertyListBinaryFormat_v1_0; } else if (bytes[0] == 0 || bytes[0] == 1) { format = NSPropertyListGNUstepBinaryFormat; } else { unsigned int index = 0; // Skip any leading white space. while (index < length && GS_IS_WHITESPACE(bytes[index]) == YES) { index++; } if (length - index > 2 && bytes[index] == '<' && bytes[index+1] == '?') { // It begins with ' 4) { [NSException raise: NSGenericException format: @"Unknown table size %d", offset_size]; DESTROY(self); // Bad format } else if (index_size < 1 || index_size > 4) { [NSException raise: NSGenericException format: @"Unknown table size %d", index_size]; DESTROY(self); // Bad format } else if (table_start > length - 32) { DESTROY(self); // Bad format } else { table_len = length - table_start - 32; ASSIGN(data, plData); _bytes = (const unsigned char*)[data bytes]; mutability = m; } } return self; } - (unsigned long) offsetForIndex: (unsigned)index { if (index > table_len) { [NSException raise: NSRangeException format: @"Object table index out of bounds %d.", index]; } if (offset_size == 1) { unsigned char offset; [data getBytes: &offset range: NSMakeRange(table_start + index, 1)]; return offset; } else if (offset_size == 2) { unsigned short offset; [data getBytes: &offset range: NSMakeRange(table_start + 2*index, 2)]; return NSSwapBigShortToHost(offset); } else { unsigned char buffer[offset_size]; int i; unsigned long num = 0; [data getBytes: &buffer range: NSMakeRange(table_start + offset_size*index, offset_size)]; for (i = 0; i < offset_size; i++) { num = (num << 8) + buffer[i]; } return num; } return 0; } - (unsigned) readObjectIndexAt: (unsigned*)counter { if (index_size == 1) { unsigned char oid; [data getBytes: &oid range: NSMakeRange(*counter,1)]; *counter += 1; return oid; } else if (index_size == 2) { unsigned short oid; [data getBytes: &oid range: NSMakeRange(*counter, 2)]; *counter += 2; return NSSwapBigShortToHost(oid); } else { unsigned char buffer[index_size]; int i; unsigned num = 0; [data getBytes: &buffer range: NSMakeRange(*counter, index_size)]; *counter += index_size; for (i = 0; i < index_size; i++) { num = (num << 8) + buffer[i]; } return num; } return 0; } - (unsigned long) readCountAt: (unsigned*) counter { unsigned char c; [data getBytes: &c range: NSMakeRange(*counter,1)]; *counter += 1; if (c == 0x10) { unsigned char count; [data getBytes: &count range: NSMakeRange(*counter,1)]; *counter += 1; return count; } else if (c == 0x11) { unsigned short count; [data getBytes: &count range: NSMakeRange(*counter,2)]; *counter += 2; return NSSwapBigShortToHost(count); } else if ((c > 0x11) && (c <= 0x13)) { unsigned len = 1 << (c - 0x10); unsigned char buffer[len]; int i; unsigned long num = 0; [data getBytes: &buffer range: NSMakeRange(*counter, len)]; *counter += len; for (i = 0; i < len; i++) { num = (num << 8) + buffer[i]; } return num; } else { //FIXME [NSException raise: NSGenericException format: @"Unknown count type %d", c]; } return 0; } - (id) rootObject { return [self objectAtIndex: 0]; } - (id) objectAtIndex: (unsigned)index { unsigned char next; unsigned counter = [self offsetForIndex: index]; id result = nil; [data getBytes: &next range: NSMakeRange(counter,1)]; //NSLog(@"read object %d at index %d type %d", index, counter, next); counter += 1; if (next == 0x08) { // NO result = [NSNumber numberWithBool: NO]; } else if (next == 0x09) { // YES result = [NSNumber numberWithBool: YES]; } else if ((next >= 0x10) && (next < 0x17)) { // integer number unsigned len = 1 << (next - 0x10); unsigned long long num = 0; unsigned i; unsigned char buffer[16]; [data getBytes: buffer range: NSMakeRange(counter, len)]; for (i = 0; i < len; i++) { num = (num << 8) + buffer[i]; } if (next == 0x10) { result = [NSNumber numberWithUnsignedChar: (unsigned char)num]; } else if (next == 0x11) { result = [NSNumber numberWithUnsignedShort: (unsigned short)num]; } else if ((next == 0x12) || (next == 13)) { result = [NSNumber numberWithUnsignedInt: (unsigned int)num]; } else { result = [NSNumber numberWithUnsignedLongLong: num]; } } else if (next == 0x22) { // float number NSSwappedFloat in; [data getBytes: &in range: NSMakeRange(counter, sizeof(float))]; result = [NSNumber numberWithFloat: NSSwapBigFloatToHost(in)]; } else if (next == 0x23) { // double number NSSwappedDouble in; [data getBytes: &in range: NSMakeRange(counter, sizeof(double))]; result = [NSNumber numberWithDouble: NSSwapBigDoubleToHost(in)]; } else if (next == 0x33) { double in; // Date NSDate *date; [data getBytes: &in range: NSMakeRange(counter, sizeof(double))]; date = [NSDate dateWithTimeIntervalSinceReferenceDate: NSSwapBigDoubleToHost(in)]; result = date; } else if ((next >= 0x40) && (next < 0x4F)) { // short data unsigned len = next - 0x40; if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableData dataWithBytes: _bytes + counter length: len]; } else { result = [data subdataWithRange: NSMakeRange(counter, len)]; } } else if (next == 0x4F) { // long data unsigned long len; len = [self readCountAt: &counter]; if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableData dataWithBytes: _bytes + counter length: len]; } else { result = [data subdataWithRange: NSMakeRange(counter, len)]; } } else if ((next >= 0x50) && (next < 0x5F)) { // Short string unsigned len = next - 0x50; unsigned char buffer[len+1]; [data getBytes: buffer range: NSMakeRange(counter, len)]; buffer[len] = '\0'; if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableString stringWithUTF8String: buffer]; } else { result = [NSString stringWithUTF8String: buffer]; } } else if (next == 0x5F) { // long string unsigned long len; char *buffer; len = [self readCountAt: &counter]; buffer = NSZoneMalloc(NSDefaultMallocZone(), len+1); [data getBytes: buffer range: NSMakeRange(counter, len)]; buffer[len] = '\0'; if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableString stringWithUTF8String: buffer]; } else { result = [NSString stringWithUTF8String: buffer]; } NSZoneFree(NSDefaultMallocZone(), buffer); } else if ((next >= 0x60) && (next < 0x6F)) { // Short unicode string unsigned len = next - 0x60; unsigned i; unichar buffer[len]; [data getBytes: buffer range: NSMakeRange(counter, sizeof(unichar)*len)]; for (i = 0; i < len; i++) { buffer[i] = NSSwapBigShortToHost(buffer[i]); } if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableString stringWithCharacters: buffer length: len]; } else { result = [NSString stringWithCharacters: buffer length: len]; } } else if (next == 0x6F) { // long unicode string unsigned long len; unsigned i; unichar *buffer; len = [self readCountAt: &counter]; buffer = NSZoneMalloc(NSDefaultMallocZone(), sizeof(unichar)*len); [data getBytes: buffer range: NSMakeRange(counter, sizeof(unichar)*len)]; for (i = 0; i < len; i++) { buffer[i] = NSSwapBigShortToHost(buffer[i]); } if (mutability == NSPropertyListMutableContainersAndLeaves) { result = [NSMutableString stringWithCharacters: buffer length: len]; } else { result = [NSString stringWithCharacters: buffer length: len]; } NSZoneFree(NSDefaultMallocZone(), buffer); } else if (next == 0x80) { unsigned char index; [data getBytes: &index range: NSMakeRange(counter,1)]; result = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: index] forKey: @"CF$UID"]; } else if (next == 0x81) { unsigned short index; [data getBytes: &index range: NSMakeRange(counter,2)]; index = NSSwapBigShortToHost(index); result = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: index] forKey: @"CF$UID"]; } else if ((next >= 0xA0) && (next < 0xAF)) { // short array unsigned len = next - 0xA0; unsigned i; id objects[len]; for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; objects[i] = [self objectAtIndex: oid]; } if (mutability == NSPropertyListMutableContainersAndLeaves || mutability == NSPropertyListMutableContainers) { result = [NSMutableArray arrayWithObjects: objects count: len]; } else { result = [NSArray arrayWithObjects: objects count: len]; } } else if (next == 0xAF) { // big array unsigned long len; unsigned i; id *objects; len = [self readCountAt: &counter]; objects = NSZoneMalloc(NSDefaultMallocZone(), sizeof(id) * len); for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; objects[i] = [self objectAtIndex: oid]; } if (mutability == NSPropertyListMutableContainersAndLeaves || mutability == NSPropertyListMutableContainers) { result =[NSMutableArray arrayWithObjects: objects count: len]; } else { result =[NSArray arrayWithObjects: objects count: len]; } NSZoneFree(NSDefaultMallocZone(), objects); } else if ((next >= 0xD0) && (next < 0xDF)) { // dictionary unsigned len = next - 0xD0; unsigned i; id keys[len]; id values[len]; for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; keys[i] = [self objectAtIndex: oid]; } for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; values[i] = [self objectAtIndex: oid]; } if (mutability == NSPropertyListMutableContainersAndLeaves || mutability == NSPropertyListMutableContainers) { result = [NSMutableDictionary dictionaryWithObjects: values forKeys: keys count: len]; } else { result = [NSDictionary dictionaryWithObjects: values forKeys: keys count: len]; } } else if (next == 0xDF) { // big dictionary unsigned long len; unsigned i; id *keys; id *values; len = [self readCountAt: &counter]; keys = NSZoneMalloc(NSDefaultMallocZone(), sizeof(id)*len); values = NSZoneMalloc(NSDefaultMallocZone(), sizeof(id)*len); for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; keys[i] = [self objectAtIndex: oid]; } for (i = 0; i < len; i++) { int oid = [self readObjectIndexAt: &counter]; values[i] = [self objectAtIndex: oid]; } if (mutability == NSPropertyListMutableContainersAndLeaves || mutability == NSPropertyListMutableContainers) { result = [NSMutableDictionary dictionaryWithObjects: values forKeys: keys count: len]; } else { result = [NSDictionary dictionaryWithObjects: values forKeys: keys count: len]; } NSZoneFree(NSDefaultMallocZone(), values); NSZoneFree(NSDefaultMallocZone(), keys); } else { [NSException raise: NSGenericException format: @"Unknown control byte = %d", next]; } return result; } @end @implementation BinaryPLGenerator + (void) serializePropertyList: (id)aPropertyList intoData: (NSMutableData *)destination { BinaryPLGenerator *gen; gen = [[BinaryPLGenerator alloc] initWithPropertyList: aPropertyList intoData: destination]; [gen generate]; RELEASE(gen); } - (id) initWithPropertyList: (id) aPropertyList intoData: (NSMutableData *)destination { ASSIGN(root, aPropertyList); ASSIGN(dest, destination); return self; } - (void) dealloc { RELEASE(root); [self cleanup]; [super dealloc]; } - (NSData*) data { return dest; } - (void) setup { if (index_size == 1) { table_size = 256; } else if (index_size == 2) { table_size = 256 * 256; } else if (index_size == 3) { table_size = 256 * 256 * 256; } else if (index_size == 4) { table_size = UINT_MAX; } table = malloc(table_size * sizeof(int)); // Always restart the destination data [dest setLength: 0]; objectsToDoList = [[NSMutableArray alloc] init]; objectList = [[NSMutableArray alloc] init]; [objectsToDoList addObject: root]; [objectList addObject: root]; } - (void) cleanup { DESTROY(dest); DESTROY(objectsToDoList); DESTROY(objectList); if (table != NULL) { free(table); table = NULL; } } - (void) writeObjects { id object; const char *prefix = "bplist00"; [dest appendBytes: prefix length: strlen(prefix)]; while ([objectsToDoList count] != 0) { object = [objectsToDoList objectAtIndex: 0]; [self storeObject: object]; [objectsToDoList removeObjectAtIndex: 0]; } } - (void) markOffset: (unsigned int) offset for: (id)object { unsigned int oid; oid = [objectList indexOfObject: object]; if (oid == NSNotFound) { [NSException raise: NSGenericException format: @"Unknown object %@.", object]; } if (oid >= table_size) { [NSException raise: NSRangeException format: @"Object table index out of bounds %d.", oid]; } table[oid] = offset; } - (void) writeObjectTable { unsigned int size; unsigned int len; unsigned int i; unsigned char *buffer; unsigned int last_offset; table_start = [dest length]; // This is a bit to much, as the length // of the last object is added. last_offset = table_start; if (last_offset < 256) { offset_size = 1; } else if (last_offset < 256 * 256) { offset_size = 2; } else if (last_offset < 256 * 256 * 256) { offset_size = 3; } else if (last_offset <= UINT_MAX) { offset_size = 4; } else { [NSException raise: NSRangeException format: @"Object table offset out of bounds %d.", last_offset]; } len = [objectList count]; size = offset_size * len; buffer = malloc(size); if (offset_size == 1) { for (i = 0; i < len; i++) { unsigned char ci; ci = table[i]; buffer[i] = ci; } } else if (offset_size == 2) { for (i = 0; i < len; i++) { unsigned short si; si = table[i]; buffer[2 * i] = (si >> 8); buffer[2 * i + 1] = si % 256; } } else if (offset_size == 3) { for (i = 0; i < len; i++) { unsigned int si; si = table[i]; buffer[3 * i] = (si >> 16); buffer[3 * i + 1] = (si >> 8) % 256; buffer[3 * i + 2] = si % 256; } } else if (offset_size == 4) { for (i = 0; i < len; i++) { unsigned int si; si = table[i]; buffer[4 * i] = (si >> 24); buffer[4 * i + 1] = (si >> 16) % 256; buffer[4 * i + 2] = (si >> 8) % 256; buffer[4 * i + 3] = si % 256; } } [dest appendBytes: buffer length: size]; free(buffer); } - (void) writeMetaData { unsigned char meta[32]; unsigned int i; unsigned int len; for (i = 0; i < 32; i++) { meta[i] = 0; } meta[6] = offset_size; meta[7] = index_size; len = [objectList count]; meta[12] = (len >> 24); meta[13] = (len >> 16) % 256; meta[14] = (len >> 8) % 256; meta[15] = len % 256; meta[28] = (table_start >> 24); meta[29] = (table_start >> 16) % 256; meta[30] = (table_start >> 8) % 256; meta[31] = table_start % 256; [dest appendBytes: meta length: 32]; } - (unsigned int) indexForObject: (id)object { unsigned int index; index = [objectList indexOfObject: object]; if (index == NSNotFound) { index = [objectList count]; [objectList addObject: object]; [objectsToDoList addObject: object]; } return index; } - (void) storeIndex: (unsigned int)index { if (index_size == 1) { unsigned char oid; oid = index; [dest appendBytes: &oid length: 1]; } else if (index_size == 2) { unsigned short oid; oid = NSSwapHostShortToBig(index); [dest appendBytes: &oid length: 2]; } else if (index_size == 4) { unsigned int oid; oid = NSSwapHostIntToBig(index); [dest appendBytes: &oid length: 4]; } else { [NSException raise: NSGenericException format: @"Unknown table size %d", index_size]; } } - (void) storeCount: (unsigned int)count { unsigned char code; if (count <= 256) { unsigned char c; code = 0x10; [dest appendBytes: &code length: 1]; c = count; [dest appendBytes: &c length: 1]; } else { unsigned short c; code = 0x11; [dest appendBytes: &code length: 1]; c = count; NSSwapHostShortToBig(c); [dest appendBytes: &c length: 2]; } } - (void) storeData: (NSData*) data { unsigned int len; unsigned char code; len = [data length]; if (len < 0x0F) { code = 0x40 + len; [dest appendBytes: &code length: 1]; [dest appendData: data]; } else { code = 0x4F; [dest appendBytes: &code length: 1]; [self storeCount: len]; [dest appendData: data]; } } - (void) storeString: (NSString*) string { unsigned int len; BOOL ascii = YES; unsigned char code; unsigned int i; unichar uchar; len = [string length]; for (i = 0; i < len; i++) { uchar = [string characterAtIndex: i]; if (uchar > 127) { ascii = NO; break; } } if (ascii) { if (len < 0x0F) { code = 0x50 + len; [dest appendBytes: &code length: 1]; [dest appendBytes: [string cString] length: len]; } else { code = 0x5F; [dest appendBytes: &code length: 1]; [self storeCount: len]; [dest appendBytes: [string cString] length: len]; } } else { if (len < 0x0F) { unichar buffer[len + 1]; int i; code = 0x60 + len; [dest appendBytes: &code length: 1]; [string getCharacters: buffer]; for (i = 0; i < len; i++) { buffer[i] = NSSwapHostShortToBig(buffer[i]); } [dest appendBytes: buffer length: len * sizeof(unichar)]; } else { unichar *buffer; code = 0x6F; [dest appendBytes: &code length: 1]; buffer = malloc(sizeof(unichar)*(len + 1)); [self storeCount: len]; [string getCharacters: buffer]; for (i = 0; i < len; i++) { buffer[i] = NSSwapHostShortToBig(buffer[i]); } [dest appendBytes: buffer length: sizeof(unichar)*len]; free(buffer); } } } - (void) storeNumber: (NSNumber*) number { const char *type; unsigned char code; type = [number objCType]; switch (*type) { case 'c': case 'C': case 's': case 'S': case 'i': case 'I': case 'l': case 'L': case 'q': case 'Q': { unsigned long long val; val = [number unsignedLongLongValue]; // FIXME: We need a better way to determine boolean values! if ((val == 0) && ((*type == 'c') || (*type == 'C'))) { code = 0x08; [dest appendBytes: &code length: 1]; } else if ((val == 1) && ((*type == 'c') || (*type == 'C'))) { code = 0x09; [dest appendBytes: &code length: 1]; } else if (val < 256) { unsigned char cval; code = 0x10; [dest appendBytes: &code length: 1]; cval = (unsigned char) val; [dest appendBytes: &cval length: 1]; } else if (val < 256 * 256) { unsigned short sval; code = 0x11; [dest appendBytes: &code length: 1]; sval = NSSwapHostShortToBig([number unsignedShortValue]); [dest appendBytes: &sval length: 2]; } else if (val <= UINT_MAX) { unsigned int ival; code = 0x12; [dest appendBytes: &code length: 1]; ival = NSSwapHostIntToBig([number unsignedIntValue]); [dest appendBytes: &ival length: 4]; } else { unsigned long long lval; code = 0x13; [dest appendBytes: &code length: 1]; lval = NSSwapHostLongLongToBig([number unsignedLongLongValue]); [dest appendBytes: &lval length: 8]; } break; } case 'f': { NSSwappedFloat val = NSSwapHostFloatToBig([number floatValue]); code = 0x22; [dest appendBytes: &code length: 1]; [dest appendBytes: &val length: sizeof(float)]; break; } case 'd': { NSSwappedDouble val = NSSwapHostDoubleToBig([number doubleValue]); code = 0x23; [dest appendBytes: &code length: 1]; [dest appendBytes: &val length: sizeof(double)]; break; } default: [NSException raise: NSGenericException format: @"Attempt to store number with unknown ObjC type"]; } } - (void) storeDate: (NSDate*) date { unsigned char code; double out; code = 0x33; [dest appendBytes: &code length: 1]; out = NSSwapHostDoubleToBig([date timeIntervalSinceReferenceDate]); [dest appendBytes: &out length: sizeof(double)]; } - (void) storeArray: (NSArray*) array { unsigned char code; unsigned int len; unsigned int i; len = [array count]; if (len < 0x0F) { code = 0xA0 + len; [dest appendBytes: &code length: 1]; } else { code = 0xAF; [dest appendBytes: &code length: 1]; [self storeCount: len]; } for (i = 0; i < len; i++) { id obj; unsigned int oid; obj = [array objectAtIndex: i]; oid = [self indexForObject: obj]; [self storeIndex: oid]; } } - (void) storeDictionary: (NSDictionary*) dict { unsigned char code; NSNumber *num; unsigned int i; num = [dict objectForKey: @"CF$UID"]; if (num != nil) { // Special dictionary from keyed encoding unsigned int index; index = [num intValue]; if (index < 256) { unsigned char ci; code = 0x80; [dest appendBytes: &code length: 1]; ci = (unsigned char)index; [dest appendBytes: &ci length: 1]; } else { unsigned short si; code = 0x81; [dest appendBytes: &code length: 1]; si = NSSwapHostShortToBig((unsigned short)index); [dest appendBytes: &si length: 2]; } } else { unsigned int len = [dict count]; NSArray *keys = [dict allKeys]; NSMutableArray *objects = [NSMutableArray arrayWithCapacity: len]; id key; for (i = 0; i < len; i++) { key = [keys objectAtIndex: i]; [objects addObject: [dict objectForKey: key]]; } if (len < 0x0F) { code = 0xD0 + len; [dest appendBytes: &code length: 1]; } else { code = 0xDF; [dest appendBytes: &code length: 1]; [self storeCount: len]; } for (i = 0; i < len; i++) { id obj; unsigned int oid; obj = [keys objectAtIndex: i]; oid = [self indexForObject: obj]; [self storeIndex: oid]; } for (i = 0; i < len; i++) { id obj; unsigned int oid; obj = [objects objectAtIndex: i]; oid = [self indexForObject: obj]; [self storeIndex: oid]; } } } - (void) storeObject: (id)object { [self markOffset: [dest length] for: object]; if ([object isKindOfClass: [NSString class]]) { [self storeString: object]; } else if ([object isKindOfClass: [NSData class]]) { [self storeData: object]; } else if ([object isKindOfClass: [NSNumber class]]) { [self storeNumber: object]; } else if ([object isKindOfClass: [NSDate class]]) { [self storeDate: object]; } else if ([object isKindOfClass: [NSArray class]]) { [self storeArray: object]; } else if ([object isKindOfClass: [NSDictionary class]]) { [self storeDictionary: object]; } else { NSLog(@"Unknown object class %@", object); } } - (void) generate { BOOL done = NO; index_size = 1; while (!done && (index_size <= 4)) { NS_DURING { [self setup]; [self writeObjects]; done = YES; } NS_HANDLER { [self cleanup]; index_size += 1; } NS_ENDHANDLER } [self writeObjectTable]; [self writeMetaData]; } @end