Comment remaining unsolved bug.
[ardour.git] / libs / backends / wavesaudio / portmidi / src / pm_mac / readbinaryplist.c
1 /*
2
3 readbinaryplist.c -- Roger B. Dannenberg, Jun 2008
4 Based on ReadBinaryPList.m by Jens Ayton, 2007
5
6 Note that this code is intended to read preference files and has an upper
7 bound on file size (currently 100MB) and assumes in some places that 32 bit
8 offsets are sufficient.
9
10 Here are his comments:
11
12 Reader for binary property list files (version 00).
13
14 This has been found to work on all 566 binary plists in my ~/Library/Preferences/
15 and /Library/Preferences/ directories. This probably does not provide full
16 test coverage. It has also been found to provide different data to Apple's
17 implementation when presented with a key-value archive. This is because Apple's
18 implementation produces undocumented CFKeyArchiverUID objects. My implementation
19 produces dictionaries instead, matching the in-file representation used in XML
20 and OpenStep plists. See extract_uid().
21
22 Full disclosure: in implementing this software, I read one comment and one
23 struct defintion in CFLite, Apple's implementation, which is under the APSL
24 license. I also deduced the information about CFKeyArchiverUID from that code.
25 However, none of the implementation was copied.
26
27 Copyright (C) 2007 Jens Ayton
28
29 Permission is hereby granted, free of charge, to any person obtaining a copy
30 of this software and associated documentation files (the "Software"), to deal
31 in the Software without restriction, including without limitation the rights
32 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33 copies of the Software, and to permit persons to whom the Software is
34 furnished to do so, subject to the following conditions:
35
36 The above copyright notice and this permission notice shall be included in all
37 copies or substantial portions of the Software.
38
39 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45 SOFTWARE.
46
47 */
48
49 /* A note about memory management:
50 Strings and possibly other values are unique and because the values
51 associated with IDs are cached, you end up with a directed graph rather
52 than a tree. It is tricky to free the data because if you do a simple
53 depth-first search to free nodes, you will free nodes twice. I decided
54 to allocate memory from blocks of 1024 bytes and keep the blocks in a
55 list associated with but private to this module. So the user should
56 access this module by calling:
57     bplist_read_file() or bplist_read_user_pref() or
58     bplist_read_system_pref()
59 which returns a value. When you are done with the value, call
60     bplist_free_data()
61 This will of course free the value_ptr returned by bplist_read_*()
62
63 To deal with memory exhaustion (what happens when malloc returns
64 NULL?), use setjmp/longjmp -- a single setjmp protects the whole
65 parser, and allocate uses longjmp to abort. After abort, memory
66 is freed and NULL is returned to caller. There is not much here
67 in the way of error reporting.
68
69 Memory is obtained by calling allocate which either returns the
70 memory requested or calls longjmp, so callers don't have to check.
71
72 */
73
74 #include <sys/types.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <assert.h>
78 #include <stdio.h>
79 #include <sys/stat.h>
80 #include "readbinaryplist.h"
81 #include <Carbon/Carbon.h>
82
83 #define NO 0
84 #define YES 1
85 #define BOOL int
86
87 #define MAXPATHLEN 256
88
89 /* there are 2 levels of error logging/printing:
90  *   BPLIST_LOG and BPLIST_LOG_VERBOSE
91  * either or both can be set to non-zero to turn on
92  * If BPLIST_LOG_VERBOSE is true, then BPLIST_LOG
93  * is also true.
94  *
95  * In the code, logging is done by calling either
96  * bplist_log() or bplist_log_verbose(), which take
97  * parameters like printf but might be a no-op.
98  */
99
100 /* #define BPLIST_LOG_VERBOSE 1 */
101
102 #if BPLIST_LOG_VERBOSE
103     #ifndef BPLIST_LOG
104         #define BPLIST_LOG 1
105     #endif
106 #endif
107
108 #if BPLIST_LOG
109     #define bplist_log printf
110 #else
111     #define bplist_log(...)
112 #endif
113
114 #if BPLIST_LOG_VERBOSE
115     #define bplist_log_verbose bplist_log
116 #else
117     #define bplist_log_verbose(...)
118 #endif
119
120
121 /********* MEMORY MANAGEMENT ********/
122 #define BLOCK_SIZE 1024
123 // memory is aligned to multiples of this; assume malloc automatically
124 // aligns to this number and assume this number is > sizeof(void *)
125 #define ALIGNMENT 8
126 static void *block_list = NULL;
127 static char *free_ptr = NULL;
128 static char *end_ptr = NULL;
129 static jmp_buf abort_parsing;
130
131 static void *allocate(size_t size)
132 {
133     void *result;
134     if (free_ptr + size > end_ptr) {
135         size_t how_much = BLOCK_SIZE;
136         // align everything to 8 bytes
137         if (size > BLOCK_SIZE - ALIGNMENT) {
138             how_much = size + ALIGNMENT;
139         }
140         result = malloc(how_much);
141         if (result == NULL) {
142             /* serious problem */
143             longjmp(abort_parsing, 1);
144         }
145         *((void **)result) = block_list;
146         block_list = result;
147         free_ptr = ((char *) result) + ALIGNMENT;
148         end_ptr = ((char *) result) + how_much;
149     }
150     // now, there is enough rooom at free_ptr
151     result = free_ptr;
152     free_ptr += size;
153     return result;
154 }
155
156 void bplist_free_data()
157 {
158     while (block_list) {
159         void *next = *(void **)block_list;
160         free(block_list);
161         block_list = next;
162     }
163     free_ptr = NULL;
164     end_ptr = NULL;
165 }
166
167 // layout of trailer -- last 32 bytes in plist data
168     uint8_t unused[6];
169     uint8_t offset_int_size;
170     uint8_t object_ref_size;
171     uint64_t object_count;
172     uint64_t top_level_object;
173     uint64_t offset_table_offset;
174
175
176 enum
177 {
178     kHEADER_SIZE = 8,
179     kTRAILER_SIZE = 32, //sizeof(bplist_trailer_node),
180     kMINIMUM_SANE_SIZE = kHEADER_SIZE + kTRAILER_SIZE
181 };
182
183
184 static const char kHEADER_BYTES[kHEADER_SIZE] = "bplist00";
185
186 // map from UID key to previously parsed value
187 typedef struct cache_struct {
188     uint64_t key;
189     value_ptr value;
190     struct cache_struct *next;
191 } cache_node, *cache_ptr;
192
193
194 typedef struct bplist_info
195 {
196     uint64_t object_count;
197     const uint8_t *data_bytes;
198     uint64_t length;
199     uint64_t offset_table_offset;
200     uint8_t offset_int_size;
201     uint8_t object_ref_size;
202     cache_ptr cache;
203 } bplist_info_node, *bplist_info_ptr;
204
205
206 static value_ptr bplist_read_pldata(pldata_ptr data);
207 static value_ptr bplist_read_pref(char *filename, OSType folder_type);
208 static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset, uint8_t size);
209 static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index);
210 static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset, uint64_t *outValue, size_t *outSize);
211
212 static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef);
213 static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset);
214 static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset);
215 static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset);
216 static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset);
217 static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset);
218 static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset);
219 static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset);
220 static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset);
221 static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset);
222 static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset);
223
224
225 value_ptr value_create()
226 {
227     value_ptr value = (value_ptr) allocate(sizeof(value_node));
228     return value;
229 }
230
231
232 void value_set_integer(value_ptr v, int64_t i) {
233     v->tag = kTAG_INT; v->integer = i;
234 }
235
236 void value_set_real(value_ptr v, double d) {
237     v->tag = kTAG_REAL; v->real = d;
238 }
239
240 // d is seconds since 1 January 2001
241 void value_set_date(value_ptr v, double d) {
242     v->tag = kTAG_DATE; v->real = d;
243 }
244
245 void value_set_ascii_string(value_ptr v, const uint8_t *s, size_t len) {
246     v->tag = kTAG_ASCIISTRING;
247     v->string = (char *) allocate(len + 1);
248     memcpy(v->string, s, len);
249     v->string[len] = 0;
250 }
251
252 void value_set_unicode_string(value_ptr v, const uint8_t *s, size_t len) {
253     v->tag = kTAG_UNICODESTRING;
254     v->string = (char *) allocate(len + 1);
255     memcpy(v->string, s, len);
256     v->string[len] = 0;
257 }
258
259 void value_set_uid(value_ptr v, uint64_t uid)
260 {
261     v->tag = kTAG_UID; v->uinteger = uid;
262 }
263
264 // v->data points to a pldata that points to the actual bytes
265 // the bytes are copied, so caller must free byte source (*data)
266 void value_set_data(value_ptr v, const uint8_t *data, size_t len) {
267     v->tag = kTAG_DATA;
268     pldata_ptr pldata = (pldata_ptr) allocate(sizeof(pldata_node));
269     pldata->data = (uint8_t *) allocate(len);
270     memcpy(pldata->data, data, len);
271     pldata->len = len;
272     v->data = pldata;
273     printf("value at %p gets data at %p\n", v, pldata);
274 }
275
276 // caller releases ownership of array to value_ptr v
277 void value_set_array(value_ptr v, value_ptr *array, size_t length) {
278     array_ptr a = (array_ptr) allocate(sizeof(array_node));
279     a->array = array;
280     a->length = length;
281     v->tag = kTAG_ARRAY;
282     v->array = a;
283 }
284
285 // caller releases ownership of dict to value_ptr v
286 void value_set_dict(value_ptr v, dict_ptr dict) {
287     v->tag = kTAG_DICTIONARY;
288     v->dict = dict;
289 }
290
291
292 // look up an objectref in the cache, a ref->value_ptr mapping
293 value_ptr cache_lookup(cache_ptr cache, uint64_t ref)
294 {
295     while (cache) {
296         if (cache->key == ref) {
297             return cache->value;
298         }
299         cache = cache->next;
300     }
301     return NULL;
302 }
303
304
305 // insert an objectref and value in the cache
306 void cache_insert(cache_ptr *cache, uint64_t ref, value_ptr value)
307 {
308     cache_ptr c = (cache_ptr) allocate(sizeof(cache_node));
309     c->key = ref;
310     c->value = value;
311     c->next = *cache;
312     *cache = c;
313 }
314
315
316 // insert an objectref and value in a dictionary
317 void dict_insert(dict_ptr *dict, value_ptr key, value_ptr value)
318 {
319     dict_ptr d = (dict_ptr) allocate(sizeof(dict_node));
320     d->key = key;
321     d->value = value;
322     d->next = *dict;
323     *dict = d;
324 }
325
326
327 BOOL is_binary_plist(pldata_ptr data)
328 {
329     if (data->len < kMINIMUM_SANE_SIZE)  return NO;
330     return memcmp(data->data, kHEADER_BYTES, kHEADER_SIZE) == 0;
331 }
332
333
334 value_ptr bplist_read_file(char *filename)
335 {
336     struct stat stbuf;
337     pldata_node pldata;
338     FILE *file;
339     size_t n;
340     value_ptr value;
341     int rslt = stat(filename, &stbuf);
342     if (rslt) {
343         #if BPLIST_LOG
344             perror("in stat");
345         #endif
346         bplist_log("Could not stat %s, error %d\n", filename, rslt);
347         return NULL;
348     }
349     // if file is >100MB, assume it is not a preferences file and give up
350     if (stbuf.st_size > 100000000) {
351         bplist_log("Large file %s encountered (%llu bytes) -- not read\n",
352                    filename, stbuf.st_size);
353         return NULL;
354     }
355     pldata.len = (size_t) stbuf.st_size;
356     // note: this is supposed to be malloc, not allocate. It is separate
357     // from the graph structure, large, and easy to free right after
358     // parsing.
359     pldata.data = (uint8_t *) malloc(pldata.len);
360     if (!pldata.data) {
361         bplist_log("Could not allocate %lu bytes for %s\n",
362                    (unsigned long) pldata.len, filename);
363         return NULL;
364     }
365     file = fopen(filename, "rb");
366     if (!file) {
367         bplist_log("Could not open %s\n", filename);
368         return NULL;
369     }
370     n = fread(pldata.data, 1, pldata.len, file);
371     if (n != pldata.len) {
372         bplist_log("Error reading from %s\n", filename);
373         return NULL;
374     }
375     value = bplist_read_pldata(&pldata);
376     free(pldata.data);
377     return value;
378 }
379
380
381 value_ptr bplist_read_pref(char *filename, OSType folder_type)
382 {
383     FSRef prefdir;
384     char cstr[MAXPATHLEN];
385
386     OSErr err = FSFindFolder(kOnAppropriateDisk, folder_type,
387                              FALSE, &prefdir);
388     if (err) {
389         bplist_log("Error finding preferences folder: %d\n", err);
390         return NULL;
391     }
392     err = FSRefMakePath(&prefdir, (UInt8 *) cstr, (UInt32) (MAXPATHLEN - 1));
393     if (err) {
394         bplist_log("Error making path name for preferences folder: %d\n", err);
395         return NULL;
396     }
397     strlcat(cstr, "/", MAXPATHLEN);
398     strlcat(cstr, filename, MAXPATHLEN);
399     return bplist_read_file(cstr);
400 }
401
402
403 value_ptr bplist_read_system_pref(char *filename) {
404     return bplist_read_pref(filename, kSystemPreferencesFolderType);
405 }
406
407
408 value_ptr bplist_read_user_pref(char *filename) {
409     return bplist_read_pref(filename, kPreferencesFolderType);
410 }
411
412
413 // data is stored with high-order bytes first.
414 // read from plist data in a machine-independent fashion
415 //
416 uint64_t convert_uint64(uint8_t *ptr)
417 {
418     uint64_t rslt = 0;
419     int i;
420     // shift in bytes, high-order first
421     for (i = 0; i < sizeof(uint64_t); i++) {
422         rslt <<= 8;
423         rslt += ptr[i];
424     }
425     return rslt;
426 }
427
428
429 value_ptr bplist_read_pldata(pldata_ptr data)
430 {
431     value_ptr result = NULL;
432     bplist_info_node bplist;
433     uint8_t *ptr;
434     uint64_t top_level_object;
435     int i;
436
437     if (data == NULL)  return NULL;
438     if (!is_binary_plist(data)) {
439         bplist_log("Bad binary plist: too short or invalid header.\n");
440         return NULL;
441     }
442
443     // read trailer
444     ptr = (uint8_t *) (data->data + data->len - kTRAILER_SIZE);
445     bplist.offset_int_size = ptr[6];
446     bplist.object_ref_size = ptr[7];
447     bplist.object_count = convert_uint64(ptr + 8);
448     top_level_object = convert_uint64(ptr + 16);
449     bplist.offset_table_offset = convert_uint64(ptr + 24);
450
451     // Basic sanity checks
452     if (bplist.offset_int_size < 1 || bplist.offset_int_size > 8 ||
453         bplist.object_ref_size < 1 || bplist.object_ref_size > 8 ||
454         bplist.offset_table_offset < kHEADER_SIZE) {
455         bplist_log("Bad binary plist: trailer declared insane.\n");
456         return NULL;
457     }
458
459     // Ensure offset table is inside file
460     uint64_t offsetTableSize = bplist.offset_int_size * bplist.object_count;
461     if (offsetTableSize + bplist.offset_table_offset + kTRAILER_SIZE >
462         data->len) {
463         bplist_log("Bad binary plist: offset table overlaps end of container.\n");
464         return NULL;
465     }
466
467     bplist.data_bytes = data->data;
468     bplist.length = data->len;
469     bplist.cache = NULL; /* dictionary is empty */
470
471     bplist_log_verbose("Got a sane bplist with %llu items, offset_int_size: %u, object_ref_size: %u\n",
472                       bplist.object_count, bplist.offset_int_size,
473                       bplist.object_ref_size);
474     /* at this point, we are ready to do some parsing which allocates
475         memory for the result data structure. If memory allocation (using
476         allocate fails, a longjmp will return to here and we simply give up
477      */
478     i = setjmp(abort_parsing);
479     if (i == 0) {
480         result = extract_object(&bplist, top_level_object);
481     } else {
482         bplist_log("allocate() failed to allocate memory. Giving up.\n");
483         result = NULL;
484     }
485     if (!result) {
486         bplist_free_data();
487     }
488     return result;
489 }
490
491
492 static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef)
493 {
494     uint64_t offset;
495     value_ptr result = NULL;
496     uint8_t objectTag;
497
498     if (objectRef >= bplist->object_count) {
499         // Out-of-range object reference.
500         bplist_log("Bad binary plist: object index is out of range.\n");
501         return NULL;
502     }
503
504     // Use cached object if it exists
505     result = cache_lookup(bplist->cache, objectRef);
506     if (result != NULL)  return result;
507
508     // Otherwise, find object in file.
509     offset = read_offset(bplist, objectRef);
510     if (offset > bplist->length) {
511         // Out-of-range offset.
512         bplist_log("Bad binary plist: object outside container.\n");
513         return NULL;
514     }
515     objectTag = *(bplist->data_bytes + offset);
516     switch (objectTag & 0xF0) {
517     case kTAG_SIMPLE:
518         result = extract_simple(bplist, offset);
519         break;
520
521     case kTAG_INT:
522         result = extract_int(bplist, offset);
523         break;
524
525     case kTAG_REAL:
526         result = extract_real(bplist, offset);
527         break;
528
529     case kTAG_DATE:
530         result = extract_date(bplist, offset);
531         break;
532
533     case kTAG_DATA:
534         result = extract_data(bplist, offset);
535         break;
536
537     case kTAG_ASCIISTRING:
538         result = extract_ascii_string(bplist, offset);
539         break;
540
541     case kTAG_UNICODESTRING:
542         result = extract_unicode_string(bplist, offset);
543         break;
544
545     case kTAG_UID:
546         result = extract_uid(bplist, offset);
547         break;
548
549     case kTAG_ARRAY:
550         result = extract_array(bplist, offset);
551         break;
552
553     case kTAG_DICTIONARY:
554         result = extract_dictionary(bplist, offset);
555         break;
556
557     default:
558         // Unknown tag.
559         bplist_log("Bad binary plist: unknown tag 0x%X.\n",
560                    (objectTag & 0x0F) >> 4);
561         result = NULL;
562     }
563
564     // Cache and return result.
565     if (result != NULL)
566         cache_insert(&bplist->cache, objectRef, result);
567     return result;
568 }
569
570
571 static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset,
572                                uint8_t size)
573 {
574     assert(bplist->data_bytes != NULL && size >= 1 && size <= 8 &&
575            offset + size <= bplist->length);
576
577     uint64_t result = 0;
578     const uint8_t *byte = bplist->data_bytes + offset;
579
580     do {
581         // note that ints seem to be high-order first
582         result = (result << 8) | *byte++;
583     } while (--size);
584
585     return result;
586 }
587
588
589 static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index)
590 {
591     assert(index < bplist->object_count);
592
593     return read_sized_int(bplist,
594             bplist->offset_table_offset + bplist->offset_int_size * index,
595             bplist->offset_int_size);
596 }
597
598
599 static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset,
600                              uint64_t *outValue, size_t *outSize)
601 {
602     uint32_t size;
603     int64_t value;
604
605     assert(bplist->data_bytes != NULL && offset < bplist->length);
606
607     size = 1 << (bplist->data_bytes[offset] & 0x0F);
608     if (size > 8) {
609         // Maximum allowable size in this implementation is 1<<3 = 8 bytes.
610         // This also happens to be the biggest we can handle.
611         return NO;
612     }
613
614     if (offset + 1 + size > bplist->length) {
615         // Out of range.
616         return NO;
617     }
618
619     value = read_sized_int(bplist, offset + 1, size);
620
621     if (outValue != NULL) *outValue = value;
622     if (outSize != NULL) *outSize = size + 1; // +1 for tag byte.
623     return YES;
624 }
625
626
627 static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset)
628 {
629     assert(bplist->data_bytes != NULL && offset < bplist->length);
630     value_ptr value = value_create();
631
632     switch (bplist->data_bytes[offset]) {
633     case kVALUE_NULL:
634         value->tag = kVALUE_NULL;
635         return value;
636
637     case kVALUE_TRUE:
638         value->tag = kVALUE_TRUE;
639         return value;
640
641     case kVALUE_FALSE:
642         value->tag = kVALUE_FALSE;
643         return value;
644     }
645
646     // Note: kVALUE_FILLER is treated as invalid, because it, er, is.
647     bplist_log("Bad binary plist: invalid atom.\n");
648     free(value);
649     return NULL;
650 }
651
652
653 static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset)
654 {
655     value_ptr value = value_create();
656     value->tag = kTAG_INT;
657
658     if (!read_self_sized_int(bplist, offset, &value->uinteger, NULL)) {
659         bplist_log("Bad binary plist: invalid integer object.\n");
660     }
661
662     /* NOTE: originally, I sign-extended here. This was the wrong thing; it
663        turns out that negative ints are always stored as 64-bit, and smaller
664        ints are unsigned.
665     */
666     return value;
667 }
668
669
670 static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset)
671 {
672     value_ptr value = value_create();
673     uint32_t size;
674
675     assert(bplist->data_bytes != NULL && offset < bplist->length);
676
677     size = 1 << (bplist->data_bytes[offset] & 0x0F);
678
679     // FIXME: what to do if faced with other sizes for float/double?
680     assert (sizeof (float) == sizeof (uint32_t) &&
681             sizeof (double) == sizeof (uint64_t));
682
683     if (offset + 1 + size > bplist->length) {
684         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
685                   "floating-point number");
686         free(value);
687         return NULL;
688     }
689
690     if (size == sizeof (float)) {
691         // cast is ok because we know size is 4 bytes
692         uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size);
693         // Note that this handles byte swapping.
694         value_set_real(value, *(float *)&i);
695         return value;
696     } else if (size == sizeof (double)) {
697         uint64_t i = read_sized_int(bplist, offset + 1, size);
698         // Note that this handles byte swapping.
699         value_set_real(value, *(double *)&i);
700         return value;
701     } else {
702         // Can't handle floats of other sizes.
703         bplist_log("Bad binary plist: can't handle %u-byte float.\n", size);
704         free(value);
705         return NULL;
706     }
707 }
708
709
710 static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset)
711 {
712     value_ptr value;
713     assert(bplist->data_bytes != NULL && offset < bplist->length);
714
715     // Data has size code like int and real, but only 3 (meaning 8 bytes) is valid.
716     if (bplist->data_bytes[offset] != kVALUE_FULLDATETAG) {
717         bplist_log("Bad binary plist: invalid size for date object.\n");
718         return NULL;
719     }
720
721     if (offset + 1 + sizeof (double) > bplist->length) {
722         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
723                   "date");
724         return NULL;
725     }
726
727     // FIXME: what to do if faced with other sizes for double?
728     assert (sizeof (double) == sizeof (uint64_t));
729
730     uint64_t date = read_sized_int(bplist, offset + 1, sizeof(double));
731     // Note that this handles byte swapping.
732     value = value_create();
733     value_set_date(value, *(double *)&date);
734     return value;
735 }
736
737
738 uint64_t bplist_get_a_size(bplist_info_ptr bplist,
739                            uint64_t *offset_ptr, char *msg)
740 {
741     uint64_t size = bplist->data_bytes[*offset_ptr] & 0x0F;
742     (*offset_ptr)++;
743     if (size == 0x0F) {
744         // 0x0F means separate int size follows.
745         // Smaller values are used for short data.
746         size_t extra; // the length of the data size we are about to read
747         if ((bplist->data_bytes[*offset_ptr] & 0xF0) != kTAG_INT) {
748             // Bad data, mistagged size int
749             bplist_log("Bad binary plist: %s object size is not tagged as int.\n",
750                        msg);
751             return UINT64_MAX; // error
752         }
753
754         // read integer data as size, extra tells how many bytes to skip
755         if (!read_self_sized_int(bplist, *offset_ptr, &size, &extra)) {
756             bplist_log("Bad binary plist: invalid %s object size tag.\n",
757                       "data");
758             return UINT64_MAX; // error
759         }
760         (*offset_ptr) += extra;
761     }
762
763     if (*offset_ptr + size > bplist->length) {
764         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
765                   "data");
766         return UINT64_MAX; // error
767     }
768     return size;
769 }
770
771
772 static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset)
773 {
774     uint64_t size;
775     value_ptr value;
776
777     assert(bplist->data_bytes != NULL && offset < bplist->length);
778
779     if ((size = bplist_get_a_size(bplist, &offset, "data")) == UINT64_MAX)
780         return NULL;
781
782     value = value_create();
783     // cast is ok because we only allow files up to 100MB:
784     value_set_data(value, bplist->data_bytes + (size_t) offset, (size_t) size);
785     return value;
786 }
787
788
789 static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset)
790 {
791     uint64_t size;
792     value_ptr value; // return value
793
794     assert(bplist->data_bytes != NULL && offset < bplist->length);
795
796     if ((size = bplist_get_a_size(bplist, &offset, "ascii string")) ==
797         UINT64_MAX)
798         return NULL;
799
800     value = value_create();
801     // cast is ok because we only allow 100MB files
802     value_set_ascii_string(value, bplist->data_bytes + (size_t) offset,
803                            (size_t) size);
804     return value;
805 }
806
807
808 static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset)
809 {
810     uint64_t size;
811     value_ptr value;
812
813     assert(bplist->data_bytes != NULL && offset < bplist->length);
814
815     if ((size = bplist_get_a_size(bplist, &offset, "unicode string")) ==
816         UINT64_MAX)
817         return NULL;
818
819     value = value_create();
820     // cast is ok because we only allow 100MB files
821     value_set_unicode_string(value, bplist->data_bytes + (size_t) offset,
822                              (size_t) size);
823     return value;
824 }
825
826
827 static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset)
828 {
829     /* UIDs are used by Cocoa's key-value coder.
830        When writing other plist formats, they are expanded to dictionaries of
831        the form <dict><key>CF$UID</key><integer>value</integer></dict>, so we
832        do the same here on reading. This results in plists identical to what
833        running plutil -convert xml1 gives us. However, this is not the same
834        result as [Core]Foundation's plist parser, which extracts them as un-
835        introspectable CF objects. In fact, it even seems to convert the CF$UID
836        dictionaries from XML plists on the fly.
837     */
838
839     value_ptr value;
840     uint64_t uid;
841
842     if (!read_self_sized_int(bplist, offset, &uid, NULL)) {
843         bplist_log("Bad binary plist: invalid UID object.\n");
844         return NULL;
845     }
846
847     // assert(NO); // original code suggests using a string for a key
848     // but our dictionaries all use big ints for keys, so I don't know
849     // what to do here
850
851     // In practice, I believe this code is never executed by PortMidi.
852     // I changed it to do something and not raise compiler warnings, but
853     // not sure what the code should do.
854
855     value = value_create();
856     value_set_uid(value, uid);
857     // return [NSDictionary dictionaryWithObject:
858     //         [NSNumber numberWithUnsignedLongLong:value]
859     //         forKey:"CF$UID"];
860     return value;
861 }
862
863
864 static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset)
865 {
866     uint64_t i, count;
867     uint64_t size;
868     uint64_t elementID;
869     value_ptr element = NULL;
870     value_ptr *array = NULL;
871     value_ptr value = NULL;
872     BOOL ok = YES;
873
874     assert(bplist->data_bytes != NULL && offset < bplist->length);
875
876     if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX)
877         return NULL;
878
879     if (count > UINT64_MAX / bplist->object_ref_size - offset) {
880         // Offset overflow.
881         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
882                    "array");
883         return NULL;
884     }
885
886     size = bplist->object_ref_size * count;
887     if (size + offset > bplist->length) {
888         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
889                    "array");
890         return NULL;
891     }
892
893     // got count, the number of array elements
894
895     value = value_create();
896     assert(value);
897
898     if (count == 0) {
899         // count must be size_t or smaller because max file size is 100MB
900         value_set_array(value, array, (size_t) count);
901         return value;
902     }
903
904     array = allocate(sizeof(value_ptr) * (size_t) count);
905
906     for (i = 0; i != count; ++i) {
907         bplist_log_verbose("[%u]\n", i);
908         elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size,
909                                  bplist->object_ref_size);
910         element = extract_object(bplist, elementID);
911         if (element != NULL) {
912             array[i] = element;
913         } else {
914             ok = NO;
915             break;
916         }
917     }
918     if (ok) { // count is smaller than size_t max because of 100MB file limit
919         value_set_array(value, array, (size_t) count);
920     }
921
922     return value;
923 }
924
925
926 static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset)
927 {
928     uint64_t i, count;
929     uint64_t size;
930     uint64_t elementID;
931     value_ptr value = NULL;
932     dict_ptr dict = NULL;
933     BOOL ok = YES;
934
935     assert(bplist->data_bytes != NULL && offset < bplist->length);
936
937
938     if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX)
939         return NULL;
940
941     if (count > UINT64_MAX / (bplist->object_ref_size * 2) - offset) {
942         // Offset overflow.
943         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
944                    "dictionary");
945         return NULL;
946     }
947
948     size = bplist->object_ref_size * count * 2;
949     if (size + offset > bplist->length) {
950         bplist_log("Bad binary plist: %s object overlaps end of container.\n",
951                    "dictionary");
952         return NULL;
953     }
954
955     value = value_create();
956     if (count == 0) {
957         value_set_dict(value, NULL);
958         return value;
959     }
960
961     for (i = 0; i != count; ++i) {
962         value_ptr key;
963         value_ptr val;
964         elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size,
965                                  bplist->object_ref_size);
966         key = extract_object(bplist, elementID);
967         if (key != NULL) {
968             bplist_log_verbose("key: %p\n", key);
969         } else {
970             ok = NO;
971             break;
972         }
973
974         elementID = read_sized_int(bplist,
975                             offset + (i + count) * bplist->object_ref_size,
976                             bplist->object_ref_size);
977         val = extract_object(bplist, elementID);
978         if (val != NULL) {
979             dict_insert(&dict, key, val);
980         } else {
981             ok = NO;
982             break;
983         }
984     }
985     if (ok) {
986         value_set_dict(value, dict);
987     }
988
989     return value;
990 }
991
992 /*************** functions for accessing values ****************/
993
994
995 char *value_get_asciistring(value_ptr v)
996 {
997     if (v->tag != kTAG_ASCIISTRING) return NULL;
998     return v->string;
999 }
1000
1001
1002 value_ptr value_dict_lookup_using_string(value_ptr v, char *key)
1003 {
1004     dict_ptr dict;
1005     if (v->tag != kTAG_DICTIONARY) return NULL; // not a dictionary
1006     dict = v->dict;
1007     /* search for key */
1008     while (dict) {
1009         if (dict->key && dict->key->tag == kTAG_ASCIISTRING &&
1010             strcmp(key, dict->key->string) == 0) { // found it
1011             return dict->value;
1012         }
1013         dict = dict->next;
1014     }
1015     return NULL; /* not found */
1016 }
1017
1018 value_ptr value_dict_lookup_using_path(value_ptr v, char *path)
1019 {
1020     char key[MAX_KEY_SIZE];
1021     while (*path) { /* more to the path */
1022         int i = 0;
1023         while (i < MAX_KEY_SIZE - 1) {
1024             key[i] = *path++;
1025             if (key[i] == '/') { /* end of entry in path */
1026                 key[i + 1] = 0;
1027                 break;
1028             }
1029             if (!key[i]) {
1030                 path--; /* back up to end of string char */
1031                 break;  /* this will cause outer loop to exit */
1032             }
1033             i++;
1034         }
1035         if (!v || v->tag != kTAG_DICTIONARY) return NULL;
1036         /* now, look up the key to get next value */
1037         v = value_dict_lookup_using_string(v, key);
1038         if (v == NULL) return NULL;
1039     }
1040     return v;
1041 }
1042
1043
1044 /*************** functions for debugging ***************/
1045
1046 void plist_print(value_ptr v)
1047 {
1048     size_t i;
1049     int comma_needed;
1050     dict_ptr dict;
1051     if (!v) {
1052         printf("NULL");
1053         return;
1054     }
1055     switch (v->tag & 0xF0) {
1056     case kTAG_SIMPLE:
1057         switch (v->tag) {
1058         case kVALUE_NULL:
1059             printf("NULL@%p", v); break;
1060         case kVALUE_FALSE:
1061             printf("FALSE@%p", v); break;
1062         case kVALUE_TRUE:
1063             printf("TRUE@%p", v); break;
1064         default:
1065             printf("UNKNOWN tag=%x@%p", v->tag, v); break;
1066         }
1067         break;
1068     case kTAG_INT:
1069         printf("%lld@%p", v->integer, v); break;
1070     case kTAG_REAL:
1071         printf("%g@%p", v->real, v); break;
1072     case kTAG_DATE:
1073         printf("date:%g@%p", v->real, v); break;
1074     case kTAG_DATA:
1075         printf("data@%p->%p:[%p:", v, v->data, v->data->data);
1076         for (i = 0; i < v->data->len; i++) {
1077             printf(" %2x", v->data->data[i]);
1078         }
1079         printf("]"); break;
1080     case kTAG_ASCIISTRING:
1081         printf("%p:\"%s\"@%p", v->string, v->string, v); break;
1082     case kTAG_UNICODESTRING:
1083         printf("unicode:%p:\"%s\"@%p", v->string, v->string, v); break;
1084     case kTAG_UID:
1085         printf("UID:%llu@%p", v->uinteger, v); break;
1086     case kTAG_ARRAY:
1087         comma_needed = FALSE;
1088         printf("%p->%p:[%p:", v, v->array, v->array->array);
1089         for (i = 0; i < v->array->length; i++) {
1090             if (comma_needed) printf(", ");
1091             plist_print(v->array->array[i]);
1092             comma_needed = TRUE;
1093         }
1094         printf("]"); break;
1095     case kTAG_DICTIONARY:
1096         comma_needed = FALSE;
1097         printf("%p:[", v);
1098         dict = v->dict;
1099         while (dict) {
1100             if (comma_needed) printf(", ");
1101             printf("%p:", dict);
1102             plist_print(dict->key);
1103             printf("->");
1104             plist_print(dict->value);
1105             comma_needed = TRUE;
1106             dict = dict->next;
1107         }
1108         printf("]"); break;
1109     default:
1110         printf("UNKNOWN tag=%x", v->tag);
1111         break;
1112     }
1113 }
1114
1115