1#include <CoreFoundation/CoreFoundation.h>
2
3#include "SecureDownloadInternal.h"
4
5//
6// SecureDownloadXML: SecureDownloadXML.c
7//        cc -g -framework CoreFoundation -o $@ $^
8//
9
10#define LOCAL_DEBUG 0
11
12#if LOCAL_DEBUG
13extern CFDataRef read_data(char* path);
14#endif
15
16#define SD_XML_NAMESPACE	CFSTR("http://www.apple.com/2006/SecureDownload/1")
17#define SD_XML_ROOT		CFSTR("SecureDownload")
18#define SD_XML_RESOURCE		CFSTR("resource")
19#define SD_XML_SITES		CFSTR("sites")
20#define SD_XML_VERIFICATION		CFSTR("verification")
21#define SD_XML_ATTR_ALGORITHM		CFSTR("algorithm")
22
23struct parseState {
24	CFDictionaryRef namespaces; // array of dictionaries of namespace declarations
25#if LOCAL_DEBUG
26	char* prefix;
27#endif
28	CFMutableArrayRef plists;	// array of all resource plists
29	CFMutableDictionaryRef plist;	// most recent entry in the plists array
30};
31
32
33static inline unsigned char decode64(unsigned char c)
34{
35	switch(c) {
36	case 'A': return 0;
37	case 'B': return 1;
38	case 'C': return 2;
39	case 'D': return 3;
40	case 'E': return 4;
41	case 'F': return 5;
42	case 'G': return 6;
43	case 'H': return 7;
44	case 'I': return 8;
45	case 'J': return 9;
46	case 'K': return 10;
47	case 'L': return 11;
48	case 'M': return 12;
49	case 'N': return 13;
50	case 'O': return 14;
51	case 'P': return 15;
52	case 'Q': return 16;
53	case 'R': return 17;
54	case 'S': return 18;
55	case 'T': return 19;
56	case 'U': return 20;
57	case 'V': return 21;
58	case 'W': return 22;
59	case 'X': return 23;
60	case 'Y': return 24;
61	case 'Z': return 25;
62	case 'a': return 26;
63	case 'b': return 27;
64	case 'c': return 28;
65	case 'd': return 29;
66	case 'e': return 30;
67	case 'f': return 31;
68	case 'g': return 32;
69	case 'h': return 33;
70	case 'i': return 34;
71	case 'j': return 35;
72	case 'k': return 36;
73	case 'l': return 37;
74	case 'm': return 38;
75	case 'n': return 39;
76	case 'o': return 40;
77	case 'p': return 41;
78	case 'q': return 42;
79	case 'r': return 43;
80	case 's': return 44;
81	case 't': return 45;
82	case 'u': return 46;
83	case 'v': return 47;
84	case 'w': return 48;
85	case 'x': return 49;
86	case 'y': return 50;
87	case 'z': return 51;
88	case '0': return 52;
89	case '1': return 53;
90	case '2': return 54;
91	case '3': return 55;
92	case '4': return 56;
93	case '5': return 57;
94	case '6': return 58;
95	case '7': return 59;
96	case '8': return 60;
97	case '9': return 61;
98	case '+': return 62;
99	case '/': return 63;
100	}
101	return 255;
102}
103
104// Decodes base64 data into a binary CFData object
105// If first character on a line is not in the base64 alphabet, the line
106// is ignored.
107static CFDataRef decodeBase64Data(const UInt8* ptr, size_t len) {
108	CFMutableDataRef result = CFDataCreateMutable(NULL, len); // data can't exceed len bytes
109	if (!result) return NULL;
110
111	CFIndex i, j;
112
113	int skip = 0;
114
115	UInt8 triplet[3] = {0, 0, 0};
116
117/*
118http://www.faqs.org/rfcs/rfc3548.html
119         +--first octet--+-second octet--+--third octet--+
120         |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
121         +-----------+---+-------+-------+---+-----------+
122         |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|
123         +--1.index--+--2.index--+--3.index--+--4.index--+
124*/
125
126	for (i = 0, j = 0; i < len; ++i) {
127		unsigned char c = ptr[i];
128		if (c == ' ')  { continue; }
129		if (c == '\t') { continue; }
130		if (c == '\r') { continue; }
131		if (c == '\n') { skip = 0; continue; }
132		if (skip)      { continue; }
133		if (!skip && c == '=')  { --j; skip = 1; continue; }
134		unsigned char x = decode64(c);
135		if (x == 255) { skip = 1; continue; }
136
137		if (j == 0) {
138			triplet[0] |= ((x << 2) & 0xFC);
139			++j;
140		} else if (j == 1) {
141			triplet[0] |= ((x >> 4) & 0x03);
142			triplet[1] |= ((x << 4) & 0xF0);
143			++j;
144		} else if (j == 2) {
145			triplet[1] |= ((x >> 2) & 0x0F);
146			triplet[2] |= ((x << 6) & 0xC0);
147			++j;
148		} else if (j == 3) {
149			triplet[2] |= ((x) & 0x3F);
150			CFDataAppendBytes(result, triplet, j);
151			memset(triplet, 0, sizeof(triplet));
152			j = 0;
153		}
154	}
155	if (j > 0) {
156		CFDataAppendBytes(result, triplet, j);
157	}
158	return result;
159}
160
161// Returns a CFString containing the base64 representation of the data.
162// boolean argument for whether to line wrap at 64 columns or not.
163static CFStringRef encodeBase64String(const UInt8* ptr, size_t len, int wrap) {
164	const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
165		"abcdefghijklmnopqrstuvwxyz"
166		"0123456789+/=";
167
168	// base64 encoded data uses 4 ASCII characters to represent 3 octets.
169	// There can be up to two == at the end of the base64 data for padding.
170	// If we are line wrapping then we need space for one newline character
171	// every 64 characters of output.
172	// Rounded 4/3 up to 2 to avoid floating point math.
173
174	//CFIndex max_len = (2*len) + 2;
175	//if (wrap) len = len + ((2*len) / 64) + 1;
176
177	CFMutableStringRef string = CFStringCreateMutable(NULL, 0);
178	if (!string) return NULL;
179
180/*
181http://www.faqs.org/rfcs/rfc3548.html
182         +--first octet--+-second octet--+--third octet--+
183         |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
184         +-----------+---+-------+-------+---+-----------+
185         |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|
186         +--1.index--+--2.index--+--3.index--+--4.index--+
187*/
188	int i = 0;		// octet offset into input data
189	int column = 0;		// output column number (used for line wrapping)
190	for (;;) {
191		UniChar c[16];	// buffer of characters to add to output
192		int j = 0;	// offset to place next character in buffer
193		int index;	// index into output alphabet
194
195#define ADDCHAR(_X_) do { c[j++] = _X_; if (wrap && (++column == 64)) { column = 0; c[j++] = '\n'; } } while (0);
196
197		// 1.index
198		index = (ptr[i] >> 2) & 0x3F;
199		ADDCHAR(alphabet[index]);
200
201		// 2.index
202		index = (ptr[i] << 4) & 0x30;
203		if ((i+1) < len) {
204			index = index | ((ptr[i+1] >> 4) & 0x0F);
205			ADDCHAR(alphabet[index]);
206		} else {	// end of input, pad as necessary
207			ADDCHAR(alphabet[index]);
208			ADDCHAR('=');
209			ADDCHAR('=');
210		}
211
212		// 3.index
213		if ((i+1) < len) {
214			index = (ptr[i+1] << 2) & 0x3C;
215			if ((i+2) < len) {
216				index = index | ((ptr[i+2] >> 6) & 0x03);
217				ADDCHAR(alphabet[index]);
218			} else {	// end of input, pad as necessary
219				ADDCHAR(alphabet[index]);
220				ADDCHAR('=');
221			}
222		}
223
224		// 4.index
225		if ((i+2) < len) {
226			index = (ptr[i+2]) & 0x3F;
227			ADDCHAR(alphabet[index]);
228		}
229
230		CFStringAppendCharacters(string, c, j);
231		i += 3; // we processed 3 bytes of input
232		if (i >= len) {
233			// end of data, append newline if we haven't already
234			if (wrap && c[j-1] != '\n') {
235				c[0] = '\n';
236				CFStringAppendCharacters(string, c, 1);
237			}
238			break;
239		}
240	}
241	return string;
242}
243
244
245// makes a copy of the current namespaces dictionary, adding in any
246// namespaces defined by the current node.
247static CFDictionaryRef copyNamespacesForNode(CFDictionaryRef namespaces, CFXMLNodeRef node) {
248	CFMutableDictionaryRef result = NULL;
249
250	CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node);
251
252	// careful, don't use the info unless we ensure type == kCFXMLNodeTypeElement
253	CFXMLElementInfo* info = (CFXMLElementInfo*)CFXMLNodeGetInfoPtr(node);
254
255	//
256	// create our result dictionary
257	// there are four possible configurations:
258	// 1. previous dictionary exists, this is an element, and has attributes:
259	//	clone existing dictionary, we may be adding to it
260	// 2. previous dictionary exists, not an element or no attributes:
261	//	retain existing dictionary and return
262	// 3. no previous dictionary, this is an element, and has attributes:
263	//	create new dictionary, we may be adding to it
264	// 4. no previous dictionary, not an element or no attributes:
265	//	create new dictionary and return
266	//
267	if (namespaces && type == kCFXMLNodeTypeElement && info->attributes && info->attributeOrder) {
268		result = CFDictionaryCreateMutableCopy(NULL, 0, namespaces);
269	} else if (namespaces) {
270		result = (CFMutableDictionaryRef)CFRetain(namespaces);
271		return result;
272	} else if (type == kCFXMLNodeTypeElement && info->attributes && info->attributeOrder) {
273		result = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
274		if (result) CFDictionarySetValue(result, CFSTR(""), CFSTR(""));
275	} else {
276		result = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
277		if (result) CFDictionarySetValue(result, CFSTR(""), CFSTR(""));
278		return result;
279	}
280	if (!result) return NULL;
281
282	//
283	// if we got this far, we're dealing with an XML element with
284	// attributes.  check to see if any are xml namespace attributes.
285	//
286	CFArrayRef attrs = info->attributeOrder;
287	CFIndex i, count = CFArrayGetCount(attrs);
288	for (i = 0; i < count; ++i) {
289		CFStringRef attr = CFArrayGetValueAtIndex(attrs, i);
290
291		if (CFEqual(CFSTR("xmlns"), attr)) {
292			// default namespace
293			CFStringRef value = CFDictionaryGetValue(info->attributes, attr);
294			if (value) {
295				CFDictionarySetValue(result, CFSTR(""), value);
296			}
297		} else {
298			// if the attribute is in the "xmlns" namespace, then it's
299			// really a declaration of a new namespace.  record it in our dictionary.
300			CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, attr, CFSTR(":"));
301			CFIndex numparts = parts ? CFArrayGetCount(parts) : 0;
302			if (numparts == 2) {
303				CFStringRef prefix = CFArrayGetValueAtIndex(parts, 0);
304				CFStringRef suffix = CFArrayGetValueAtIndex(parts, 1);
305				if (CFEqual(CFSTR("xmlns"), prefix)) {
306					CFStringRef value = CFDictionaryGetValue(info->attributes, attr);
307					if (value) {
308						CFDictionarySetValue(result, suffix, value);
309					}
310				}
311			}
312			if (parts) CFRelease(parts);
313		}
314	}
315	return result;
316}
317
318// returns the current node's element name and namespace URI
319// based on the currently defined namespaces.
320static void copyNodeNamespaceAndName(CFDictionaryRef namespaces, CFXMLNodeRef node, CFStringRef* namespace, CFStringRef* name) {
321	CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node);
322	*namespace = NULL;
323	*name = NULL;
324	if (type == kCFXMLNodeTypeElement) {
325		CFStringRef qname = CFXMLNodeGetString(node);
326		CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, qname, CFSTR(":"));
327		CFIndex numparts = parts ? CFArrayGetCount(parts) : 0;
328		if (numparts == 1) {
329			// default namespace
330			*namespace = CFRetain(CFDictionaryGetValue(namespaces, CFSTR("")));
331			*name = CFRetain(CFArrayGetValueAtIndex(parts, 0));
332		} else if (numparts == 2) {
333			CFStringRef prefix = CFArrayGetValueAtIndex(parts, 0);
334			CFStringRef ns = CFDictionaryGetValue(namespaces, prefix);
335			*namespace = ns ? CFRetain(ns) : NULL;
336			*name = CFRetain(CFArrayGetValueAtIndex(parts, 1));
337		} else {
338			// bogus xml
339		}
340		if (parts) CFRelease(parts);
341	}
342}
343
344// helper function for copyTreeString() below
345// appends text nodes to the mutable string context
346static void _appendTreeString(const void *value, void *context) {
347	CFXMLTreeRef tree = (CFXMLTreeRef)value;
348	CFMutableStringRef result = (CFMutableStringRef)context;
349
350	CFXMLNodeRef node = CFXMLTreeGetNode(tree);
351	CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node);
352	if (type == kCFXMLNodeTypeElement) {
353		CFTreeApplyFunctionToChildren(tree, _appendTreeString, result);
354	} else if (type == kCFXMLNodeTypeText) {
355		CFStringRef str = CFXMLNodeGetString(node);
356		if (str) CFStringAppend(result, str);
357	}
358}
359
360// equivalent to the XPATH string() function
361// concatenates all text nodes into a single string
362static CFMutableStringRef copyTreeString(CFXMLTreeRef tree) {
363	CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
364	CFTreeApplyFunctionToChildren(tree, _appendTreeString, result);
365	return result;
366}
367
368
369// returns an array of CFXMLTreeRef objects that are immediate
370// children of the context node that match the specified element
371// name and namespace URI
372static CFArrayRef copyChildrenWithName(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
373	CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
374	tree = CFTreeGetFirstChild(tree);
375	while (tree) {
376		CFXMLNodeRef node = CFXMLTreeGetNode(tree);
377		CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node);
378		if (type == kCFXMLNodeTypeElement) {
379			CFDictionaryRef namespaces = copyNamespacesForNode(inNamespaces, node);
380			if (namespaces) {
381				CFStringRef ns, n;
382				copyNodeNamespaceAndName(namespaces, node, &ns, &n);
383
384				if (ns && n && CFEqual(ns, namespace) && CFEqual(n, name)) {
385					CFArrayAppendValue(result, tree);
386				}
387				if (ns) CFRelease(ns);
388				if (n) CFRelease(n);
389
390				CFRelease(namespaces);
391			}
392		}
393		tree = CFTreeGetNextSibling(tree);
394	}
395	return result;
396}
397
398// convenience function to find the first child element
399// with the given element name and namespace URI
400static CFXMLTreeRef getChildWithName(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
401	CFXMLTreeRef result = NULL;
402	CFArrayRef array = copyChildrenWithName(tree, inNamespaces, namespace, name);
403	if (array && CFArrayGetCount(array) > 0) {
404		result = (CFXMLTreeRef)CFArrayGetValueAtIndex(array, 0);
405	}
406	if (array) CFRelease(array);
407	return result;
408}
409
410// returns the string value of the specified child node
411static CFStringRef copyChildWithNameAsString(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
412	CFStringRef result = NULL;
413	CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name);
414	if (child) result = copyTreeString(child);
415	return result;
416}
417
418// returns the integer value of the specified child node
419static CFNumberRef copyChildWithNameAsInteger(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
420	CFNumberRef result = NULL;
421	CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name);
422	if (child) {
423		CFStringRef str = copyTreeString(child);
424		if (str) {
425			SInt32 size = CFStringGetIntValue(str);
426			result = CFNumberCreate(NULL, kCFNumberSInt32Type, &size);
427			CFRelease(str);
428		}
429	}
430	return result;
431}
432
433// returns an array of URLs aggregated from the child
434// nodes matching the given name and namespace URI.
435static CFArrayRef copyChildrenWithNameAsURLs(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
436	CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
437	CFArrayRef children = copyChildrenWithName(tree, inNamespaces, namespace, name);
438	if (children) {
439		CFIndex i, count = CFArrayGetCount(children);
440		for (i = 0; i < count; ++i) {
441			CFXMLTreeRef child = (CFXMLTreeRef)CFArrayGetValueAtIndex(children, i);
442			CFStringRef str = copyTreeString(child);
443			if (str) {
444				CFURLRef url = CFURLCreateWithString(NULL, str, NULL);
445				if (url) {
446					CFArrayAppendValue(result, url);
447					CFRelease(url);
448				}
449				CFRelease(str);
450			}
451		}
452		CFRelease(children);
453	}
454	return result;
455}
456
457// returns the base 64 decoded value of the specified child node
458static CFDataRef copyChildWithNameAsData(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
459	CFDataRef result = NULL;
460	CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name);
461	if (child) {
462		CFStringRef str = copyTreeString(child);
463		if (str) {
464			CFIndex len = CFStringGetLength(str);
465			CFIndex used;
466			UInt8* buffer = malloc(len);
467			// ASCII is one byte per character.  Any inconvertible characters
468			// are assigned to whitespace and skipped.
469			if (buffer) {
470				if (CFStringGetBytes(str, CFRangeMake(0, len), kCFStringEncodingASCII, ' ', 0, buffer, len, &used)) {
471					result = decodeBase64Data(buffer, used);
472				}
473				free(buffer);
474			}
475			CFRelease(str);
476		}
477	}
478	return result;
479}
480
481// returns the CFDate value of the specified child node
482// whose string value is interpreted in W3C DateTime format
483static CFDateRef copyChildWithNameAsDate(CFXMLTreeRef tree, CFDictionaryRef inNamespaces, CFStringRef namespace, CFStringRef name) {
484	CFDateRef result = NULL;
485	CFXMLTreeRef child = getChildWithName(tree, inNamespaces, namespace, name);
486	if (child) {
487		CFMutableStringRef str = copyTreeString(child);
488		if (str) {
489			CFStringTrimWhitespace(str);
490			if (CFStringGetLength(str) > 21) {
491				CFStringRef year = CFStringCreateWithSubstring(NULL, str, CFRangeMake(0, 4));
492				CFStringRef month = CFStringCreateWithSubstring(NULL, str, CFRangeMake(5, 2));
493				CFStringRef day = CFStringCreateWithSubstring(NULL, str, CFRangeMake(8, 2));
494				CFStringRef hour = CFStringCreateWithSubstring(NULL, str, CFRangeMake(11, 2));
495				CFStringRef minute = CFStringCreateWithSubstring(NULL, str, CFRangeMake(14, 2));
496				CFStringRef second = CFStringCreateWithSubstring(NULL, str, CFRangeMake(17, 2));
497				CFStringRef tenth = CFStringCreateWithSubstring(NULL, str, CFRangeMake(20, 1));
498
499				CFGregorianDate gregory;
500				memset(&gregory, 0, sizeof(gregory));
501				if (year) { gregory.year = CFStringGetIntValue(year); CFRelease(year); }
502				if (month) { gregory.month = CFStringGetIntValue(month); CFRelease(month); }
503				if (day) { gregory.day = CFStringGetIntValue(day); CFRelease(day); }
504				if (hour) { gregory.hour = CFStringGetIntValue(hour); CFRelease(hour); }
505				if (minute) { gregory.minute = CFStringGetIntValue(minute); CFRelease(minute); }
506				if (second) { gregory.second = (double)CFStringGetIntValue(second); CFRelease(second); }
507				if (tenth) { gregory.second += ((double)CFStringGetIntValue(tenth)/(double)10.0); CFRelease(tenth); }
508
509				CFTimeZoneRef tz = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
510				if (tz) {
511					CFAbsoluteTime at = CFGregorianDateGetAbsoluteTime(gregory, tz);
512					result = CFDateCreate(NULL, at);
513					CFRelease(tz);
514				}
515			}
516			CFRelease(str);
517		}
518	}
519	return result;
520}
521
522
523// generic parser for XML nodes in our ticket format
524static void _parseXMLNode(const void *value, void *context) {
525	CFXMLTreeRef tree = (CFXMLTreeRef)value;
526	struct parseState* state = (struct parseState*)context;
527
528	CFXMLNodeRef node = CFXMLTreeGetNode(tree);
529	assert(node);
530
531	CFDictionaryRef namespaces = copyNamespacesForNode(state->namespaces, node);
532
533	CFXMLNodeTypeCode type = CFXMLNodeGetTypeCode(node);
534
535	int descend = 0;
536
537	if (type == kCFXMLNodeTypeElement) {
538		CFStringRef ns, name;
539		copyNodeNamespaceAndName(namespaces, node, &ns, &name);
540
541		if (ns && name) {
542
543			if (CFEqual(ns, SD_XML_NAMESPACE)) {
544				if (CFEqual(name, SD_XML_ROOT)) {
545
546					descend = 1;
547
548				} else if (CFEqual(name, SD_XML_RESOURCE)) {
549
550					state->plist = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
551					if (state->plist) {
552						CFArrayAppendValue(state->plists, state->plist);
553						CFRelease(state->plist);
554					}
555
556					CFStringRef name = copyChildWithNameAsString(tree, namespaces, SD_XML_NAMESPACE, SD_XML_NAME);
557					if (name && state->plist) {
558						CFDictionarySetValue(state->plist, SD_XML_NAME, name);
559						CFRelease(name);
560					}
561
562					CFNumberRef size = copyChildWithNameAsInteger(tree, namespaces, SD_XML_NAMESPACE, SD_XML_SIZE);
563					if (size && state->plist) {
564						CFDictionarySetValue(state->plist, SD_XML_SIZE, size);
565						CFRelease(size);
566					}
567
568					CFDateRef created = copyChildWithNameAsDate(tree, namespaces, SD_XML_NAMESPACE, SD_XML_CREATED);
569					if (created && state->plist) {
570						CFDictionarySetValue(state->plist, SD_XML_CREATED, created);
571						CFRelease(created);
572					}
573
574					descend = 1;
575
576				} else if (CFEqual(name, SD_XML_SITES)) {
577					CFArrayRef urls = copyChildrenWithNameAsURLs(tree, namespaces, SD_XML_NAMESPACE, SD_XML_URL);
578					if (urls && state->plist) {
579						CFDictionarySetValue(state->plist, SD_XML_URL, urls);
580						CFRelease(urls);
581					}
582
583				} else if (CFEqual(name, SD_XML_VERIFICATIONS)) {
584					CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
585					if (dict && state->plist) {
586						CFDictionarySetValue(state->plist, SD_XML_VERIFICATIONS, dict);
587						CFRelease(dict);
588						descend = 1;
589					}
590
591				} else if (CFEqual(name, SD_XML_VERIFICATION) && state->plist) {
592					CFMutableDictionaryRef verifications = (CFMutableDictionaryRef)CFDictionaryGetValue(state->plist, SD_XML_VERIFICATIONS);
593					CFXMLElementInfo* info = (CFXMLElementInfo*)CFXMLNodeGetInfoPtr(node);
594					if (verifications && info && info->attributes) {
595						CFStringRef algorithm = CFDictionaryGetValue(info->attributes, SD_XML_ATTR_ALGORITHM);
596						if (algorithm) {
597							CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
598							if (dict) {
599								CFXMLTreeRef child;
600								#pragma unused(child)
601
602								CFNumberRef sector_size = copyChildWithNameAsInteger(tree, namespaces, SD_XML_NAMESPACE, SD_XML_SECTOR_SIZE);
603								if (sector_size) {
604									CFDictionarySetValue(dict, SD_XML_SECTOR_SIZE, sector_size);
605									CFRelease(sector_size);
606								}
607
608								CFDataRef digest = copyChildWithNameAsData(tree, namespaces, SD_XML_NAMESPACE, SD_XML_DIGEST);
609								if (digest) {
610									CFDictionarySetValue(dict, SD_XML_DIGEST, digest);
611									CFRelease(digest);
612								}
613
614								CFDataRef digests = copyChildWithNameAsData(tree, namespaces, SD_XML_NAMESPACE, SD_XML_DIGESTS);
615								if (digests) {
616									CFDictionarySetValue(dict, SD_XML_DIGESTS, digests);
617									CFRelease(digests);
618								}
619
620								CFDictionarySetValue(verifications, algorithm, dict);
621								CFRelease(dict);
622							}
623						}
624					}
625				}
626			}
627#if LOCAL_DEBUG
628			cfprintf(stderr, "%sELEM:\t%@\t[%@]\n", state->prefix, name, ns);
629#endif
630		}
631		if (ns) CFRelease(ns);
632		if (name) CFRelease(name);
633	} else if (type == kCFXMLNodeTypeWhitespace) {
634		// do nothing
635	} else {
636#if LOCAL_DEBUG
637		CFStringRef str = CFXMLNodeGetString(node);
638		cfprintf(stderr, "%s% 4d:\t%@\n", state->prefix, type, str);
639#endif
640	}
641
642	// only recurse further if we have been specifically instructed to
643	// do so.
644	if (descend) {
645		struct parseState local;
646		memcpy(&local, state, sizeof(struct parseState));
647		local.namespaces = namespaces;
648#if LOCAL_DEBUG
649		asprintf(&local.prefix, "%s  ", state->prefix);
650#endif
651		CFTreeApplyFunctionToChildren(tree, _parseXMLNode, &local);
652#if LOCAL_DEBUG
653		free(local.prefix);
654#endif
655	}
656	if (namespaces) CFRelease(namespaces);
657}
658
659CFPropertyListRef _SecureDownloadParseTicketXML(CFDataRef xmlData) {
660	if (!xmlData) return NULL;
661	CFURLRef url = NULL;
662
663	CFXMLTreeRef tree = CFXMLTreeCreateFromData(NULL, xmlData, url, kCFXMLParserNoOptions, kCFXMLNodeCurrentVersion);
664	if (!tree) return NULL;
665
666	struct parseState state;
667	memset(&state, 0, sizeof(state));
668#if LOCAL_DEBUG
669	state.prefix = "";
670#endif
671	state.plists = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
672	if (state.plists) {
673		CFTreeApplyFunctionToChildren(tree, _parseXMLNode, &state);
674	}
675	CFRelease(tree);
676
677	CFPropertyListRef result = NULL;
678	// For now, only return the first resource encountered
679	if (state.plists && CFArrayGetCount(state.plists) > 0) {
680		result = CFArrayGetValueAtIndex(state.plists, 0);
681		CFRetain(result);
682	}
683	if (state.plists) CFRelease(state.plists);
684	return result;
685}
686
687static void _appendCString(CFMutableDataRef data, const char* cstring) {
688	CFDataAppendBytes(data, (UInt8*)cstring, strlen(cstring));
689}
690
691static void _appendCFString(CFMutableDataRef data, CFStringRef string) {
692	CFDataRef utf8 = CFStringCreateExternalRepresentation(NULL, string, kCFStringEncodingUTF8, '?');
693	if (utf8) {
694		CFDataAppendBytes(data, CFDataGetBytePtr(utf8), CFDataGetLength(utf8));
695		CFRelease(utf8);
696	}
697}
698
699static void _appendCFNumber(CFMutableDataRef data, CFNumberRef number) {
700	CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), number);
701	if (str) {
702		_appendCFString(data, str);
703		CFRelease(str);
704	}
705}
706
707static void _appendCFURL(CFMutableDataRef data, CFURLRef url) {
708	CFURLRef abs = CFURLCopyAbsoluteURL(url);
709	if (abs) {
710		CFStringRef str = CFURLGetString(abs);
711		if (str) {
712			_appendCFString(data, str);
713		}
714		CFRelease(abs);
715	}
716}
717
718// appends data in base64 encoded form
719static void _appendCFData(CFMutableDataRef data, CFDataRef moreData) {
720	CFStringRef str = encodeBase64String(CFDataGetBytePtr(moreData), CFDataGetLength(moreData), 0);
721	if (str) {
722		_appendCFString(data, str);
723		CFRelease(str);
724	}
725}
726
727// appends a date in W3C DateTime format
728static void _appendCFDate(CFMutableDataRef data, CFDateRef date) {
729	CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US"));
730	if (locale) {
731		CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
732		if (formatter) {
733			CFDateFormatterSetFormat(formatter, CFSTR("yyyy-MM-dd'T'HH:mm:ss.S'Z'"));
734			CFTimeZoneRef tz = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
735			if (tz) {
736				CFDateFormatterSetProperty(formatter, kCFDateFormatterTimeZone, tz);
737				CFStringRef str = CFDateFormatterCreateStringWithDate(NULL, formatter, date);
738				if (str) {
739					_appendCFString(data, str);
740					CFRelease(str);
741				}
742				CFRelease(tz);
743			}
744			CFRelease(formatter);
745		}
746		CFRelease(locale);
747	}
748}
749
750static CFArrayRef dictionaryGetSortedKeys(CFDictionaryRef dictionary) {
751        CFIndex count = CFDictionaryGetCount(dictionary);
752
753        const void** keys = malloc(sizeof(CFStringRef) * count);
754        CFDictionaryGetKeysAndValues(dictionary, keys, NULL);
755        CFArrayRef keysArray = CFArrayCreate(NULL, keys, count, &kCFTypeArrayCallBacks);
756        CFMutableArrayRef sortedKeys = CFArrayCreateMutableCopy(NULL, count, keysArray);
757        CFRelease(keysArray);
758        free(keys);
759
760        CFArraySortValues(sortedKeys, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, 0);
761        return sortedKeys;
762}
763
764CFDataRef _SecureDownloadCreateTicketXML(CFPropertyListRef plist) {
765	CFMutableDataRef data = CFDataCreateMutable(NULL, 0);
766	if (!data) return NULL;
767
768	_appendCString(data, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
769	_appendCString(data, "<SecureDownload xmlns=\"http://www.apple.com/2006/SecureDownload/1\">\n");
770	_appendCString(data, "    <resource>\n");
771
772	CFStringRef name = CFDictionaryGetValue(plist, SD_XML_NAME);
773	if (name) {
774		_appendCString(data, "\t<name>");
775		_appendCFString(data, name);
776		_appendCString(data, "</name>\n");
777	}
778
779	CFNumberRef num = CFDictionaryGetValue(plist, SD_XML_SIZE);
780	if (num) {
781		_appendCString(data, "\t<size>");
782		_appendCFNumber(data, num);
783		_appendCString(data, "</size>\n");
784	}
785
786	CFDateRef created = CFDictionaryGetValue(plist, SD_XML_CREATED);
787	if (created) {
788		_appendCString(data, "\t<created>");
789		_appendCFDate(data, created);
790		_appendCString(data, "</created>\n");
791	}
792
793	_appendCString(data, "\t<sites>\n");
794	CFArrayRef urls = CFDictionaryGetValue(plist, SD_XML_URL);
795	if (urls) {
796		CFIndex i, count = CFArrayGetCount(urls);
797		for (i = 0; i < count; ++i) {
798			_appendCString(data, "\t\t<url>");
799			_appendCFURL(data, CFArrayGetValueAtIndex(urls, i));
800			_appendCString(data, "</url>\n");
801		}
802	}
803	_appendCString(data, "\t</sites>\n");
804
805	CFDictionaryRef verifications = CFDictionaryGetValue(plist, SD_XML_VERIFICATIONS);
806	if (verifications) {
807		_appendCString(data, "\t<verifications>\n");
808		CFArrayRef algorithms = dictionaryGetSortedKeys(verifications);
809		if (algorithms) {
810			CFIndex i, count = CFArrayGetCount(algorithms);
811			for (i = 0; i < count; ++i) {
812				CFStringRef algorithm = CFArrayGetValueAtIndex(algorithms, i);
813				if (algorithm) {
814					_appendCString(data, "\t\t<verification algorithm=\"");
815					_appendCFString(data, algorithm);
816					_appendCString(data, "\">\n");
817					CFDictionaryRef dict = CFDictionaryGetValue(verifications, algorithm);
818					if (dict) {
819						CFDataRef digest = CFDictionaryGetValue(dict, SD_XML_DIGEST);
820						if (digest) {
821							_appendCString(data, "\t\t\t<digest>");
822							_appendCFData(data, digest);
823							_appendCString(data, "</digest>\n");
824						}
825
826						CFNumberRef sector_size = CFDictionaryGetValue(dict, SD_XML_SECTOR_SIZE);
827						if (sector_size) {
828							_appendCString(data, "\t\t\t<sector_size>");
829							_appendCFNumber(data, sector_size);
830							_appendCString(data, "</sector_size>\n");
831						}
832
833						CFDataRef digests = CFDictionaryGetValue(dict, SD_XML_DIGESTS);
834						if (digest) {
835							_appendCString(data, "\t\t\t<digests>");
836							_appendCFData(data, digests);
837							_appendCString(data, "</digests>\n");
838						}
839					}
840					_appendCString(data, "\t\t</verification>\n");
841				}
842			}
843			CFRelease(algorithms);
844		}
845		_appendCString(data, "\t</verifications>\n");
846	}
847
848	_appendCString(data, "    </resource>\n");
849	_appendCString(data, "</SecureDownload>\n");
850
851	return data;
852}
853
854#if LOCAL_DEBUG
855#include <unistd.h>
856int main(int argc, char* argv[]) {
857
858	CFDataRef data = read_data("/Users/kevin/Desktop/SecureDownloadXML/SecureDownload.xml");
859	if (data) {
860		CFPropertyListRef plist = _SecureDownloadParseTicketXML(data);
861		CFShow(plist);
862		if (plist) {
863			CFDataRef output = _SecureDownloadCreateTicketXML(plist);
864			if (output) {
865				write(STDOUT_FILENO, CFDataGetBytePtr(output), CFDataGetLength(output));
866				CFRelease(output);
867			}
868			CFRelease(plist);
869		}
870		CFRelease(data);
871	}
872
873	// help look for leaks
874	cfprintf(stderr, "pid = %d\n", getpid());
875	sleep(1000);
876}
877#endif
878