]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | #include <CoreFoundation/CoreFoundation.h> |
2 | ||
3 | #include "SecureDownloadInternal.h" | |
4 | ||
5 | // | |
6 | // SecureDownloadXML: SecureDownloadXML.c | |
7 | // cc -g -framework CoreFoundation -o $@ $^ | |
8 | // | |
9 | ||
427c49bc | 10 | #define LOCAL_DEBUG 0 |
b1ab9ed8 | 11 | |
427c49bc | 12 | #if LOCAL_DEBUG |
b1ab9ed8 A |
13 | extern 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 | ||
23 | struct parseState { | |
24 | CFDictionaryRef namespaces; // array of dictionaries of namespace declarations | |
427c49bc | 25 | #if LOCAL_DEBUG |
b1ab9ed8 A |
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 | ||
33 | static 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. | |
107 | static 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 | /* | |
118 | http://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. | |
163 | static 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 | /* | |
181 | http://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. | |
247 | static 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. | |
320 | static 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 | |
346 | static 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 | |
362 | static 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 | |
372 | static 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 | |
400 | static 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 | |
411 | static 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 | |
419 | static 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. | |
435 | static 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 | |
458 | static 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 | |
483 | static 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 | |
524 | static 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 | } | |
427c49bc | 627 | #if LOCAL_DEBUG |
b1ab9ed8 A |
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 { | |
427c49bc | 636 | #if LOCAL_DEBUG |
b1ab9ed8 A |
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; | |
427c49bc | 648 | #if LOCAL_DEBUG |
b1ab9ed8 A |
649 | asprintf(&local.prefix, "%s ", state->prefix); |
650 | #endif | |
651 | CFTreeApplyFunctionToChildren(tree, _parseXMLNode, &local); | |
427c49bc | 652 | #if LOCAL_DEBUG |
b1ab9ed8 A |
653 | free(local.prefix); |
654 | #endif | |
655 | } | |
656 | if (namespaces) CFRelease(namespaces); | |
657 | } | |
658 | ||
659 | CFPropertyListRef _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)); | |
427c49bc | 668 | #if LOCAL_DEBUG |
b1ab9ed8 A |
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 | ||
687 | static void _appendCString(CFMutableDataRef data, const char* cstring) { | |
688 | CFDataAppendBytes(data, (UInt8*)cstring, strlen(cstring)); | |
689 | } | |
690 | ||
691 | static 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 | ||
699 | static 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 | ||
707 | static 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 | |
719 | static 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 | |
728 | static 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 | ||
750 | static 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 | ||
764 | CFDataRef _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 | ||
427c49bc | 854 | #if LOCAL_DEBUG |
b1ab9ed8 A |
855 | #include <unistd.h> |
856 | int 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 |