#include #include "SecureDownloadInternal.h" // // SecureDownloadXML: SecureDownloadXML.c // cc -g -framework CoreFoundation -o $@ $^ // #define DEBUG 0 #if DEBUG extern CFDataRef read_data(char* path); #endif #define SD_XML_NAMESPACE CFSTR("http://www.apple.com/2006/SecureDownload/1") #define SD_XML_ROOT CFSTR("SecureDownload") #define SD_XML_RESOURCE CFSTR("resource") #define SD_XML_SITES CFSTR("sites") #define SD_XML_VERIFICATION CFSTR("verification") #define SD_XML_ATTR_ALGORITHM CFSTR("algorithm") struct parseState { CFDictionaryRef namespaces; // array of dictionaries of namespace declarations #if DEBUG char* prefix; #endif CFMutableArrayRef plists; // array of all resource plists CFMutableDictionaryRef plist; // most recent entry in the plists array }; static inline unsigned char decode64(unsigned char c) { switch(c) { case 'A': return 0; case 'B': return 1; case 'C': return 2; case 'D': return 3; case 'E': return 4; case 'F': return 5; case 'G': return 6; case 'H': return 7; case 'I': return 8; case 'J': return 9; case 'K': return 10; case 'L': return 11; case 'M': return 12; case 'N': return 13; case 'O': return 14; case 'P': return 15; case 'Q': return 16; case 'R': return 17; case 'S': return 18; case 'T': return 19; case 'U': return 20; case 'V': return 21; case 'W': return 22; case 'X': return 23; case 'Y': return 24; case 'Z': return 25; case 'a': return 26; case 'b': return 27; case 'c': return 28; case 'd': return 29; case 'e': return 30; case 'f': return 31; case 'g': return 32; case 'h': return 33; case 'i': return 34; case 'j': return 35; case 'k': return 36; case 'l': return 37; case 'm': return 38; case 'n': return 39; case 'o': return 40; case 'p': return 41; case 'q': return 42; case 'r': return 43; case 's': return 44; case 't': return 45; case 'u': return 46; case 'v': return 47; case 'w': return 48; case 'x': return 49; case 'y': return 50; case 'z': return 51; case '0': return 52; case '1': return 53; case '2': return 54; case '3': return 55; case '4': return 56; case '5': return 57; case '6': return 58; case '7': return 59; case '8': return 60; case '9': return 61; case '+': return 62; case '/': return 63; } return 255; } // Decodes base64 data into a binary CFData object // If first character on a line is not in the base64 alphabet, the line // is ignored. static CFDataRef decodeBase64Data(const UInt8* ptr, size_t len) { CFMutableDataRef result = CFDataCreateMutable(NULL, len); // data can't exceed len bytes if (!result) return NULL; CFIndex i, j; int skip = 0; UInt8 triplet[3] = {0, 0, 0}; /* http://www.faqs.org/rfcs/rfc3548.html +--first octet--+-second octet--+--third octet--+ |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| +-----------+---+-------+-------+---+-----------+ |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| +--1.index--+--2.index--+--3.index--+--4.index--+ */ for (i = 0, j = 0; i < len; ++i) { unsigned char c = ptr[i]; if (c == ' ') { continue; } if (c == '\t') { continue; } if (c == '\r') { continue; } if (c == '\n') { skip = 0; continue; } if (skip) { continue; } if (!skip && c == '=') { --j; skip = 1; continue; } unsigned char x = decode64(c); if (x == 255) { skip = 1; continue; } if (j == 0) { triplet[0] |= ((x << 2) & 0xFC); ++j; } else if (j == 1) { triplet[0] |= ((x >> 4) & 0x03); triplet[1] |= ((x << 4) & 0xF0); ++j; } else if (j == 2) { triplet[1] |= ((x >> 2) & 0x0F); triplet[2] |= ((x << 6) & 0xC0); ++j; } else if (j == 3) { triplet[2] |= ((x) & 0x3F); CFDataAppendBytes(result, triplet, j); memset(triplet, 0, sizeof(triplet)); j = 0; } } if (j > 0) { CFDataAppendBytes(result, triplet, j); } return result; } // Returns a CFString containing the base64 representation of the data. // boolean argument for whether to line wrap at 64 columns or not. static CFStringRef encodeBase64String(const UInt8* ptr, size_t len, int wrap) { const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/="; // base64 encoded data uses 4 ASCII characters to represent 3 octets. // There can be up to two == at the end of the base64 data for padding. // If we are line wrapping then we need space for one newline character // every 64 characters of output. // Rounded 4/3 up to 2 to avoid floating point math. //CFIndex max_len = (2*len) + 2; //if (wrap) len = len + ((2*len) / 64) + 1; CFMutableStringRef string = CFStringCreateMutable(NULL, 0); if (!string) return NULL; /* http://www.faqs.org/rfcs/rfc3548.html +--first octet--+-second octet--+--third octet--+ |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| +-----------+---+-------+-------+---+-----------+ |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| +--1.index--+--2.index--+--3.index--+--4.index--+ */ int i = 0; // octet offset into input data int column = 0; // output column number (used for line wrapping) for (;;) { UniChar c[16]; // buffer of characters to add to output int j = 0; // offset to place next character in buffer int index; // index into output alphabet #define ADDCHAR(_X_) do { c[j++] = _X_; if (wrap && (++column == 64)) { column = 0; c[j++] = '\n'; } } while (0); // 1.index index = (ptr[i] >> 2) & 0x3F; ADDCHAR(alphabet[index]); // 2.index index = (ptr[i] << 4) & 0x30; if ((i+1) < len) { index = index | ((ptr[i+1] >> 4) & 0x0F); ADDCHAR(alphabet[index]); } else { // end of input, pad as necessary ADDCHAR(alphabet[index]); ADDCHAR('='); ADDCHAR('='); } // 3.index if ((i+1) < len) { index = (ptr[i+1] << 2) & 0x3C; if ((i+2) < len) { index = index | ((ptr[i+2] >> 6) & 0x03); ADDCHAR(alphabet[index]); } else { // end of input, pad as necessary ADDCHAR(alphabet[index]); ADDCHAR('='); } } // 4.index if ((i+2) < len) { index = (ptr[i+2]) & 0x3F; ADDCHAR(alphabet[index]); } CFStringAppendCharacters(string, c, j); i += 3; // we processed 3 bytes of input if (i >= len) { // end of data, append newline if we haven't already if (wrap && c[j-1] != '\n') { c[0] = '\n'; CFStringAppendCharacters(string, c, 1); } break; } } return string; } // makes a copy of the current namespaces dictionary, adding in any // namespaces defined by the current node. static CFDictionaryRef copyNamespacesForNode(CFDictionaryRef namespaces, CFXMLNodeRef node) { CFMutableDictionaryRef result = NULL; CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node); // careful, don't use the info unless we ensure type == kCFXMLNodeTypeElement CFXMLElementInfo* info = (CFXMLElementInfo*)CFXMLNodeGetInfoPtr(node); // // create our result dictionary // there are four possible configurations: // 1. previous dictionary exists, this is an element, and has attributes: // clone existing dictionary, we may be adding to it // 2. previous dictionary exists, not an element or no attributes: // retain existing dictionary and return // 3. no previous dictionary, this is an element, and has attributes: // create new dictionary, we may be adding to it // 4. no previous dictionary, not an element or no attributes: // create new dictionary and return // if (namespaces && type == kCFXMLNodeTypeElement && info->attributes && info->attributeOrder) { result = CFDictionaryCreateMutableCopy(NULL, 0, namespaces); } else if (namespaces) { result = (CFMutableDictionaryRef)CFRetain(namespaces); return result; } else if (type == kCFXMLNodeTypeElement && info->attributes && info->attributeOrder) { result = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (result) CFDictionarySetValue(result, CFSTR(""), CFSTR("")); } else { result = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (result) CFDictionarySetValue(result, CFSTR(""), CFSTR("")); return result; } if (!result) return NULL; // // if we got this far, we're dealing with an XML element with // attributes. check to see if any are xml namespace attributes. // CFArrayRef attrs = info->attributeOrder; CFIndex i, count = CFArrayGetCount(attrs); for (i = 0; i < count; ++i) { CFStringRef attr = CFArrayGetValueAtIndex(attrs, i); if (CFEqual(CFSTR("xmlns"), attr)) { // default namespace CFStringRef value = CFDictionaryGetValue(info->attributes, attr); if (value) { CFDictionarySetValue(result, CFSTR(""), value); } } else { // if the attribute is in the "xmlns" namespace, then it's // really a declaration of a new namespace. record it in our dictionary. CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, attr, CFSTR(":")); CFIndex numparts = parts ? CFArrayGetCount(parts) : 0; if (numparts == 2) { CFStringRef prefix = CFArrayGetValueAtIndex(parts, 0); CFStringRef suffix = CFArrayGetValueAtIndex(parts, 1); if (CFEqual(CFSTR("xmlns"), prefix)) { CFStringRef value = CFDictionaryGetValue(info->attributes, attr); if (value) { CFDictionarySetValue(result, suffix, value); } } } if (parts) CFRelease(parts); } } return result; } // returns the current node's element name and namespace URI // based on the currently defined namespaces. static void copyNodeNamespaceAndName(CFDictionaryRef namespaces, CFXMLNodeRef node, CFStringRef* namespace, CFStringRef* name) { CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node); *namespace = NULL; *name = NULL; if (type == kCFXMLNodeTypeElement) { CFStringRef qname = CFXMLNodeGetString(node); CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, qname, CFSTR(":")); CFIndex numparts = parts ? CFArrayGetCount(parts) : 0; if (numparts == 1) { // default namespace *namespace = CFRetain(CFDictionaryGetValue(namespaces, CFSTR(""))); *name = CFRetain(CFArrayGetValueAtIndex(parts, 0)); } else if (numparts == 2) { CFStringRef prefix = CFArrayGetValueAtIndex(parts, 0); CFStringRef ns = CFDictionaryGetValue(namespaces, prefix); *namespace = ns ? CFRetain(ns) : NULL; *name = CFRetain(CFArrayGetValueAtIndex(parts, 1)); } else { // bogus xml } if (parts) CFRelease(parts); } } // helper function for copyTreeString() below // appends text nodes to the mutable string context static void _appendTreeString(const void *value, void *context) { CFXMLTreeRef tree = (CFXMLTreeRef)value; CFMutableStringRef result = (CFMutableStringRef)context; CFXMLNodeRef node = CFXMLTreeGetNode(tree); CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node); if (type == kCFXMLNodeTypeElement) { CFTreeApplyFunctionToChildren(tree, _appendTreeString, result); } else if (type == kCFXMLNodeTypeText) { CFStringRef str = CFXMLNodeGetString(node); if (str) CFStringAppend(result, str); } } // equivalent to the XPATH string() function // concatenates all text nodes into a single string static CFMutableStringRef copyTreeString(CFXMLTreeRef tree) { CFMutableStringRef result = CFStringCreateMutable(NULL, 0); CFTreeApplyFunctionToChildren(tree, _appendTreeString, result); return result; } // returns an array of CFXMLTreeRef objects that are immediate // children of the context node that match the specified element // name and namespace URI static CFArrayRef copyChildrenWithName(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); tree = CFTreeGetFirstChild(tree); while (tree) { CFXMLNodeRef node = CFXMLTreeGetNode(tree); CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node); if (type == kCFXMLNodeTypeElement) { CFDictionaryRef namespaces = copyNamespacesForNode(inNamespaces, node); if (namespaces) { CFStringRef ns, n; copyNodeNamespaceAndName(namespaces, node, &ns, &n); if (ns && n && CFEqual(ns, namespace) && CFEqual(n, name)) { CFArrayAppendValue(result, tree); } if (ns) CFRelease(ns); if (n) CFRelease(n); CFRelease(namespaces); } } tree = CFTreeGetNextSibling(tree); } return result; } // convenience function to find the first child element // with the given element name and namespace URI static CFXMLTreeRef getChildWithName(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFXMLTreeRef result = NULL; CFArrayRef array = copyChildrenWithName(tree, inNamespaces, namespace, name); if (array && CFArrayGetCount(array) > 0) { result = (CFXMLTreeRef)CFArrayGetValueAtIndex(array, 0); } if (array) CFRelease(array); return result; } // returns the string value of the specified child node static CFStringRef copyChildWithNameAsString(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFStringRef result = NULL; CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name); if (child) result = copyTreeString(child); return result; } // returns the integer value of the specified child node static CFNumberRef copyChildWithNameAsInteger(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFNumberRef result = NULL; CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name); if (child) { CFStringRef str = copyTreeString(child); if (str) { SInt32 size = CFStringGetIntValue(str); result = CFNumberCreate(NULL, kCFNumberSInt32Type, &size); CFRelease(str); } } return result; } // returns the URL value of the specified child node static CFURLRef copyChildWithNameAsURL(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFURLRef result = NULL; CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name); if (child) { CFStringRef str = copyTreeString(child); if (str) { result = CFURLCreateWithString(NULL, str, NULL); CFRelease(str); } } return result; } // returns an array of URLs aggregated from the child // nodes matching the given name and namespace URI. static CFArrayRef copyChildrenWithNameAsURLs(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); CFArrayRef children = copyChildrenWithName(tree, inNamespaces, namespace, name); if (children) { CFIndex i, count = CFArrayGetCount(children); for (i = 0; i < count; ++i) { CFXMLTreeRef child = (CFXMLTreeRef)CFArrayGetValueAtIndex(children, i); CFStringRef str = copyTreeString(child); if (str) { CFURLRef url = CFURLCreateWithString(NULL, str, NULL); if (url) { CFArrayAppendValue(result, url); CFRelease(url); } CFRelease(str); } } CFRelease(children); } return result; } // returns the base 64 decoded value of the specified child node static CFDataRef copyChildWithNameAsData(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFDataRef result = NULL; CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name); if (child) { CFStringRef str = copyTreeString(child); if (str) { CFIndex len = CFStringGetLength(str); CFIndex used; UInt8* buffer = malloc(len); // ASCII is one byte per character. Any inconvertible characters // are assigned to whitespace and skipped. if (buffer) { if (CFStringGetBytes(str, CFRangeMake(0, len), kCFStringEncodingASCII, ' ', 0, buffer, len, &used)) { result = decodeBase64Data(buffer, used); } free(buffer); } CFRelease(str); } } return result; } // returns the CFDate value of the specified child node // whose string value is interpreted in W3C DateTime format static CFDateRef copyChildWithNameAsDate(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) { CFDateRef result = NULL; CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name); if (child) { CFMutableStringRef str = copyTreeString(child); if (str) { CFStringTrimWhitespace(str); if (CFStringGetLength(str) > 21) { CFStringRef year = CFStringCreateWithSubstring(NULL, str, CFRangeMake(0, 4)); CFStringRef month = CFStringCreateWithSubstring(NULL, str, CFRangeMake(5, 2)); CFStringRef day = CFStringCreateWithSubstring(NULL, str, CFRangeMake(8, 2)); CFStringRef hour = CFStringCreateWithSubstring(NULL, str, CFRangeMake(11, 2)); CFStringRef minute = CFStringCreateWithSubstring(NULL, str, CFRangeMake(14, 2)); CFStringRef second = CFStringCreateWithSubstring(NULL, str, CFRangeMake(17, 2)); CFStringRef tenth = CFStringCreateWithSubstring(NULL, str, CFRangeMake(20, 1)); CFGregorianDate gregory; memset(&gregory, 0, sizeof(gregory)); if (year) { gregory.year = CFStringGetIntValue(year); CFRelease(year); } if (month) { gregory.month = CFStringGetIntValue(month); CFRelease(month); } if (day) { gregory.day = CFStringGetIntValue(day); CFRelease(day); } if (hour) { gregory.hour = CFStringGetIntValue(hour); CFRelease(hour); } if (minute) { gregory.minute = CFStringGetIntValue(minute); CFRelease(minute); } if (second) { gregory.second = (double)CFStringGetIntValue(second); CFRelease(second); } if (tenth) { gregory.second += ((double)CFStringGetIntValue(tenth)/(double)10.0); CFRelease(tenth); } CFTimeZoneRef tz = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0); if (tz) { CFAbsoluteTime at = CFGregorianDateGetAbsoluteTime(gregory, tz); result = CFDateCreate(NULL, at); CFRelease(tz); } } CFRelease(str); } } return result; } // generic parser for XML nodes in our ticket format static void _parseXMLNode(const void *value, void *context) { CFXMLTreeRef tree = (CFXMLTreeRef)value; struct parseState* state = (struct parseState*)context; CFXMLNodeRef node = CFXMLTreeGetNode(tree); assert(node); CFDictionaryRef namespaces = copyNamespacesForNode(state->namespaces, node); CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node); int descend = 0; if (type == kCFXMLNodeTypeElement) { CFStringRef ns, name; copyNodeNamespaceAndName(namespaces, node, &ns, &name); if (ns && name) { if (CFEqual(ns, SD_XML_NAMESPACE)) { if (CFEqual(name, SD_XML_ROOT)) { descend = 1; } else if (CFEqual(name, SD_XML_RESOURCE)) { state->plist = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (state->plist) { CFArrayAppendValue(state->plists, state->plist); CFRelease(state->plist); } CFStringRef name = copyChildWithNameAsString(tree, namespaces, SD_XML_NAMESPACE, SD_XML_NAME); if (name && state->plist) { CFDictionarySetValue(state->plist, SD_XML_NAME, name); CFRelease(name); } CFNumberRef size = copyChildWithNameAsInteger(tree, namespaces, SD_XML_NAMESPACE, SD_XML_SIZE); if (size && state->plist) { CFDictionarySetValue(state->plist, SD_XML_SIZE, size); CFRelease(size); } CFDateRef created = copyChildWithNameAsDate(tree, namespaces, SD_XML_NAMESPACE, SD_XML_CREATED); if (created && state->plist) { CFDictionarySetValue(state->plist, SD_XML_CREATED, created); CFRelease(created); } descend = 1; } else if (CFEqual(name, SD_XML_SITES)) { CFArrayRef urls = copyChildrenWithNameAsURLs(tree, namespaces, SD_XML_NAMESPACE, SD_XML_URL); if (urls && state->plist) { CFDictionarySetValue(state->plist, SD_XML_URL, urls); CFRelease(urls); } } else if (CFEqual(name, SD_XML_VERIFICATIONS)) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (dict && state->plist) { CFDictionarySetValue(state->plist, SD_XML_VERIFICATIONS, dict); CFRelease(dict); descend = 1; } } else if (CFEqual(name, SD_XML_VERIFICATION) && state->plist) { CFMutableDictionaryRef verifications = (CFMutableDictionaryRef)CFDictionaryGetValue(state->plist, SD_XML_VERIFICATIONS); CFXMLElementInfo* info = (CFXMLElementInfo*)CFXMLNodeGetInfoPtr(node); if (verifications && info && info->attributes) { CFStringRef algorithm = CFDictionaryGetValue(info->attributes, SD_XML_ATTR_ALGORITHM); if (algorithm) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (dict) { CFXMLTreeRef child; #pragma unused(child) CFNumberRef sector_size = copyChildWithNameAsInteger(tree, namespaces, SD_XML_NAMESPACE, SD_XML_SECTOR_SIZE); if (sector_size) { CFDictionarySetValue(dict, SD_XML_SECTOR_SIZE, sector_size); CFRelease(sector_size); } CFDataRef digest = copyChildWithNameAsData(tree, namespaces, SD_XML_NAMESPACE, SD_XML_DIGEST); if (digest) { CFDictionarySetValue(dict, SD_XML_DIGEST, digest); CFRelease(digest); } CFDataRef digests = copyChildWithNameAsData(tree, namespaces, SD_XML_NAMESPACE, SD_XML_DIGESTS); if (digests) { CFDictionarySetValue(dict, SD_XML_DIGESTS, digests); CFRelease(digests); } CFDictionarySetValue(verifications, algorithm, dict); CFRelease(dict); } } } } } #if DEBUG cfprintf(stderr, "%sELEM:\t%@\t[%@]\n", state->prefix, name, ns); #endif } if (ns) CFRelease(ns); if (name) CFRelease(name); } else if (type == kCFXMLNodeTypeWhitespace) { // do nothing } else { #if DEBUG CFStringRef str = CFXMLNodeGetString(node); cfprintf(stderr, "%s% 4d:\t%@\n", state->prefix, type, str); #endif } // only recurse further if we have been specifically instructed to // do so. if (descend) { struct parseState local; memcpy(&local, state, sizeof(struct parseState)); local.namespaces = namespaces; #if DEBUG asprintf(&local.prefix, "%s ", state->prefix); #endif CFTreeApplyFunctionToChildren(tree, _parseXMLNode, &local); #if DEBUG free(local.prefix); #endif } if (namespaces) CFRelease(namespaces); } CFPropertyListRef _SecureDownloadParseTicketXML(CFDataRef xmlData) { if (!xmlData) return NULL; CFURLRef url = NULL; CFXMLTreeRef tree = CFXMLTreeCreateFromData(NULL, xmlData, url, kCFXMLParserNoOptions, kCFXMLNodeCurrentVersion); if (!tree) return NULL; struct parseState state; memset(&state, 0, sizeof(state)); #if DEBUG state.prefix = ""; #endif state.plists = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (state.plists) { CFTreeApplyFunctionToChildren(tree, _parseXMLNode, &state); } CFRelease(tree); CFPropertyListRef result = NULL; // For now, only return the first resource encountered if (state.plists && CFArrayGetCount(state.plists) > 0) { result = CFArrayGetValueAtIndex(state.plists, 0); CFRetain(result); } if (state.plists) CFRelease(state.plists); return result; } static void _appendCString(CFMutableDataRef data, const char* cstring) { CFDataAppendBytes(data, (UInt8*)cstring, strlen(cstring)); } static void _appendCFString(CFMutableDataRef data, CFStringRef string) { CFDataRef utf8 = CFStringCreateExternalRepresentation(NULL, string, kCFStringEncodingUTF8, '?'); if (utf8) { CFDataAppendBytes(data, CFDataGetBytePtr(utf8), CFDataGetLength(utf8)); CFRelease(utf8); } } static void _appendCFNumber(CFMutableDataRef data, CFNumberRef number) { CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), number); if (str) { _appendCFString(data, str); CFRelease(str); } } static void _appendCFURL(CFMutableDataRef data, CFURLRef url) { CFURLRef abs = CFURLCopyAbsoluteURL(url); if (abs) { CFStringRef str = CFURLGetString(abs); if (str) { _appendCFString(data, str); } CFRelease(abs); } } // appends data in base64 encoded form static void _appendCFData(CFMutableDataRef data, CFDataRef moreData) { CFStringRef str = encodeBase64String(CFDataGetBytePtr(moreData), CFDataGetLength(moreData), 0); if (str) { _appendCFString(data, str); CFRelease(str); } } // appends a date in W3C DateTime format static void _appendCFDate(CFMutableDataRef data, CFDateRef date) { CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US")); if (locale) { CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); if (formatter) { CFDateFormatterSetFormat(formatter, CFSTR("yyyy-MM-dd'T'HH:mm:ss.S'Z'")); CFTimeZoneRef tz = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0); if (tz) { CFDateFormatterSetProperty(formatter, kCFDateFormatterTimeZone, tz); CFStringRef str = CFDateFormatterCreateStringWithDate(NULL, formatter, date); if (str) { _appendCFString(data, str); CFRelease(str); } CFRelease(tz); } CFRelease(formatter); } CFRelease(locale); } } static CFArrayRef dictionaryGetSortedKeys(CFDictionaryRef dictionary) { CFIndex count = CFDictionaryGetCount(dictionary); const void** keys = malloc(sizeof(CFStringRef) * count); CFDictionaryGetKeysAndValues(dictionary, keys, NULL); CFArrayRef keysArray = CFArrayCreate(NULL, keys, count, &kCFTypeArrayCallBacks); CFMutableArrayRef sortedKeys = CFArrayCreateMutableCopy(NULL, count, keysArray); CFRelease(keysArray); free(keys); CFArraySortValues(sortedKeys, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, 0); return sortedKeys; } CFDataRef _SecureDownloadCreateTicketXML(CFPropertyListRef plist) { CFMutableDataRef data = CFDataCreateMutable(NULL, 0); if (!data) return NULL; _appendCString(data, "\n"); _appendCString(data, "\n"); _appendCString(data, " \n"); CFStringRef name = CFDictionaryGetValue(plist, SD_XML_NAME); if (name) { _appendCString(data, "\t"); _appendCFString(data, name); _appendCString(data, "\n"); } CFNumberRef num = CFDictionaryGetValue(plist, SD_XML_SIZE); if (num) { _appendCString(data, "\t"); _appendCFNumber(data, num); _appendCString(data, "\n"); } CFDateRef created = CFDictionaryGetValue(plist, SD_XML_CREATED); if (created) { _appendCString(data, "\t"); _appendCFDate(data, created); _appendCString(data, "\n"); } _appendCString(data, "\t\n"); CFArrayRef urls = CFDictionaryGetValue(plist, SD_XML_URL); if (urls) { CFIndex i, count = CFArrayGetCount(urls); for (i = 0; i < count; ++i) { _appendCString(data, "\t\t"); _appendCFURL(data, CFArrayGetValueAtIndex(urls, i)); _appendCString(data, "\n"); } } _appendCString(data, "\t\n"); CFDictionaryRef verifications = CFDictionaryGetValue(plist, SD_XML_VERIFICATIONS); if (verifications) { _appendCString(data, "\t\n"); CFArrayRef algorithms = dictionaryGetSortedKeys(verifications); if (algorithms) { CFIndex i, count = CFArrayGetCount(algorithms); for (i = 0; i < count; ++i) { CFStringRef algorithm = CFArrayGetValueAtIndex(algorithms, i); if (algorithm) { _appendCString(data, "\t\t\n"); CFDictionaryRef dict = CFDictionaryGetValue(verifications, algorithm); if (dict) { CFDataRef digest = CFDictionaryGetValue(dict, SD_XML_DIGEST); if (digest) { _appendCString(data, "\t\t\t"); _appendCFData(data, digest); _appendCString(data, "\n"); } CFNumberRef sector_size = CFDictionaryGetValue(dict, SD_XML_SECTOR_SIZE); if (sector_size) { _appendCString(data, "\t\t\t"); _appendCFNumber(data, sector_size); _appendCString(data, "\n"); } CFDataRef digests = CFDictionaryGetValue(dict, SD_XML_DIGESTS); if (digest) { _appendCString(data, "\t\t\t"); _appendCFData(data, digests); _appendCString(data, "\n"); } } _appendCString(data, "\t\t\n"); } } CFRelease(algorithms); } _appendCString(data, "\t\n"); } _appendCString(data, " \n"); _appendCString(data, "\n"); return data; } #if DEBUG #include int main(int argc, char* argv[]) { CFDataRef data = read_data("/Users/kevin/Desktop/SecureDownloadXML/SecureDownload.xml"); if (data) { CFPropertyListRef plist = _SecureDownloadParseTicketXML(data); CFShow(plist); if (plist) { CFDataRef output = _SecureDownloadCreateTicketXML(plist); if (output) { write(STDOUT_FILENO, CFDataGetBytePtr(output), CFDataGetLength(output)); CFRelease(output); } CFRelease(plist); } CFRelease(data); } // help look for leaks cfprintf(stderr, "pid = %d\n", getpid()); sleep(1000); } #endif