SecureDownloadInternal.c   [plain text]


#include <CoreFoundation/CoreFoundation.h>

#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 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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	_appendCString(data, "<SecureDownload xmlns=\"http://www.apple.com/2006/SecureDownload/1\">\n");
	_appendCString(data, "    <resource>\n");
	
	CFStringRef name = CFDictionaryGetValue(plist, SD_XML_NAME);
	if (name) {
		_appendCString(data, "\t<name>");
		_appendCFString(data, name);
		_appendCString(data, "</name>\n");
	}
	
	CFNumberRef num = CFDictionaryGetValue(plist, SD_XML_SIZE);
	if (num) {
		_appendCString(data, "\t<size>");
		_appendCFNumber(data, num);
		_appendCString(data, "</size>\n");
	}
	
	CFDateRef created = CFDictionaryGetValue(plist, SD_XML_CREATED);
	if (created) {
		_appendCString(data, "\t<created>");
		_appendCFDate(data, created);
		_appendCString(data, "</created>\n");
	}
	
	_appendCString(data, "\t<sites>\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<url>");
			_appendCFURL(data, CFArrayGetValueAtIndex(urls, i));
			_appendCString(data, "</url>\n");
		}
	}
	_appendCString(data, "\t</sites>\n");
	
	CFDictionaryRef verifications = CFDictionaryGetValue(plist, SD_XML_VERIFICATIONS);
	if (verifications) {
		_appendCString(data, "\t<verifications>\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<verification algorithm=\"");
					_appendCFString(data, algorithm);
					_appendCString(data, "\">\n");					
					CFDictionaryRef dict = CFDictionaryGetValue(verifications, algorithm);
					if (dict) {
						CFDataRef digest = CFDictionaryGetValue(dict, SD_XML_DIGEST);
						if (digest) {
							_appendCString(data, "\t\t\t<digest>");
							_appendCFData(data, digest);
							_appendCString(data, "</digest>\n");
						}
						
						CFNumberRef sector_size = CFDictionaryGetValue(dict, SD_XML_SECTOR_SIZE);
						if (sector_size) {
							_appendCString(data, "\t\t\t<sector_size>");
							_appendCFNumber(data, sector_size);
							_appendCString(data, "</sector_size>\n");
						}
						
						CFDataRef digests = CFDictionaryGetValue(dict, SD_XML_DIGESTS);
						if (digest) {
							_appendCString(data, "\t\t\t<digests>");
							_appendCFData(data, digests);
							_appendCString(data, "</digests>\n");
						}
					}
					_appendCString(data, "\t\t</verification>\n");
				}
			}
			CFRelease(algorithms);
		}
		_appendCString(data, "\t</verifications>\n");
	}
	
	_appendCString(data, "    </resource>\n");
	_appendCString(data, "</SecureDownload>\n");

	return data;
}

#if DEBUG
#include <unistd.h>
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