/* * Copyright (c) 1999-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * HISTORY * * 25 Nov 98 from IOSerialize.cpp created by rsulack * 31 Aug 99 sdouglas CF version. * 09 Dec 99 rsulack converted to serialize to "XML" syntax */ #include #include #include typedef struct { CFMutableDataRef data; int idrefNumRefs; /* For each CFType, we track whether a given plist value hasRefs, * and what the ids used for those refs are. 'hasRefs' is set to * kCFBooleanFalse the first time we see a given value, so we know * we saw it, and then kCFBooleanTrue if we see it again, so we know * we need an id for it. On writing out the XML, we generate ids as * we encounter the need. */ CFMutableDictionaryRef stringIDRefDictionary; CFMutableDictionaryRef numberIDRefDictionary; CFMutableDictionaryRef dataIDRefDictionary; CFMutableDictionaryRef dictionaryIDRefDictionary; CFMutableDictionaryRef arrayIDRefDictionary; CFMutableDictionaryRef setIDRefDictionary; } IOCFSerializeState; static Boolean DoCFSerialize(CFTypeRef object, IOCFSerializeState * state); static Boolean addChar(char chr, IOCFSerializeState * state) { CFDataAppendBytes(state->data, (UInt8 *) &chr, 1); return true; } static Boolean addString(const char * str, IOCFSerializeState * state) { CFIndex len = strlen(str); CFDataAppendBytes(state->data, (UInt8 *) str, len); return true; } static const char * getTagString(CFTypeRef object) { CFTypeID type; assert(object); type = CFGetTypeID(object); if (type == CFStringGetTypeID()) return "string"; if (type == CFNumberGetTypeID()) return "integer"; if (type == CFDataGetTypeID()) return "data"; if (type == CFDictionaryGetTypeID()) return "dict"; if (type == CFArrayGetTypeID()) return "array"; if (type == CFSetGetTypeID()) return "set"; return "internal error"; } static CFMutableDictionaryRef idRefDictionaryForObject( CFTypeRef object, IOCFSerializeState * state) { CFMutableDictionaryRef result = NULL; CFTypeID objectType = CFNullGetTypeID(); // do not release objectType = CFGetTypeID(object); /* Sorted by rough order of % occurence in big plists. */ if (objectType == CFDictionaryGetTypeID()) { result = state->dictionaryIDRefDictionary; } else if (objectType == CFStringGetTypeID()) { result = state->stringIDRefDictionary; } else if (objectType == CFArrayGetTypeID()) { result = state->arrayIDRefDictionary; } else if (objectType == CFNumberGetTypeID()) { result = state->numberIDRefDictionary; } else if (objectType == CFDataGetTypeID()) { result = state->dataIDRefDictionary; } else if (objectType == CFSetGetTypeID()) { result = state->setIDRefDictionary; } return result; } void recordObjectInIDRefDictionary( CFTypeRef object, IOCFSerializeState * state) { CFTypeRef refEntry = NULL; // do not release CFMutableDictionaryRef idRefDictionary = NULL; // do not release if (!object || !state) { goto finish; } idRefDictionary = idRefDictionaryForObject(object, state); if (!idRefDictionary) { goto finish; } refEntry = CFDictionaryGetValue(idRefDictionary, object); /* If we have never seen this object value, then add an entry * in the dictionary with value false, indicating we have seen * it once. * * If we have seen this object value, then set its entry to true * to indicate that we have now seen a second occurrence * of the object value, which means we will generate an ID and IDREFs * in the XML. */ if (!refEntry) { /* Use AddValue here so as not to overwrite. */ CFDictionaryAddValue(idRefDictionary, object, kCFBooleanFalse); } else { CFDictionarySetValue(idRefDictionary, object, kCFBooleanTrue); } finish: return; } Boolean previouslySerialized( CFTypeRef object, IOCFSerializeState * state) { Boolean result = FALSE; CFMutableDictionaryRef idRefDict = NULL; // do not release CFTypeRef idRefEntry = NULL; // do not release CFTypeID idRefType = CFNullGetTypeID(); // do not release char temp[64]; int idInt = -1; if (!object || !state) { goto finish; } /* If we don't get a lookup dict or an entry for the object, * then no ID or IDREF will be involved, * so treat is if never before serialized. */ idRefDict = idRefDictionaryForObject(object, state); if (!idRefDict) { goto finish; } idRefEntry = (CFTypeRef)CFDictionaryGetValue(idRefDict, object); if (!idRefEntry) { goto finish; } idRefType = CFGetTypeID(idRefEntry); /* If the entry is of Boolean type, then an ID/IDREF may be involved, * but we haven't created one yet, so not yet serialized. */ if (idRefType == CFBooleanGetTypeID()) { goto finish; } /* At this point the entry should be a CFNumber. */ if (idRefType != CFNumberGetTypeID()) { goto finish; } /* Finally, get the IDREF value out of the idRef entry and write the * XML for it. Bail on extraction error. */ if (!CFNumberGetValue((CFNumberRef)idRefEntry, kCFNumberIntType, &idInt)) { goto finish; } snprintf(temp, sizeof(temp), "<%s IDREF=\"%d\"/>", getTagString(object), idInt); result = addString(temp, state); finish: return result; } static Boolean addStartTag( CFTypeRef object, const char * additionalTags, IOCFSerializeState * state) { CFMutableDictionaryRef idRefDict = NULL; // do not release CFTypeRef idRefEntry = NULL; // do not release CFNumberRef idRef = NULL; // must release char temp[128]; idRefDict = idRefDictionaryForObject(object, state); if (idRefDict) { idRefEntry = (CFTypeRef)CFDictionaryGetValue(idRefDict, object); } /* If the IDRef entry is 'true', then we know we have an object value * with multiple references and need to emit an ID. So we create one * by incrementing the state's counter, making a CFNumber, and *replacing* * the boolean value in the IDRef dictionary with that number. */ if (idRefEntry == kCFBooleanTrue) { int idInt = state->idrefNumRefs++; idRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &idInt); assert(idRef); CFDictionarySetValue(idRefDict, object, idRef); CFRelease(idRef); if (additionalTags) { snprintf(temp, sizeof(temp) * sizeof(char), "<%s ID=\"%d\" %s>", getTagString(object), idInt, additionalTags); } else { snprintf(temp, sizeof(temp) * sizeof(char), "<%s ID=\"%d\">", getTagString(object), idInt); } } else { if (additionalTags) { snprintf(temp, sizeof(temp) * sizeof(char), "<%s %s>", getTagString(object), additionalTags); } else { snprintf(temp, sizeof(temp) * sizeof(char), "<%s>", getTagString(object)); } } return addString(temp, state); } static Boolean addEndTag(CFTypeRef object, IOCFSerializeState * state) { char temp[128]; snprintf(temp, sizeof(temp) * sizeof(char), "", getTagString(object)); return addString(temp, state); } static Boolean DoCFSerializeNumber(CFNumberRef object, IOCFSerializeState * state) { char temp[32]; long long value; int size; if (previouslySerialized(object, state)) return true; if (!CFNumberGetValue(object, kCFNumberLongLongType, &value)) { return(false); } switch(CFNumberGetType(object)) { case kCFNumberSInt8Type: case kCFNumberCharType: size = 8; break; case kCFNumberSInt16Type: case kCFNumberShortType: size = 16; break; case kCFNumberSInt32Type: case kCFNumberIntType: size = 32; break; case kCFNumberLongType: #if __LP64__ size = 64; #else size = 32; #endif break; case kCFNumberSInt64Type: case kCFNumberLongLongType: default: size = 64; break; } snprintf(temp, sizeof(temp), "size=\"%d\"", size); if (!addStartTag(object, temp, state)) return false; if (size <= 32) snprintf(temp, sizeof(temp), "0x%lx", (unsigned long int) value); else snprintf(temp, sizeof(temp), "0x%qx", value); if (!addString(temp, state)) return false; return addEndTag(object, state); } static Boolean DoCFSerializeBoolean(CFBooleanRef object, IOCFSerializeState * state) { return addString(CFBooleanGetValue(object) ? "" : "", state); } static Boolean DoCFSerializeString(CFStringRef object, IOCFSerializeState * state) { CFDataRef dataBuffer = 0; const char * buffer; CFIndex length; bool conversionFailed = false; char c; int i; if (previouslySerialized(object, state)) return true; if (!addStartTag(object, 0, state)) return false; dataBuffer = CFStringCreateExternalRepresentation(kCFAllocatorDefault, object, kCFStringEncodingUTF8, 0); if (!dataBuffer) { dataBuffer = CFStringCreateExternalRepresentation(kCFAllocatorDefault, object, kCFStringEncodingUTF8, (UInt8)'?'); conversionFailed = true; } if (dataBuffer) { length = CFDataGetLength(dataBuffer); buffer = (char *) CFDataGetBytePtr(dataBuffer); } else { length = 0; buffer = ""; conversionFailed = true; } if (conversionFailed) { char * tempBuffer; if (buffer && (tempBuffer = malloc(length + 1))) { bcopy(buffer, tempBuffer, length); tempBuffer[length] = 0; syslog(LOG_ERR, "FIXME: IOCFSerialize has detected a string that can not be converted to UTF-8, \"%s\"", tempBuffer); free(tempBuffer); } } // this works because all bytes in a multi-byte utf-8 character have the high order bit set for (i = 0; i < length; i++) { c = buffer[i]; if (c == '<') { if (!addString("<", state)) return false; } else if (c == '>') { if (!addString(">", state)) return false; } else if (c == '&') { if (!addString("&", state)) return false; } else { if (!addChar(c, state)) return false; } } if (dataBuffer) CFRelease(dataBuffer); return addEndTag(object, state); } static Boolean DoCFSerializeKey(CFStringRef object, IOCFSerializeState * state) { CFDataRef dataBuffer = 0; const char * buffer; CFIndex length; bool conversionFailed = false; char c; int i; if (!addString("", state)) return false; dataBuffer = CFStringCreateExternalRepresentation(kCFAllocatorDefault, object, kCFStringEncodingUTF8, 0); if (!dataBuffer) { dataBuffer = CFStringCreateExternalRepresentation(kCFAllocatorDefault, object, kCFStringEncodingUTF8, (UInt8)'?'); conversionFailed = true; } if (dataBuffer) { length = CFDataGetLength(dataBuffer); buffer = (char *) CFDataGetBytePtr(dataBuffer); } else { length = 0; buffer = ""; conversionFailed = true; } if (conversionFailed) { char * tempBuffer; if (buffer && (tempBuffer = malloc(length + 1))) { bcopy(buffer, tempBuffer, length); tempBuffer[length] = 0; syslog(LOG_ERR, "FIXME: IOCFSerialize has detected a string that can not be converted to UTF-8, \"%s\"", tempBuffer); free(tempBuffer); } } // this works because all bytes in a multi-byte utf-8 character have the high order bit set for (i = 0; i < length; i++) { c = buffer[i]; if (c == '<') { if (!addString("<", state)) return false; } else if (c == '>') { if (!addString(">", state)) return false; } else if (c == '&') { if (!addString("&", state)) return false; } else { if (!addChar(c, state)) return false; } } if (dataBuffer) CFRelease(dataBuffer); return addString("", state); } //this was taken from CFPropertyList.c static const char __CFPLDataEncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static Boolean DoCFSerializeData(CFDataRef object, IOCFSerializeState * state) { CFIndex length; const UInt8 * bytes; CFIndex i; const unsigned char *p; unsigned char c = 0; if (previouslySerialized(object, state)) return true; length = CFDataGetLength(object); bytes = CFDataGetBytePtr(object); if (!addStartTag(object, 0, state)) return false; for (i = 0, p = (unsigned char *)bytes; i < length; i++, p++) { /* 3 bytes are encoded as 4 */ switch (i % 3) { case 0: c = __CFPLDataEncodeTable [ ((p[0] >> 2) & 0x3f)]; if (!addChar(c, state)) return false; break; case 1: c = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 4) & 0x3f)]; if (!addChar(c, state)) return false; break; case 2: c = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 6) & 0x3f)]; if (!addChar(c, state)) return false; c = __CFPLDataEncodeTable [ (p[0] & 0x3f)]; if (!addChar(c, state)) return false; break; } } switch (i % 3) { case 0: break; case 1: c = __CFPLDataEncodeTable [ ((p[-1] << 4) & 0x30)]; if (!addChar(c, state)) return false; if (!addChar('=', state)) return false; if (!addChar('=', state)) return false; break; case 2: c = __CFPLDataEncodeTable [ ((p[-1] << 2) & 0x3c)]; if (!addChar(c, state)) return false; if (!addChar('=', state)) return false; break; } return addEndTag(object, state); } static Boolean DoCFSerializeArray(CFArrayRef object, IOCFSerializeState * state) { CFIndex count, i; Boolean ok = true; if (previouslySerialized(object, state)) return true; if (!addStartTag(object, 0, state)) return false; count = CFArrayGetCount(object); if (count) { for (i = 0; ok && (i < count); i++) { ok = DoCFSerialize((CFTypeRef) CFArrayGetValueAtIndex(object, i), state); } } return ok && addEndTag(object, state); } static Boolean DoCFSerializeSet(CFSetRef object, IOCFSerializeState * state) { CFIndex count, i; const void ** values; Boolean ok = true; if (previouslySerialized(object, state)) return true; if (!addStartTag(object, 0, state)) return false; count = CFSetGetCount(object); if (count) { values = (const void **) malloc(count * sizeof(void *)); if (!values) return false; CFSetGetValues(object, values); } else { values = 0; } for (i = 0; ok && (i < count); i++) { ok = DoCFSerialize((CFTypeRef) values[i], state); } if (values) free(values); return ok && addEndTag(object, state); } static Boolean DoCFSerializeDictionary(CFDictionaryRef object, IOCFSerializeState * state) { CFIndex count, i; const void ** values; const void ** keys; Boolean ok = true; if (previouslySerialized(object, state)) return true; if (!addStartTag(object, 0, state)) return false; count = CFDictionaryGetCount(object); if (count) { values = (const void **) malloc(2 * count * sizeof(void *)); if (!values) return false; keys = values + count; CFDictionaryGetKeysAndValues(object, keys, values); } else { values = keys = 0; } for (i = 0; ok && (i < count); i++) { ok = DoCFSerializeKey((CFTypeRef) keys[i], state); if (!ok) break; if (!(ok = DoCFSerialize((CFTypeRef) values[i], state))) break; } if (values) free(values); return ok && addEndTag(object, state); } static Boolean DoIdrefScan(CFTypeRef object, IOCFSerializeState * state) { CFTypeID type; Boolean ok = true; // such an optimist CFIndex count, i; const void ** keys; const void ** values; assert(object); recordObjectInIDRefDictionary(object, state); type = CFGetTypeID(object); if (type == CFDictionaryGetTypeID()) { CFDictionaryRef dict = (CFDictionaryRef)object; count = CFDictionaryGetCount(dict); if (count) { keys = (const void **)malloc(count * sizeof(void *)); values = (const void **)malloc(count * sizeof(void *)); if (!keys || !values) { return false; } CFDictionaryGetKeysAndValues(dict, keys, values); for (i = 0; ok && (i < count); i++) { // don't record dictionary keys ok &= DoIdrefScan((CFTypeRef)values[i], state); } free(keys); free(values); if (!ok) { return ok; } } } else if (type == CFArrayGetTypeID()) { CFArrayRef array = (CFArrayRef)object; count = CFArrayGetCount(array); for (i = 0; i < count; i++) { CFTypeRef element = CFArrayGetValueAtIndex(array, i); ok = DoIdrefScan(element, state); if (!ok) { return ok; } } } else if (type == CFSetGetTypeID()) { CFSetRef set = (CFSetRef)object; count = CFSetGetCount(set); if (count) { values = (const void **)malloc(count * sizeof(void *)); if (!values) { return false; } CFSetGetValues(set, values); for (i = 0; ok && (i < count); i++) { ok = DoIdrefScan((CFTypeRef)values[i], state); } free(values); if (!ok) { return ok; } } } return ok; } static Boolean DoCFSerialize(CFTypeRef object, IOCFSerializeState * state) { CFTypeID type; Boolean ok; assert(object); type = CFGetTypeID(object); /* Sorted by rough order of % occurrence in big plists. */ if (type == CFDictionaryGetTypeID()) { ok = DoCFSerializeDictionary((CFDictionaryRef) object, state); } else if (type == CFStringGetTypeID()) { ok = DoCFSerializeString((CFStringRef) object, state); } else if (type == CFArrayGetTypeID()) { ok = DoCFSerializeArray((CFArrayRef) object, state); } else if (type == CFNumberGetTypeID()) { ok = DoCFSerializeNumber((CFNumberRef) object, state); } else if (type == CFDataGetTypeID()) { ok = DoCFSerializeData((CFDataRef) object, state); } else if (type == CFBooleanGetTypeID()) { ok = DoCFSerializeBoolean((CFBooleanRef) object, state); } else if (type == CFSetGetTypeID()) { ok = DoCFSerializeSet((CFSetRef) object, state); } else { CFStringRef temp = NULL; temp = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("typeID 0x%x not serializable"), (int) type); if (temp) { ok = DoCFSerializeString(temp, state); CFRelease(temp); } else { ok = false; } } return ok; } CFDataRef IOCFSerialize(CFTypeRef object, CFOptionFlags options) { IOCFSerializeState state; Boolean ok = FALSE; CFDictionaryKeyCallBacks idrefKeyCallbacks; if ((!object) || (options)) return 0; state.data = CFDataCreateMutable(kCFAllocatorDefault, 0); assert(state.data); state.idrefNumRefs = 0; idrefKeyCallbacks = kCFTypeDictionaryKeyCallBacks; // only use pointer equality for these keys idrefKeyCallbacks.equal = NULL; state.stringIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.stringIDRefDictionary); state.numberIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.numberIDRefDictionary); state.dataIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.dataIDRefDictionary); state.dictionaryIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.dictionaryIDRefDictionary); state.arrayIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.arrayIDRefDictionary); state.setIDRefDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &idrefKeyCallbacks, &kCFTypeDictionaryValueCallBacks); assert(state.setIDRefDictionary); ok = DoIdrefScan(object, &state); if (!ok) { goto finish; } ok = DoCFSerialize(object, &state); if (ok) { ok = addChar(0, &state); } if (!ok) { goto finish; } finish: if (!ok && state.data) { CFRelease(state.data); state.data = NULL; // it's returned } if (state.stringIDRefDictionary) CFRelease(state.stringIDRefDictionary); if (state.numberIDRefDictionary) CFRelease(state.numberIDRefDictionary); if (state.dataIDRefDictionary) CFRelease(state.dataIDRefDictionary); if (state.dictionaryIDRefDictionary) CFRelease(state.dictionaryIDRefDictionary); if (state.arrayIDRefDictionary) CFRelease(state.arrayIDRefDictionary); if (state.setIDRefDictionary) CFRelease(state.setIDRefDictionary); return state.data; }