static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset) { uint64_t i, count; uint64_t size; uint64_t elementID; value_ptr element = NULL; value_ptr *array = NULL; value_ptr value = NULL; BOOL ok = YES; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX) return NULL; if (count > UINT64_MAX / bplist->object_ref_size - offset) { // Offset overflow. bplist_log("Bad binary plist: %s object overlaps end of container.\n", "array"); return NULL; } size = bplist->object_ref_size * count; if (size + offset > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "array"); return NULL; } // got count, the number of array elements value = value_create(); assert(value); if (count == 0) { // count must be size_t or smaller because max file size is 100MB value_set_array(value, array, (size_t) count); return value; } array = allocate(sizeof(value_ptr) * (size_t) count); for (i = 0; i != count; ++i) { bplist_log_verbose("[%u]\n", i); elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, bplist->object_ref_size); element = extract_object(bplist, elementID); if (element != NULL) { array[i] = element; } else { ok = NO; break; } } if (ok) { // count is smaller than size_t max because of 100MB file limit value_set_array(value, array, (size_t) count); } return value; }
uint64_t bplist_get_a_size(bplist_info_ptr bplist, uint64_t *offset_ptr, char *msg) { uint64_t size = bplist->data_bytes[*offset_ptr] & 0x0F; (*offset_ptr)++; if (size == 0x0F) { // 0x0F means separate int size follows. // Smaller values are used for short data. size_t extra; // the length of the data size we are about to read if ((bplist->data_bytes[*offset_ptr] & 0xF0) != kTAG_INT) { // Bad data, mistagged size int bplist_log("Bad binary plist: %s object size is not tagged as int.\n", msg); return UINT64_MAX; // error } // read integer data as size, extra tells how many bytes to skip if (!read_self_sized_int(bplist, *offset_ptr, &size, &extra)) { bplist_log("Bad binary plist: invalid %s object size tag.\n", "data"); return UINT64_MAX; // error } (*offset_ptr) += extra; } if (*offset_ptr + size > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "data"); return UINT64_MAX; // error } return size; }
static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset) { value_ptr value; assert(bplist->data_bytes != NULL && offset < bplist->length); // Data has size code like int and real, but only 3 (meaning 8 bytes) is valid. if (bplist->data_bytes[offset] != kVALUE_FULLDATETAG) { bplist_log("Bad binary plist: invalid size for date object.\n"); return NULL; } if (offset + 1 + sizeof (double) > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "date"); return NULL; } // FIXME: what to do if faced with other sizes for double? assert (sizeof (double) == sizeof (uint64_t)); uint64_t date = read_sized_int(bplist, offset + 1, sizeof(double)); // Note that this handles byte swapping. value = value_create(); value_set_date(value, *(double *)&date); return value; }
static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset) { /* UIDs are used by Cocoa's key-value coder. When writing other plist formats, they are expanded to dictionaries of the form <dict><key>CF$UID</key><integer>value</integer></dict>, so we do the same here on reading. This results in plists identical to what running plutil -convert xml1 gives us. However, this is not the same result as [Core]Foundation's plist parser, which extracts them as un- introspectable CF objects. In fact, it even seems to convert the CF$UID dictionaries from XML plists on the fly. */ value_ptr value; uint64_t uid; if (!read_self_sized_int(bplist, offset, &uid, NULL)) { bplist_log("Bad binary plist: invalid UID object.\n"); return NULL; } // assert(NO); // original code suggests using a string for a key // but our dictionaries all use big ints for keys, so I don't know // what to do here // In practice, I believe this code is never executed by PortMidi. // I changed it to do something and not raise compiler warnings, but // not sure what the code should do. value = value_create(); value_set_uid(value, uid); // return [NSDictionary dictionaryWithObject: // [NSNumber numberWithUnsignedLongLong:value] // forKey:"CF$UID"]; return value; }
value_ptr bplist_read_pref(char *filename, OSType folder_type) { char cstr[MAXPATHLEN]; char *foundstr; memset(cstr, 0, MAXPATHLEN); // for later OS X, the user preferences folder (~/Library/Preferences) is not available directly from Cocoa, // Apple documentation suggests just using POSIX APIs like so. if (folder_type == kPreferencesFolderType) { strlcpy(cstr, getenv("HOME"), MAXPATHLEN); strlcat(cstr, "/Library/Preferences", MAXPATHLEN); } else // the system preferences folder (~/Library/PreferencePanes) is accessible from Cocoa however { foundstr = FindPrefsDir(); if (!foundstr) { bplist_log("Error finding preferences folder\n"); return NULL; } strlcat(cstr, foundstr, MAXPATHLEN); free(foundstr); foundstr = NULL; } strlcat(cstr, "/", MAXPATHLEN); strlcat(cstr, filename, MAXPATHLEN); return bplist_read_file(cstr); }
value_ptr bplist_read_file(char *filename) { struct stat stbuf; pldata_node pldata; FILE *file; size_t n; value_ptr value; int rslt = stat(filename, &stbuf); if (rslt) { #if BPLIST_LOG perror("in stat"); #endif bplist_log("Could not stat %s, error %d\n", filename, rslt); return NULL; } // if file is >100MB, assume it is not a preferences file and give up if (stbuf.st_size > 100000000) { bplist_log("Large file %s encountered (%llu bytes) -- not read\n", filename, stbuf.st_size); return NULL; } pldata.len = (size_t) stbuf.st_size; // note: this is supposed to be malloc, not allocate. It is separate // from the graph structure, large, and easy to free right after // parsing. pldata.data = (uint8_t *) malloc(pldata.len); if (!pldata.data) { bplist_log("Could not allocate %lu bytes for %s\n", (unsigned long) pldata.len, filename); return NULL; } file = fopen(filename, "rb"); if (!file) { bplist_log("Could not open %s\n", filename); return NULL; } n = fread(pldata.data, 1, pldata.len, file); if (n != pldata.len) { bplist_log("Error reading from %s\n", filename); return NULL; } value = bplist_read_pldata(&pldata); free(pldata.data); return value; }
value_ptr bplist_read_pref(char *filename, OSType folder_type) { FSRef prefdir; char cstr[MAXPATHLEN]; OSErr err = FSFindFolder(kOnAppropriateDisk, folder_type, FALSE, &prefdir); if (err) { bplist_log("Error finding preferences folder: %d\n", err); return NULL; } err = FSRefMakePath(&prefdir, (UInt8 *) cstr, (UInt32) (MAXPATHLEN - 1)); if (err) { bplist_log("Error making path name for preferences folder: %d\n", err); return NULL; } strlcat(cstr, "/", MAXPATHLEN); strlcat(cstr, filename, MAXPATHLEN); return bplist_read_file(cstr); }
static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset) { value_ptr value = value_create(); uint32_t size; assert(bplist->data_bytes != NULL && offset < bplist->length); size = 1 << (bplist->data_bytes[offset] & 0x0F); // FIXME: what to do if faced with other sizes for float/double? assert (sizeof (float) == sizeof (uint32_t) && sizeof (double) == sizeof (uint64_t)); if (offset + 1 + size > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "floating-point number"); free(value); return NULL; } if (size == sizeof (float)) { // cast is ok because we know size is 4 bytes uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size); // Note that this handles byte swapping. value_set_real(value, *(float *)&i); return value; } else if (size == sizeof (double)) { uint64_t i = read_sized_int(bplist, offset + 1, size); // Note that this handles byte swapping. value_set_real(value, *(double *)&i); return value; } else { // Can't handle floats of other sizes. bplist_log("Bad binary plist: can't handle %u-byte float.\n", size); free(value); return NULL; } }
static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset) { value_ptr value = value_create(); value->tag = kTAG_INT; if (!read_self_sized_int(bplist, offset, &value->uinteger, NULL)) { bplist_log("Bad binary plist: invalid integer object.\n"); } /* NOTE: originally, I sign-extended here. This was the wrong thing; it turns out that negative ints are always stored as 64-bit, and smaller ints are unsigned. */ return value; }
static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset) { assert(bplist->data_bytes != NULL && offset < bplist->length); value_ptr value = value_create(); switch (bplist->data_bytes[offset]) { case kVALUE_NULL: value->tag = kVALUE_NULL; return value; case kVALUE_TRUE: value->tag = kVALUE_TRUE; return value; case kVALUE_FALSE: value->tag = kVALUE_FALSE; return value; } // Note: kVALUE_FILLER is treated as invalid, because it, er, is. bplist_log("Bad binary plist: invalid atom.\n"); free(value); return NULL; }
static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset) { uint64_t i, count; uint64_t size; uint64_t elementID; value_ptr value = NULL; dict_ptr dict = NULL; BOOL ok = YES; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX) return NULL; if (count > UINT64_MAX / (bplist->object_ref_size * 2) - offset) { // Offset overflow. bplist_log("Bad binary plist: %s object overlaps end of container.\n", "dictionary"); return NULL; } size = bplist->object_ref_size * count * 2; if (size + offset > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "dictionary"); return NULL; } value = value_create(); if (count == 0) { value_set_dict(value, NULL); return value; } for (i = 0; i != count; ++i) { value_ptr key; value_ptr val; elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, bplist->object_ref_size); key = extract_object(bplist, elementID); if (key != NULL) { bplist_log_verbose("key: %p\n", key); } else { ok = NO; break; } elementID = read_sized_int(bplist, offset + (i + count) * bplist->object_ref_size, bplist->object_ref_size); val = extract_object(bplist, elementID); if (val != NULL) { dict_insert(&dict, key, val); } else { ok = NO; break; } } if (ok) { value_set_dict(value, dict); } return value; }
static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef) { uint64_t offset; value_ptr result = NULL; uint8_t objectTag; if (objectRef >= bplist->object_count) { // Out-of-range object reference. bplist_log("Bad binary plist: object index is out of range.\n"); return NULL; } // Use cached object if it exists result = cache_lookup(bplist->cache, objectRef); if (result != NULL) return result; // Otherwise, find object in file. offset = read_offset(bplist, objectRef); if (offset > bplist->length) { // Out-of-range offset. bplist_log("Bad binary plist: object outside container.\n"); return NULL; } objectTag = *(bplist->data_bytes + offset); switch (objectTag & 0xF0) { case kTAG_SIMPLE: result = extract_simple(bplist, offset); break; case kTAG_INT: result = extract_int(bplist, offset); break; case kTAG_REAL: result = extract_real(bplist, offset); break; case kTAG_DATE: result = extract_date(bplist, offset); break; case kTAG_DATA: result = extract_data(bplist, offset); break; case kTAG_ASCIISTRING: result = extract_ascii_string(bplist, offset); break; case kTAG_UNICODESTRING: result = extract_unicode_string(bplist, offset); break; case kTAG_UID: result = extract_uid(bplist, offset); break; case kTAG_ARRAY: result = extract_array(bplist, offset); break; case kTAG_DICTIONARY: result = extract_dictionary(bplist, offset); break; default: // Unknown tag. bplist_log("Bad binary plist: unknown tag 0x%X.\n", (objectTag & 0x0F) >> 4); result = NULL; } // Cache and return result. if (result != NULL) cache_insert(&bplist->cache, objectRef, result); return result; }
value_ptr bplist_read_pldata(pldata_ptr data) { value_ptr result = NULL; bplist_info_node bplist; uint8_t *ptr; uint64_t top_level_object; int i; if (data == NULL) return NULL; if (!is_binary_plist(data)) { bplist_log("Bad binary plist: too short or invalid header.\n"); return NULL; } // read trailer ptr = (uint8_t *) (data->data + data->len - kTRAILER_SIZE); bplist.offset_int_size = ptr[6]; bplist.object_ref_size = ptr[7]; bplist.object_count = convert_uint64(ptr + 8); top_level_object = convert_uint64(ptr + 16); bplist.offset_table_offset = convert_uint64(ptr + 24); // Basic sanity checks if (bplist.offset_int_size < 1 || bplist.offset_int_size > 8 || bplist.object_ref_size < 1 || bplist.object_ref_size > 8 || bplist.offset_table_offset < kHEADER_SIZE) { bplist_log("Bad binary plist: trailer declared insane.\n"); return NULL; } // Ensure offset table is inside file uint64_t offsetTableSize = bplist.offset_int_size * bplist.object_count; if (offsetTableSize + bplist.offset_table_offset + kTRAILER_SIZE > data->len) { bplist_log("Bad binary plist: offset table overlaps end of container.\n"); return NULL; } bplist.data_bytes = data->data; bplist.length = data->len; bplist.cache = NULL; /* dictionary is empty */ bplist_log_verbose("Got a sane bplist with %llu items, offset_int_size: %u, object_ref_size: %u\n", bplist.object_count, bplist.offset_int_size, bplist.object_ref_size); /* at this point, we are ready to do some parsing which allocates memory for the result data structure. If memory allocation (using allocate fails, a longjmp will return to here and we simply give up */ i = setjmp(abort_parsing); if (i == 0) { result = extract_object(&bplist, top_level_object); } else { bplist_log("allocate() failed to allocate memory. Giving up.\n"); result = NULL; } if (!result) { bplist_free_data(); } return result; }