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