/src/libplist/src/bplist.c
Line | Count | Source |
1 | | /* |
2 | | * bplist.c |
3 | | * Binary plist implementation |
4 | | * |
5 | | * Copyright (c) 2011-2017 Nikias Bassen, All Rights Reserved. |
6 | | * Copyright (c) 2008-2010 Jonathan Beck, All Rights Reserved. |
7 | | * |
8 | | * This library is free software; you can redistribute it and/or |
9 | | * modify it under the terms of the GNU Lesser General Public |
10 | | * License as published by the Free Software Foundation; either |
11 | | * version 2.1 of the License, or (at your option) any later version. |
12 | | * |
13 | | * This library is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | | * Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public |
19 | | * License along with this library; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | | */ |
22 | | |
23 | | #ifdef HAVE_CONFIG_H |
24 | | #include <config.h> |
25 | | #endif |
26 | | |
27 | | #include <stdlib.h> |
28 | | #include <stdio.h> |
29 | | #include <string.h> |
30 | | #include <assert.h> |
31 | | |
32 | | #include <ctype.h> |
33 | | #include <inttypes.h> |
34 | | |
35 | | #include "plist.h" |
36 | | #include "hashtable.h" |
37 | | #include "bytearray.h" |
38 | | #include "ptrarray.h" |
39 | | #include "plist/plist.h" |
40 | | |
41 | | #include <node.h> |
42 | | |
43 | | /* Magic marker and size. */ |
44 | 0 | #define BPLIST_MAGIC ((uint8_t*)"bplist") |
45 | 0 | #define BPLIST_MAGIC_SIZE 6 |
46 | | |
47 | 0 | #define BPLIST_VERSION ((uint8_t*)"00") |
48 | 0 | #define BPLIST_VERSION_SIZE 2 |
49 | | |
50 | | #pragma pack(push,1) |
51 | | typedef struct { |
52 | | uint8_t unused[6]; |
53 | | uint8_t offset_size; |
54 | | uint8_t ref_size; |
55 | | uint64_t num_objects; |
56 | | uint64_t root_object_index; |
57 | | uint64_t offset_table_offset; |
58 | | } bplist_trailer_t; |
59 | | #pragma pack(pop) |
60 | | |
61 | | enum |
62 | | { |
63 | | BPLIST_NULL = 0x00, |
64 | | BPLIST_FALSE = 0x08, |
65 | | BPLIST_TRUE = 0x09, |
66 | | BPLIST_FILL = 0x0F, /* will be used for length grabbing */ |
67 | | BPLIST_INT = 0x10, |
68 | | BPLIST_REAL = 0x20, |
69 | | BPLIST_DATE = 0x30, |
70 | | BPLIST_DATA = 0x40, |
71 | | BPLIST_STRING = 0x50, |
72 | | BPLIST_UNICODE = 0x60, |
73 | | BPLIST_UNK_0x70 = 0x70, |
74 | | BPLIST_UID = 0x80, |
75 | | BPLIST_ARRAY = 0xA0, |
76 | | BPLIST_SET = 0xC0, |
77 | | BPLIST_DICT = 0xD0, |
78 | | BPLIST_MASK = 0xF0 |
79 | | }; |
80 | | |
81 | | union plist_uint_ptr |
82 | | { |
83 | | const void *src; |
84 | | uint8_t *u8ptr; |
85 | | uint16_t *u16ptr; |
86 | | uint32_t *u32ptr; |
87 | | uint64_t *u64ptr; |
88 | | }; |
89 | | |
90 | | #ifdef _MSC_VER |
91 | | uint64_t get_unaligned_64(uint64_t *ptr) |
92 | | { |
93 | | uint64_t temp; |
94 | | memcpy(&temp, ptr, sizeof(temp)); |
95 | | return temp; |
96 | | } |
97 | | |
98 | | uint32_t get_unaligned_32(uint32_t *ptr) |
99 | | { |
100 | | uint32_t temp; |
101 | | memcpy(&temp, ptr, sizeof(temp)); |
102 | | return temp; |
103 | | } |
104 | | |
105 | | uint16_t get_unaligned_16(uint16_t *ptr) |
106 | | { |
107 | | uint16_t temp; |
108 | | memcpy(&temp, ptr, sizeof(temp)); |
109 | | return temp; |
110 | | } |
111 | | #else |
112 | | #define get_unaligned(ptr) \ |
113 | | ({ \ |
114 | | struct __attribute__((packed)) { \ |
115 | | typeof(*(ptr)) __v; \ |
116 | | } *__p = (void *) (ptr); \ |
117 | | __p->__v; \ |
118 | | }) |
119 | | #endif |
120 | | |
121 | | |
122 | | #ifndef bswap16 |
123 | | #define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8)) |
124 | | #endif |
125 | | |
126 | | #ifndef bswap32 |
127 | 0 | #define bswap32(x) ((((x) & 0xFF000000) >> 24) \ |
128 | 0 | | (((x) & 0x00FF0000) >> 8) \ |
129 | 0 | | (((x) & 0x0000FF00) << 8) \ |
130 | 0 | | (((x) & 0x000000FF) << 24)) |
131 | | #endif |
132 | | |
133 | | #ifndef bswap64 |
134 | 0 | #define bswap64(x) ((((x) & 0xFF00000000000000ull) >> 56) \ |
135 | 0 | | (((x) & 0x00FF000000000000ull) >> 40) \ |
136 | 0 | | (((x) & 0x0000FF0000000000ull) >> 24) \ |
137 | 0 | | (((x) & 0x000000FF00000000ull) >> 8) \ |
138 | 0 | | (((x) & 0x00000000FF000000ull) << 8) \ |
139 | 0 | | (((x) & 0x0000000000FF0000ull) << 24) \ |
140 | 0 | | (((x) & 0x000000000000FF00ull) << 40) \ |
141 | 0 | | (((x) & 0x00000000000000FFull) << 56)) |
142 | | #endif |
143 | | |
144 | | #ifndef be16toh |
145 | | #ifdef __BIG_ENDIAN__ |
146 | | #define be16toh(x) (x) |
147 | | #else |
148 | | #define be16toh(x) bswap16(x) |
149 | | #endif |
150 | | #endif |
151 | | |
152 | | #ifndef be32toh |
153 | | #ifdef __BIG_ENDIAN__ |
154 | | #define be32toh(x) (x) |
155 | | #else |
156 | | #define be32toh(x) bswap32(x) |
157 | | #endif |
158 | | #endif |
159 | | |
160 | | #ifndef be64toh |
161 | | #ifdef __BIG_ENDIAN__ |
162 | | #define be64toh(x) (x) |
163 | | #else |
164 | | #define be64toh(x) bswap64(x) |
165 | | #endif |
166 | | #endif |
167 | | |
168 | | #ifdef __BIG_ENDIAN__ |
169 | | #define beNtoh(x,n) (x >> ((8-n) << 3)) |
170 | | #else |
171 | 0 | #define beNtoh(x,n) be64toh((x) << ((8-(n)) << 3)) |
172 | | #endif |
173 | | |
174 | | #ifdef _MSC_VER |
175 | | static uint64_t UINT_TO_HOST(const void* x, uint8_t n) |
176 | | { |
177 | | union plist_uint_ptr __up; |
178 | | __up.src = (n > 8) ? (const char*)x + (n - 8) : (const char*)x; |
179 | | return (n >= 8 ? be64toh( get_unaligned_64(__up.u64ptr) ) : |
180 | | (n == 4 ? be32toh( get_unaligned_32(__up.u32ptr) ) : |
181 | | (n == 2 ? be16toh( get_unaligned_16(__up.u16ptr) ) : |
182 | | (n == 1 ? *__up.u8ptr : |
183 | | beNtoh( get_unaligned_64(__up.u64ptr), n) |
184 | | )))); |
185 | | } |
186 | | #else |
187 | | #define UINT_TO_HOST(x, n) \ |
188 | 0 | ({ \ |
189 | 0 | union plist_uint_ptr __up; \ |
190 | 0 | __up.src = ((n) > 8) ? (const char*)(x) + ((n) - 8) : (const char*)(x); \ |
191 | 0 | ((n) >= 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \ |
192 | 0 | ((n) == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \ |
193 | 0 | ((n) == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \ |
194 | 0 | ((n) == 1 ? *__up.u8ptr : \ |
195 | 0 | beNtoh( get_unaligned(__up.u64ptr), n) \ |
196 | 0 | )))); \ |
197 | 0 | }) |
198 | | #endif |
199 | | |
200 | | #define get_needed_bytes(x) \ |
201 | 0 | ( ((uint64_t)(x)) < (1ULL << 8) ? 1 : \ |
202 | 0 | ( ((uint64_t)(x)) < (1ULL << 16) ? 2 : \ |
203 | 0 | ( ((uint64_t)(x)) < (1ULL << 24) ? 3 : \ |
204 | 0 | ( ((uint64_t)(x)) < (1ULL << 32) ? 4 : 8)))) |
205 | | |
206 | 0 | #define get_real_bytes(x) ((x) == (float) (x) ? sizeof(float) : sizeof(double)) |
207 | | |
208 | | #if (defined(__BIG_ENDIAN__) && !defined(__FLOAT_WORD_ORDER__)) \ |
209 | | || (defined(__FLOAT_WORD_ORDER__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__) |
210 | | #define float_bswap64(x) (x) |
211 | | #define float_bswap32(x) (x) |
212 | | #else |
213 | 0 | #define float_bswap64(x) bswap64(x) |
214 | 0 | #define float_bswap32(x) bswap32(x) |
215 | | #endif |
216 | | |
217 | | #ifndef __has_builtin |
218 | | #define __has_builtin(x) 0 |
219 | | #endif |
220 | | |
221 | | #if __has_builtin(__builtin_umulll_overflow) || __GNUC__ >= 5 |
222 | 0 | #define uint64_mul_overflow(a, b, r) __builtin_umulll_overflow(a, b, (unsigned long long*)(r)) |
223 | | #else |
224 | | static int uint64_mul_overflow(uint64_t a, uint64_t b, uint64_t *res) |
225 | | { |
226 | | *res = a * b; |
227 | | return (a > UINT64_MAX / b); |
228 | | } |
229 | | #endif |
230 | | |
231 | | #define NODE_IS_ROOT(x) (((node_t)(x))->isRoot) |
232 | | |
233 | | struct bplist_data { |
234 | | const char* data; |
235 | | uint64_t size; |
236 | | uint64_t num_objects; |
237 | | uint8_t ref_size; |
238 | | uint8_t offset_size; |
239 | | const char* offset_table; |
240 | | uint32_t level; |
241 | | ptrarray_t* used_indexes; |
242 | | }; |
243 | | |
244 | | #ifdef DEBUG |
245 | | static int plist_bin_debug = 0; |
246 | 0 | #define PLIST_BIN_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binparser] ERROR: " __VA_ARGS__); } |
247 | | #else |
248 | | #define PLIST_BIN_ERR(...) |
249 | | #endif |
250 | | |
251 | | void plist_bin_init(void) |
252 | 2 | { |
253 | | /* init binary plist stuff */ |
254 | 2 | #ifdef DEBUG |
255 | 2 | char *env_debug = getenv("PLIST_BIN_DEBUG"); |
256 | 2 | if (env_debug && !strcmp(env_debug, "1")) { |
257 | 0 | plist_bin_debug = 1; |
258 | 0 | } |
259 | 2 | #endif |
260 | 2 | } |
261 | | |
262 | | void plist_bin_deinit(void) |
263 | 0 | { |
264 | | /* deinit binary plist stuff */ |
265 | 0 | } |
266 | | |
267 | | void plist_bin_set_debug(int debug) |
268 | 0 | { |
269 | 0 | #if DEBUG |
270 | 0 | plist_bin_debug = debug; |
271 | 0 | #endif |
272 | 0 | } |
273 | | |
274 | | static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index); |
275 | | |
276 | | static plist_t parse_int_node(const char **bnode, uint8_t size) |
277 | 0 | { |
278 | 0 | plist_data_t data = plist_new_plist_data(); |
279 | |
|
280 | 0 | size = 1 << size; // make length less misleading |
281 | 0 | switch (size) |
282 | 0 | { |
283 | 0 | case sizeof(uint8_t): |
284 | 0 | case sizeof(uint16_t): |
285 | 0 | case sizeof(uint32_t): |
286 | 0 | case sizeof(uint64_t): |
287 | 0 | data->length = sizeof(uint64_t); |
288 | 0 | break; |
289 | 0 | case 16: |
290 | 0 | data->length = size; |
291 | 0 | break; |
292 | 0 | default: |
293 | 0 | free(data); |
294 | 0 | PLIST_BIN_ERR("%s: Invalid byte size for integer node\n", __func__); |
295 | 0 | return NULL; |
296 | 0 | }; |
297 | |
|
298 | 0 | data->intval = UINT_TO_HOST(*bnode, size); |
299 | |
|
300 | 0 | (*bnode) += size; |
301 | 0 | data->type = PLIST_INT; |
302 | |
|
303 | 0 | return node_create(NULL, data); |
304 | 0 | } |
305 | | |
306 | | static plist_t parse_real_node(const char **bnode, uint8_t size) |
307 | 0 | { |
308 | 0 | plist_data_t data = plist_new_plist_data(); |
309 | |
|
310 | 0 | size = 1 << size; // make length less misleading |
311 | 0 | switch (size) |
312 | 0 | { |
313 | 0 | case sizeof(uint32_t): |
314 | 0 | { |
315 | 0 | uint32_t ival; |
316 | 0 | memcpy(&ival, *bnode, sizeof(uint32_t)); |
317 | 0 | ival = float_bswap32(ival); |
318 | 0 | float fval; |
319 | 0 | memcpy(&fval, &ival, sizeof(float)); |
320 | 0 | data->realval = fval; |
321 | 0 | } |
322 | 0 | break; |
323 | | |
324 | 0 | case sizeof(uint64_t): |
325 | 0 | { |
326 | 0 | uint64_t ival; |
327 | 0 | memcpy(&ival, *bnode, sizeof(uint64_t)); |
328 | 0 | ival = float_bswap64(ival); |
329 | 0 | memcpy(&data->realval, &ival, sizeof(double)); |
330 | 0 | break; |
331 | 0 | } |
332 | | |
333 | 0 | default: |
334 | 0 | free(data); |
335 | 0 | PLIST_BIN_ERR("%s: Invalid byte size for real node\n", __func__); |
336 | 0 | return NULL; |
337 | 0 | } |
338 | 0 | data->type = PLIST_REAL; |
339 | 0 | data->length = sizeof(double); |
340 | |
|
341 | 0 | return node_create(NULL, data); |
342 | 0 | } |
343 | | |
344 | | static plist_t parse_date_node(const char **bnode, uint8_t size) |
345 | 0 | { |
346 | 0 | plist_t node = parse_real_node(bnode, size); |
347 | 0 | plist_data_t data = plist_get_data(node); |
348 | |
|
349 | 0 | data->type = PLIST_DATE; |
350 | |
|
351 | 0 | return node; |
352 | 0 | } |
353 | | |
354 | | static plist_t parse_string_node(const char **bnode, uint64_t size) |
355 | 0 | { |
356 | 0 | plist_data_t data = plist_new_plist_data(); |
357 | |
|
358 | 0 | data->type = PLIST_STRING; |
359 | 0 | data->strval = (char *) malloc(sizeof(char) * (size + 1)); |
360 | 0 | if (!data->strval) { |
361 | 0 | plist_free_data(data); |
362 | 0 | PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(char) * (size + 1)); |
363 | 0 | return NULL; |
364 | 0 | } |
365 | 0 | memcpy(data->strval, *bnode, size); |
366 | 0 | data->strval[size] = '\0'; |
367 | 0 | data->length = strlen(data->strval); |
368 | |
|
369 | 0 | return node_create(NULL, data); |
370 | 0 | } |
371 | | |
372 | | static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read, long *items_written) |
373 | 0 | { |
374 | 0 | if (!unistr || (len <= 0)) return NULL; |
375 | 0 | char* outbuf; |
376 | 0 | char* outbuf_new; |
377 | 0 | int p = 0; |
378 | 0 | long i = 0; |
379 | |
|
380 | 0 | uint16_t wc; |
381 | 0 | uint32_t w; |
382 | 0 | int read_lead_surrogate = 0; |
383 | | |
384 | | /* allocate with enough space */ |
385 | 0 | outbuf = (char*)malloc(4*(len+1)); |
386 | 0 | if (!outbuf) { |
387 | 0 | PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)(4*(len+1))); |
388 | 0 | return NULL; |
389 | 0 | } |
390 | | |
391 | 0 | while (i < len) { |
392 | 0 | wc = UINT_TO_HOST(unistr + i, sizeof(wc)); |
393 | 0 | i++; |
394 | 0 | if (wc >= 0xD800 && wc <= 0xDBFF) { |
395 | 0 | if (!read_lead_surrogate) { |
396 | 0 | read_lead_surrogate = 1; |
397 | 0 | w = 0x010000 + ((wc & 0x3FF) << 10); |
398 | 0 | } else { |
399 | | // This is invalid, the next 16 bit char should be a trail surrogate. |
400 | | // Handling error by skipping. |
401 | 0 | read_lead_surrogate = 0; |
402 | 0 | } |
403 | 0 | } else if (wc >= 0xDC00 && wc <= 0xDFFF) { |
404 | 0 | if (read_lead_surrogate) { |
405 | 0 | read_lead_surrogate = 0; |
406 | 0 | w = w | (wc & 0x3FF); |
407 | 0 | outbuf[p++] = (char)(0xF0 + ((w >> 18) & 0x7)); |
408 | 0 | outbuf[p++] = (char)(0x80 + ((w >> 12) & 0x3F)); |
409 | 0 | outbuf[p++] = (char)(0x80 + ((w >> 6) & 0x3F)); |
410 | 0 | outbuf[p++] = (char)(0x80 + (w & 0x3F)); |
411 | 0 | } else { |
412 | | // This is invalid. A trail surrogate should always follow a lead surrogate. |
413 | | // Handling error by skipping |
414 | 0 | } |
415 | 0 | } else if (wc >= 0x800) { |
416 | 0 | outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF)); |
417 | 0 | outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F)); |
418 | 0 | outbuf[p++] = (char)(0x80 + (wc & 0x3F)); |
419 | 0 | } else if (wc >= 0x80) { |
420 | 0 | outbuf[p++] = (char)(0xC0 + ((wc >> 6) & 0x1F)); |
421 | 0 | outbuf[p++] = (char)(0x80 + (wc & 0x3F)); |
422 | 0 | } else { |
423 | 0 | outbuf[p++] = (char)(wc & 0x7F); |
424 | 0 | } |
425 | 0 | } |
426 | 0 | if (items_read) { |
427 | 0 | *items_read = i; |
428 | 0 | } |
429 | 0 | if (items_written) { |
430 | 0 | *items_written = p; |
431 | 0 | } |
432 | 0 | outbuf[p] = 0; |
433 | | |
434 | | /* reduce the size to the actual size */ |
435 | 0 | outbuf_new = (char*)realloc(outbuf, p+1); |
436 | 0 | if (outbuf_new) { |
437 | 0 | outbuf = outbuf_new; |
438 | 0 | } |
439 | |
|
440 | 0 | return outbuf; |
441 | 0 | } |
442 | | |
443 | | static plist_t parse_unicode_node(const char **bnode, uint64_t size) |
444 | 0 | { |
445 | 0 | plist_data_t data = plist_new_plist_data(); |
446 | 0 | long items_read = 0; |
447 | 0 | long items_written = 0; |
448 | |
|
449 | 0 | data->type = PLIST_STRING; |
450 | 0 | data->strval = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written); |
451 | 0 | if (!data->strval) { |
452 | 0 | plist_free_data(data); |
453 | 0 | return NULL; |
454 | 0 | } |
455 | 0 | data->length = items_written; |
456 | |
|
457 | 0 | return node_create(NULL, data); |
458 | 0 | } |
459 | | |
460 | | static plist_t parse_data_node(const char **bnode, uint64_t size) |
461 | 0 | { |
462 | 0 | plist_data_t data = plist_new_plist_data(); |
463 | |
|
464 | 0 | data->type = PLIST_DATA; |
465 | 0 | data->length = size; |
466 | 0 | data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size); |
467 | 0 | if (!data->strval) { |
468 | 0 | plist_free_data(data); |
469 | 0 | PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(uint8_t) * size); |
470 | 0 | return NULL; |
471 | 0 | } |
472 | 0 | memcpy(data->buff, *bnode, sizeof(uint8_t) * size); |
473 | |
|
474 | 0 | return node_create(NULL, data); |
475 | 0 | } |
476 | | |
477 | | static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, uint64_t size) |
478 | 0 | { |
479 | 0 | uint64_t j; |
480 | 0 | uint64_t str_i = 0, str_j = 0; |
481 | 0 | uint64_t index1, index2; |
482 | 0 | plist_data_t data = plist_new_plist_data(); |
483 | 0 | const char *index1_ptr = NULL; |
484 | 0 | const char *index2_ptr = NULL; |
485 | |
|
486 | 0 | data->type = PLIST_DICT; |
487 | 0 | data->length = size; |
488 | |
|
489 | 0 | plist_t node = node_create(NULL, data); |
490 | |
|
491 | 0 | for (j = 0; j < data->length; j++) { |
492 | 0 | str_i = j * bplist->ref_size; |
493 | 0 | str_j = (j + size) * bplist->ref_size; |
494 | 0 | index1_ptr = (*bnode) + str_i; |
495 | 0 | index2_ptr = (*bnode) + str_j; |
496 | |
|
497 | 0 | if ((index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) || |
498 | 0 | (index2_ptr < bplist->data || index2_ptr + bplist->ref_size > bplist->offset_table)) { |
499 | 0 | plist_free(node); |
500 | 0 | PLIST_BIN_ERR("%s: dict entry %" PRIu64 " is outside of valid range\n", __func__, j); |
501 | 0 | return NULL; |
502 | 0 | } |
503 | | |
504 | 0 | index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size); |
505 | 0 | index2 = UINT_TO_HOST(index2_ptr, bplist->ref_size); |
506 | |
|
507 | 0 | if (index1 >= bplist->num_objects) { |
508 | 0 | plist_free(node); |
509 | 0 | PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects); |
510 | 0 | return NULL; |
511 | 0 | } |
512 | 0 | if (index2 >= bplist->num_objects) { |
513 | 0 | plist_free(node); |
514 | 0 | PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": value index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects); |
515 | 0 | return NULL; |
516 | 0 | } |
517 | | |
518 | | /* process key node */ |
519 | 0 | plist_t key = parse_bin_node_at_index(bplist, index1); |
520 | 0 | if (!key) { |
521 | 0 | plist_free(node); |
522 | 0 | return NULL; |
523 | 0 | } |
524 | | |
525 | 0 | if (plist_get_data(key)->type != PLIST_STRING) { |
526 | 0 | PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": invalid node type for key\n", __func__, j); |
527 | 0 | plist_free(key); |
528 | 0 | plist_free(node); |
529 | 0 | return NULL; |
530 | 0 | } |
531 | | |
532 | | /* enforce key type */ |
533 | 0 | plist_get_data(key)->type = PLIST_KEY; |
534 | 0 | if (!plist_get_data(key)->strval) { |
535 | 0 | PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key must not be NULL\n", __func__, j); |
536 | 0 | plist_free(key); |
537 | 0 | plist_free(node); |
538 | 0 | return NULL; |
539 | 0 | } |
540 | | |
541 | | /* process value node */ |
542 | 0 | plist_t val = parse_bin_node_at_index(bplist, index2); |
543 | 0 | if (!val) { |
544 | 0 | plist_free(key); |
545 | 0 | plist_free(node); |
546 | 0 | return NULL; |
547 | 0 | } |
548 | | |
549 | 0 | node_attach((node_t)node, (node_t)key); |
550 | 0 | node_attach((node_t)node, (node_t)val); |
551 | 0 | } |
552 | | |
553 | 0 | return node; |
554 | 0 | } |
555 | | |
556 | | static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode, uint64_t size) |
557 | 0 | { |
558 | 0 | uint64_t j; |
559 | 0 | uint64_t str_j = 0; |
560 | 0 | uint64_t index1; |
561 | 0 | plist_data_t data = plist_new_plist_data(); |
562 | 0 | const char *index1_ptr = NULL; |
563 | |
|
564 | 0 | data->type = PLIST_ARRAY; |
565 | 0 | data->length = size; |
566 | |
|
567 | 0 | plist_t node = node_create(NULL, data); |
568 | |
|
569 | 0 | for (j = 0; j < data->length; j++) { |
570 | 0 | str_j = j * bplist->ref_size; |
571 | 0 | index1_ptr = (*bnode) + str_j; |
572 | |
|
573 | 0 | if (index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) { |
574 | 0 | plist_free(node); |
575 | 0 | PLIST_BIN_ERR("%s: array item %" PRIu64 " is outside of valid range\n", __func__, j); |
576 | 0 | return NULL; |
577 | 0 | } |
578 | | |
579 | 0 | index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size); |
580 | |
|
581 | 0 | if (index1 >= bplist->num_objects) { |
582 | 0 | plist_free(node); |
583 | 0 | PLIST_BIN_ERR("%s: array item %" PRIu64 " object index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects); |
584 | 0 | return NULL; |
585 | 0 | } |
586 | | |
587 | | /* process value node */ |
588 | 0 | plist_t val = parse_bin_node_at_index(bplist, index1); |
589 | 0 | if (!val) { |
590 | 0 | plist_free(node); |
591 | 0 | return NULL; |
592 | 0 | } |
593 | | |
594 | 0 | node_attach((node_t)node, (node_t)val); |
595 | 0 | } |
596 | | |
597 | 0 | return node; |
598 | 0 | } |
599 | | |
600 | | static plist_t parse_uid_node(const char **bnode, uint8_t size) |
601 | 0 | { |
602 | 0 | plist_data_t data = plist_new_plist_data(); |
603 | 0 | size = size + 1; |
604 | 0 | data->intval = UINT_TO_HOST(*bnode, size); |
605 | 0 | if (data->intval > UINT32_MAX) { |
606 | 0 | PLIST_BIN_ERR("%s: value %" PRIu64 " too large for UID node (must be <= %u)\n", __func__, (uint64_t)data->intval, UINT32_MAX); |
607 | 0 | free(data); |
608 | 0 | return NULL; |
609 | 0 | } |
610 | | |
611 | 0 | (*bnode) += size; |
612 | 0 | data->type = PLIST_UID; |
613 | 0 | data->length = sizeof(uint64_t); |
614 | |
|
615 | 0 | return node_create(NULL, data); |
616 | 0 | } |
617 | | |
618 | | static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) |
619 | 0 | { |
620 | 0 | uint16_t type = 0; |
621 | 0 | uint64_t size = 0; |
622 | 0 | uint64_t pobject = 0; |
623 | 0 | uint64_t poffset_table = (uint64_t)(uintptr_t)bplist->offset_table; |
624 | |
|
625 | 0 | if (!object) |
626 | 0 | return NULL; |
627 | | |
628 | 0 | type = (**object) & BPLIST_MASK; |
629 | 0 | size = (**object) & BPLIST_FILL; |
630 | 0 | (*object)++; |
631 | |
|
632 | 0 | if (size == BPLIST_FILL) { |
633 | 0 | switch (type) { |
634 | 0 | case BPLIST_DATA: |
635 | 0 | case BPLIST_STRING: |
636 | 0 | case BPLIST_UNICODE: |
637 | 0 | case BPLIST_ARRAY: |
638 | 0 | case BPLIST_SET: |
639 | 0 | case BPLIST_DICT: |
640 | 0 | { |
641 | 0 | uint16_t next_size = **object & BPLIST_FILL; |
642 | 0 | if ((**object & BPLIST_MASK) != BPLIST_INT) { |
643 | 0 | PLIST_BIN_ERR("%s: invalid size node type for node type 0x%02x: found 0x%02x, expected 0x%02x\n", __func__, type, **object & BPLIST_MASK, BPLIST_INT); |
644 | 0 | return NULL; |
645 | 0 | } |
646 | 0 | (*object)++; |
647 | 0 | next_size = 1 << next_size; |
648 | 0 | if (*object + next_size > bplist->offset_table) { |
649 | 0 | PLIST_BIN_ERR("%s: size node data bytes for node type 0x%02x point outside of valid range\n", __func__, type); |
650 | 0 | return NULL; |
651 | 0 | } |
652 | 0 | size = UINT_TO_HOST(*object, next_size); |
653 | 0 | (*object) += next_size; |
654 | 0 | break; |
655 | 0 | } |
656 | 0 | default: |
657 | 0 | break; |
658 | 0 | } |
659 | 0 | } |
660 | | |
661 | 0 | pobject = (uint64_t)(uintptr_t)*object; |
662 | |
|
663 | 0 | switch (type) |
664 | 0 | { |
665 | | |
666 | 0 | case BPLIST_NULL: |
667 | 0 | switch (size) |
668 | 0 | { |
669 | | |
670 | 0 | case BPLIST_TRUE: |
671 | 0 | { |
672 | 0 | plist_data_t data = plist_new_plist_data(); |
673 | 0 | data->type = PLIST_BOOLEAN; |
674 | 0 | data->boolval = TRUE; |
675 | 0 | data->length = 1; |
676 | 0 | return node_create(NULL, data); |
677 | 0 | } |
678 | | |
679 | 0 | case BPLIST_FALSE: |
680 | 0 | { |
681 | 0 | plist_data_t data = plist_new_plist_data(); |
682 | 0 | data->type = PLIST_BOOLEAN; |
683 | 0 | data->boolval = FALSE; |
684 | 0 | data->length = 1; |
685 | 0 | return node_create(NULL, data); |
686 | 0 | } |
687 | | |
688 | 0 | case BPLIST_NULL: |
689 | 0 | { |
690 | 0 | plist_data_t data = plist_new_plist_data(); |
691 | 0 | data->type = PLIST_NULL; |
692 | 0 | data->length = 0; |
693 | 0 | return node_create(NULL, data); |
694 | 0 | } |
695 | | |
696 | 0 | default: |
697 | 0 | return NULL; |
698 | 0 | } |
699 | | |
700 | 0 | case BPLIST_INT: |
701 | 0 | if (pobject + (uint64_t)(1 << size) > poffset_table) { |
702 | 0 | PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__); |
703 | 0 | return NULL; |
704 | 0 | } |
705 | 0 | return parse_int_node(object, size); |
706 | | |
707 | 0 | case BPLIST_REAL: |
708 | 0 | if (pobject + (uint64_t)(1 << size) > poffset_table) { |
709 | 0 | PLIST_BIN_ERR("%s: BPLIST_REAL data bytes point outside of valid range\n", __func__); |
710 | 0 | return NULL; |
711 | 0 | } |
712 | 0 | return parse_real_node(object, size); |
713 | | |
714 | 0 | case BPLIST_DATE: |
715 | 0 | if (3 != size) { |
716 | 0 | PLIST_BIN_ERR("%s: invalid data size for BPLIST_DATE node\n", __func__); |
717 | 0 | return NULL; |
718 | 0 | } |
719 | 0 | if (pobject + (uint64_t)(1 << size) > poffset_table) { |
720 | 0 | PLIST_BIN_ERR("%s: BPLIST_DATE data bytes point outside of valid range\n", __func__); |
721 | 0 | return NULL; |
722 | 0 | } |
723 | 0 | return parse_date_node(object, size); |
724 | | |
725 | 0 | case BPLIST_DATA: |
726 | 0 | if (pobject + size < pobject || pobject + size > poffset_table) { |
727 | 0 | PLIST_BIN_ERR("%s: BPLIST_DATA data bytes point outside of valid range\n", __func__); |
728 | 0 | return NULL; |
729 | 0 | } |
730 | 0 | return parse_data_node(object, size); |
731 | | |
732 | 0 | case BPLIST_STRING: |
733 | 0 | if (pobject + size < pobject || pobject + size > poffset_table) { |
734 | 0 | PLIST_BIN_ERR("%s: BPLIST_STRING data bytes point outside of valid range\n", __func__); |
735 | 0 | return NULL; |
736 | 0 | } |
737 | 0 | return parse_string_node(object, size); |
738 | | |
739 | 0 | case BPLIST_UNICODE: |
740 | 0 | if (size*2 < size) { |
741 | 0 | PLIST_BIN_ERR("%s: Integer overflow when calculating BPLIST_UNICODE data size.\n", __func__); |
742 | 0 | return NULL; |
743 | 0 | } |
744 | 0 | if (pobject + size*2 < pobject || pobject + size*2 > poffset_table) { |
745 | 0 | PLIST_BIN_ERR("%s: BPLIST_UNICODE data bytes point outside of valid range\n", __func__); |
746 | 0 | return NULL; |
747 | 0 | } |
748 | 0 | return parse_unicode_node(object, size); |
749 | | |
750 | 0 | case BPLIST_SET: |
751 | 0 | case BPLIST_ARRAY: |
752 | 0 | if (pobject + size < pobject || pobject + size > poffset_table) { |
753 | 0 | PLIST_BIN_ERR("%s: BPLIST_ARRAY data bytes point outside of valid range\n", __func__); |
754 | 0 | return NULL; |
755 | 0 | } |
756 | 0 | return parse_array_node(bplist, object, size); |
757 | | |
758 | 0 | case BPLIST_UID: |
759 | 0 | if (pobject + size+1 > poffset_table) { |
760 | 0 | PLIST_BIN_ERR("%s: BPLIST_UID data bytes point outside of valid range\n", __func__); |
761 | 0 | return NULL; |
762 | 0 | } |
763 | 0 | return parse_uid_node(object, size); |
764 | | |
765 | 0 | case BPLIST_DICT: |
766 | 0 | if (pobject + size < pobject || pobject + size > poffset_table) { |
767 | 0 | PLIST_BIN_ERR("%s: BPLIST_DICT data bytes point outside of valid range\n", __func__); |
768 | 0 | return NULL; |
769 | 0 | } |
770 | 0 | return parse_dict_node(bplist, object, size); |
771 | | |
772 | 0 | default: |
773 | 0 | PLIST_BIN_ERR("%s: unexpected node type 0x%02x\n", __func__, type); |
774 | 0 | return NULL; |
775 | 0 | } |
776 | 0 | return NULL; |
777 | 0 | } |
778 | | |
779 | | static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index) |
780 | 0 | { |
781 | 0 | int i = 0; |
782 | 0 | const char* ptr = NULL; |
783 | 0 | plist_t plist = NULL; |
784 | 0 | const char* idx_ptr = NULL; |
785 | |
|
786 | 0 | if (node_index >= bplist->num_objects) { |
787 | 0 | PLIST_BIN_ERR("node index (%u) must be smaller than the number of objects (%" PRIu64 ")\n", node_index, bplist->num_objects); |
788 | 0 | return NULL; |
789 | 0 | } |
790 | | |
791 | 0 | idx_ptr = bplist->offset_table + node_index * bplist->offset_size; |
792 | 0 | if (idx_ptr < bplist->offset_table || |
793 | 0 | idx_ptr >= bplist->offset_table + bplist->num_objects * bplist->offset_size) { |
794 | 0 | PLIST_BIN_ERR("node index %u points outside of valid range\n", node_index); |
795 | 0 | return NULL; |
796 | 0 | } |
797 | | |
798 | 0 | ptr = bplist->data + UINT_TO_HOST(idx_ptr, bplist->offset_size); |
799 | | /* make sure the node offset is in a sane range */ |
800 | 0 | if ((ptr < bplist->data+BPLIST_MAGIC_SIZE+BPLIST_VERSION_SIZE) || (ptr >= bplist->offset_table)) { |
801 | 0 | PLIST_BIN_ERR("offset for node index %u points outside of valid range\n", node_index); |
802 | 0 | return NULL; |
803 | 0 | } |
804 | | |
805 | | /* store node_index for current recursion level */ |
806 | 0 | if ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) { |
807 | 0 | while ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) { |
808 | 0 | ptr_array_add(bplist->used_indexes, (void*)(uintptr_t)node_index); |
809 | 0 | } |
810 | 0 | } else { |
811 | 0 | ptr_array_set(bplist->used_indexes, (void*)(uintptr_t)node_index, bplist->level); |
812 | 0 | } |
813 | | |
814 | | /* recursion check */ |
815 | 0 | if (bplist->level > 0) { |
816 | 0 | for (i = bplist->level-1; i >= 0; i--) { |
817 | 0 | void *node_i = ptr_array_index(bplist->used_indexes, i); |
818 | 0 | void *node_level = ptr_array_index(bplist->used_indexes, bplist->level); |
819 | 0 | if (node_i == node_level) { |
820 | 0 | PLIST_BIN_ERR("recursion detected in binary plist\n"); |
821 | 0 | return NULL; |
822 | 0 | } |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | | /* finally parse node */ |
827 | 0 | bplist->level++; |
828 | 0 | plist = parse_bin_node(bplist, &ptr); |
829 | 0 | bplist->level--; |
830 | 0 | return plist; |
831 | 0 | } |
832 | | |
833 | | plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist) |
834 | 0 | { |
835 | 0 | bplist_trailer_t *trailer = NULL; |
836 | 0 | uint8_t offset_size = 0; |
837 | 0 | uint8_t ref_size = 0; |
838 | 0 | uint64_t num_objects = 0; |
839 | 0 | uint64_t root_object = 0; |
840 | 0 | const char *offset_table = NULL; |
841 | 0 | uint64_t offset_table_size = 0; |
842 | 0 | const char *start_data = NULL; |
843 | 0 | const char *end_data = NULL; |
844 | |
|
845 | 0 | if (!plist) { |
846 | 0 | return PLIST_ERR_INVALID_ARG; |
847 | 0 | } |
848 | 0 | *plist = NULL; |
849 | 0 | if (!plist_bin || length == 0) { |
850 | 0 | return PLIST_ERR_INVALID_ARG; |
851 | 0 | } |
852 | | |
853 | | //first check we have enough data |
854 | 0 | if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + sizeof(bplist_trailer_t))) { |
855 | 0 | PLIST_BIN_ERR("plist data is to small to hold a binary plist\n"); |
856 | 0 | return PLIST_ERR_PARSE; |
857 | 0 | } |
858 | | //check that plist_bin in actually a plist |
859 | 0 | if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0) { |
860 | 0 | PLIST_BIN_ERR("bplist magic mismatch\n"); |
861 | 0 | return PLIST_ERR_PARSE; |
862 | 0 | } |
863 | | //check for known version |
864 | 0 | if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0) { |
865 | 0 | PLIST_BIN_ERR("unsupported binary plist version '%.2s\n", plist_bin+BPLIST_MAGIC_SIZE); |
866 | 0 | return PLIST_ERR_PARSE; |
867 | 0 | } |
868 | | |
869 | 0 | start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE; |
870 | 0 | end_data = plist_bin + length - sizeof(bplist_trailer_t); |
871 | | |
872 | | //now parse trailer |
873 | 0 | trailer = (bplist_trailer_t*)end_data; |
874 | |
|
875 | 0 | offset_size = trailer->offset_size; |
876 | 0 | ref_size = trailer->ref_size; |
877 | 0 | num_objects = be64toh(trailer->num_objects); |
878 | 0 | root_object = be64toh(trailer->root_object_index); |
879 | 0 | offset_table = (char *)(plist_bin + be64toh(trailer->offset_table_offset)); |
880 | |
|
881 | 0 | if (num_objects == 0) { |
882 | 0 | PLIST_BIN_ERR("number of objects must be larger than 0\n"); |
883 | 0 | return PLIST_ERR_PARSE; |
884 | 0 | } |
885 | | |
886 | 0 | if (offset_size == 0) { |
887 | 0 | PLIST_BIN_ERR("offset size in trailer must be larger than 0\n"); |
888 | 0 | return PLIST_ERR_PARSE; |
889 | 0 | } |
890 | | |
891 | 0 | if (ref_size == 0) { |
892 | 0 | PLIST_BIN_ERR("object reference size in trailer must be larger than 0\n"); |
893 | 0 | return PLIST_ERR_PARSE; |
894 | 0 | } |
895 | | |
896 | 0 | if (root_object >= num_objects) { |
897 | 0 | PLIST_BIN_ERR("root object index (%" PRIu64 ") must be smaller than number of objects (%" PRIu64 ")\n", root_object, num_objects); |
898 | 0 | return PLIST_ERR_PARSE; |
899 | 0 | } |
900 | | |
901 | 0 | if (offset_table < start_data || offset_table >= end_data) { |
902 | 0 | PLIST_BIN_ERR("offset table offset points outside of valid range\n"); |
903 | 0 | return PLIST_ERR_PARSE; |
904 | 0 | } |
905 | | |
906 | 0 | if (uint64_mul_overflow(num_objects, offset_size, &offset_table_size)) { |
907 | 0 | PLIST_BIN_ERR("integer overflow when calculating offset table size\n"); |
908 | 0 | return PLIST_ERR_PARSE; |
909 | 0 | } |
910 | | |
911 | 0 | if (offset_table_size > (uint64_t)(end_data - offset_table)) { |
912 | 0 | PLIST_BIN_ERR("offset table points outside of valid range\n"); |
913 | 0 | return PLIST_ERR_PARSE; |
914 | 0 | } |
915 | | |
916 | 0 | struct bplist_data bplist; |
917 | 0 | bplist.data = plist_bin; |
918 | 0 | bplist.size = length; |
919 | 0 | bplist.num_objects = num_objects; |
920 | 0 | bplist.ref_size = ref_size; |
921 | 0 | bplist.offset_size = offset_size; |
922 | 0 | bplist.offset_table = offset_table; |
923 | 0 | bplist.level = 0; |
924 | 0 | bplist.used_indexes = ptr_array_new(16); |
925 | |
|
926 | 0 | if (!bplist.used_indexes) { |
927 | 0 | PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n"); |
928 | 0 | return PLIST_ERR_NO_MEM; |
929 | 0 | } |
930 | | |
931 | 0 | *plist = parse_bin_node_at_index(&bplist, root_object); |
932 | |
|
933 | 0 | ptr_array_free(bplist.used_indexes); |
934 | |
|
935 | 0 | if (!*plist) { |
936 | 0 | return PLIST_ERR_PARSE; |
937 | 0 | } |
938 | | |
939 | 0 | return PLIST_ERR_SUCCESS; |
940 | 0 | } |
941 | | |
942 | | static unsigned int plist_data_hash(const void* key) |
943 | 0 | { |
944 | 0 | plist_data_t data = plist_get_data((plist_t) key); |
945 | |
|
946 | 0 | unsigned int hash = data->type; |
947 | 0 | unsigned int i = 0; |
948 | |
|
949 | 0 | char *buff = NULL; |
950 | 0 | unsigned int size = 0; |
951 | |
|
952 | 0 | switch (data->type) |
953 | 0 | { |
954 | 0 | case PLIST_BOOLEAN: |
955 | 0 | case PLIST_NULL: |
956 | 0 | case PLIST_INT: |
957 | 0 | case PLIST_REAL: |
958 | 0 | case PLIST_DATE: |
959 | 0 | case PLIST_UID: |
960 | 0 | buff = (char *) &data->intval; //works also for real as we use an union |
961 | 0 | size = 8; |
962 | 0 | break; |
963 | 0 | case PLIST_KEY: |
964 | 0 | case PLIST_STRING: |
965 | 0 | buff = data->strval; |
966 | 0 | size = data->length; |
967 | 0 | break; |
968 | 0 | case PLIST_DATA: |
969 | 0 | case PLIST_ARRAY: |
970 | 0 | case PLIST_DICT: |
971 | | //for these types only hash pointer |
972 | 0 | buff = (char *) &key; |
973 | 0 | size = sizeof(const void*); |
974 | 0 | break; |
975 | 0 | default: |
976 | 0 | break; |
977 | 0 | } |
978 | | |
979 | | // now perform hash using djb2 hashing algorithm |
980 | | // see: http://www.cse.yorku.ca/~oz/hash.html |
981 | 0 | hash += 5381; |
982 | 0 | for (i = 0; i < size; buff++, i++) { |
983 | 0 | hash = ((hash << 5) + hash) + *buff; |
984 | 0 | } |
985 | |
|
986 | 0 | return hash; |
987 | 0 | } |
988 | | |
989 | | struct serialize_s |
990 | | { |
991 | | ptrarray_t* objects; |
992 | | hashtable_t* ref_table; |
993 | | }; |
994 | | |
995 | | static void serialize_plist(node_t node, void* data) |
996 | 0 | { |
997 | 0 | uint64_t *index_val = NULL; |
998 | 0 | struct serialize_s *ser = (struct serialize_s *) data; |
999 | 0 | uint64_t current_index = ser->objects->len; |
1000 | | |
1001 | | //first check that node is not yet in objects |
1002 | 0 | void* val = hash_table_lookup(ser->ref_table, node); |
1003 | 0 | if (val) |
1004 | 0 | { |
1005 | | //data is already in table |
1006 | 0 | return; |
1007 | 0 | } |
1008 | | //insert new ref |
1009 | 0 | index_val = (uint64_t *) malloc(sizeof(uint64_t)); |
1010 | 0 | assert(index_val != NULL); |
1011 | 0 | *index_val = current_index; |
1012 | 0 | hash_table_insert(ser->ref_table, node, index_val); |
1013 | | |
1014 | | //now append current node to object array |
1015 | 0 | ptr_array_add(ser->objects, node); |
1016 | | |
1017 | | //now recurse on children |
1018 | 0 | node_t ch; |
1019 | 0 | for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { |
1020 | 0 | serialize_plist(ch, data); |
1021 | 0 | } |
1022 | 0 | } |
1023 | | |
1024 | 0 | #define Log2(x) ((x) == 8 ? 3 : ((x) == 4 ? 2 : ((x) == 2 ? 1 : 0))) |
1025 | | |
1026 | | static void write_int(bytearray_t * bplist, uint64_t val) |
1027 | 0 | { |
1028 | 0 | int size = get_needed_bytes(val); |
1029 | 0 | uint8_t sz; |
1030 | | //do not write 3bytes int node |
1031 | 0 | if (size == 3) |
1032 | 0 | size++; |
1033 | 0 | sz = BPLIST_INT | Log2(size); |
1034 | |
|
1035 | 0 | val = be64toh(val); |
1036 | 0 | byte_array_append(bplist, &sz, 1); |
1037 | 0 | byte_array_append(bplist, (uint8_t*)&val + (8-size), size); |
1038 | 0 | } |
1039 | | |
1040 | | static void write_uint(bytearray_t * bplist, uint64_t val) |
1041 | 0 | { |
1042 | 0 | uint8_t sz = BPLIST_INT | 4; |
1043 | 0 | uint64_t zero = 0; |
1044 | |
|
1045 | 0 | val = be64toh(val); |
1046 | 0 | byte_array_append(bplist, &sz, 1); |
1047 | 0 | byte_array_append(bplist, &zero, sizeof(uint64_t)); |
1048 | 0 | byte_array_append(bplist, &val, sizeof(uint64_t)); |
1049 | 0 | } |
1050 | | |
1051 | | static void write_real(bytearray_t * bplist, double val) |
1052 | 0 | { |
1053 | 0 | int size = get_real_bytes(val); //cheat to know used space |
1054 | 0 | uint8_t buff[16]; |
1055 | 0 | buff[7] = BPLIST_REAL | Log2(size); |
1056 | 0 | if (size == sizeof(float)) { |
1057 | 0 | float floatval = (float)val; |
1058 | 0 | uint32_t intval; |
1059 | 0 | memcpy(&intval, &floatval, sizeof(float)); |
1060 | 0 | *(uint32_t*)(buff+8) = float_bswap32(intval); |
1061 | 0 | } else { |
1062 | 0 | uint64_t intval; |
1063 | 0 | memcpy(&intval, &val, sizeof(double)); |
1064 | 0 | *(uint64_t*)(buff+8) = float_bswap64(intval); |
1065 | 0 | } |
1066 | 0 | byte_array_append(bplist, buff+7, size+1); |
1067 | 0 | } |
1068 | | |
1069 | | static void write_date(bytearray_t * bplist, double val) |
1070 | 0 | { |
1071 | 0 | uint64_t intval; |
1072 | 0 | memcpy(&intval, &val, sizeof(double)); |
1073 | 0 | uint8_t buff[16]; |
1074 | 0 | buff[7] = BPLIST_DATE | 3; |
1075 | 0 | *(uint64_t*)(buff+8) = float_bswap64(intval); |
1076 | 0 | byte_array_append(bplist, buff+7, 9); |
1077 | 0 | } |
1078 | | |
1079 | | static void write_raw_data(bytearray_t * bplist, uint8_t mark, uint8_t * val, uint64_t size) |
1080 | 0 | { |
1081 | 0 | uint8_t marker = mark | (size < 15 ? size : 0xf); |
1082 | 0 | byte_array_append(bplist, &marker, sizeof(uint8_t)); |
1083 | 0 | if (size >= 15) { |
1084 | 0 | write_int(bplist, size); |
1085 | 0 | } |
1086 | 0 | if (BPLIST_UNICODE==mark) size <<= 1; |
1087 | 0 | byte_array_append(bplist, val, size); |
1088 | 0 | } |
1089 | | |
1090 | | static void write_data(bytearray_t * bplist, uint8_t * val, uint64_t size) |
1091 | 0 | { |
1092 | 0 | write_raw_data(bplist, BPLIST_DATA, val, size); |
1093 | 0 | } |
1094 | | |
1095 | | static void write_string(bytearray_t * bplist, char *val, uint64_t size) |
1096 | 0 | { |
1097 | 0 | write_raw_data(bplist, BPLIST_STRING, (uint8_t *) val, size); |
1098 | 0 | } |
1099 | | |
1100 | | static uint16_t *plist_utf8_to_utf16be(char *unistr, long size, long *items_read, long *items_written) |
1101 | 0 | { |
1102 | 0 | uint16_t *outbuf; |
1103 | 0 | int p = 0; |
1104 | 0 | long i = 0; |
1105 | |
|
1106 | 0 | unsigned char c0; |
1107 | 0 | unsigned char c1; |
1108 | 0 | unsigned char c2; |
1109 | 0 | unsigned char c3; |
1110 | |
|
1111 | 0 | uint32_t w; |
1112 | |
|
1113 | 0 | outbuf = (uint16_t*)malloc(((size*2)+1)*sizeof(uint16_t)); |
1114 | 0 | if (!outbuf) { |
1115 | 0 | PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)((size*2)+1)*sizeof(uint16_t)); |
1116 | 0 | return NULL; |
1117 | 0 | } |
1118 | | |
1119 | 0 | while (i < size) { |
1120 | 0 | c0 = unistr[i]; |
1121 | 0 | c1 = (i < size-1) ? unistr[i+1] : 0; |
1122 | 0 | c2 = (i < size-2) ? unistr[i+2] : 0; |
1123 | 0 | c3 = (i < size-3) ? unistr[i+3] : 0; |
1124 | 0 | if ((c0 >= 0xF0) && (i < size-3) && (c1 >= 0x80) && (c2 >= 0x80) && (c3 >= 0x80)) { |
1125 | | // 4 byte sequence. Need to generate UTF-16 surrogate pair |
1126 | 0 | w = ((((c0 & 7) << 18) + ((c1 & 0x3F) << 12) + ((c2 & 0x3F) << 6) + (c3 & 0x3F)) & 0x1FFFFF) - 0x010000; |
1127 | 0 | outbuf[p++] = be16toh(0xD800 + (w >> 10)); |
1128 | 0 | outbuf[p++] = be16toh(0xDC00 + (w & 0x3FF)); |
1129 | 0 | i+=4; |
1130 | 0 | } else if ((c0 >= 0xE0) && (i < size-2) && (c1 >= 0x80) && (c2 >= 0x80)) { |
1131 | | // 3 byte sequence |
1132 | 0 | outbuf[p++] = be16toh(((c2 & 0x3F) + ((c1 & 3) << 6)) + (((c1 >> 2) & 15) << 8) + ((c0 & 15) << 12)); |
1133 | 0 | i+=3; |
1134 | 0 | } else if ((c0 >= 0xC0) && (i < size-1) && (c1 >= 0x80)) { |
1135 | | // 2 byte sequence |
1136 | 0 | outbuf[p++] = be16toh(((c1 & 0x3F) + ((c0 & 3) << 6)) + (((c0 >> 2) & 7) << 8)); |
1137 | 0 | i+=2; |
1138 | 0 | } else if (c0 < 0x80) { |
1139 | | // 1 byte sequence |
1140 | 0 | outbuf[p++] = be16toh(c0); |
1141 | 0 | i+=1; |
1142 | 0 | } else { |
1143 | | // invalid character |
1144 | 0 | PLIST_BIN_ERR("%s: invalid utf8 sequence in string at index %lu\n", __func__, i); |
1145 | 0 | break; |
1146 | 0 | } |
1147 | 0 | } |
1148 | 0 | if (items_read) { |
1149 | 0 | *items_read = i; |
1150 | 0 | } |
1151 | 0 | if (items_written) { |
1152 | 0 | *items_written = p; |
1153 | 0 | } |
1154 | 0 | outbuf[p] = 0; |
1155 | |
|
1156 | 0 | return outbuf; |
1157 | 0 | } |
1158 | | |
1159 | | static void write_unicode(bytearray_t * bplist, char *val, uint64_t size) |
1160 | 0 | { |
1161 | 0 | long items_read = 0; |
1162 | 0 | long items_written = 0; |
1163 | 0 | uint16_t *unicodestr = NULL; |
1164 | |
|
1165 | 0 | unicodestr = plist_utf8_to_utf16be(val, size, &items_read, &items_written); |
1166 | 0 | write_raw_data(bplist, BPLIST_UNICODE, (uint8_t*)unicodestr, items_written); |
1167 | 0 | free(unicodestr); |
1168 | 0 | } |
1169 | | |
1170 | | static void write_array(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size) |
1171 | 0 | { |
1172 | 0 | node_t cur = NULL; |
1173 | 0 | uint64_t i = 0; |
1174 | |
|
1175 | 0 | uint64_t size = node_n_children(node); |
1176 | 0 | uint8_t marker = BPLIST_ARRAY | (size < 15 ? size : 0xf); |
1177 | 0 | byte_array_append(bplist, &marker, sizeof(uint8_t)); |
1178 | 0 | if (size >= 15) { |
1179 | 0 | write_int(bplist, size); |
1180 | 0 | } |
1181 | |
|
1182 | 0 | for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(cur), i++) { |
1183 | 0 | uint64_t idx = *(uint64_t *) (hash_table_lookup(ref_table, cur)); |
1184 | 0 | idx = be64toh(idx); |
1185 | 0 | byte_array_append(bplist, (uint8_t*)&idx + (sizeof(uint64_t) - ref_size), ref_size); |
1186 | 0 | } |
1187 | 0 | } |
1188 | | |
1189 | | static void write_dict(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size) |
1190 | 0 | { |
1191 | 0 | node_t cur = NULL; |
1192 | 0 | uint64_t i = 0; |
1193 | |
|
1194 | 0 | uint64_t size = node_n_children(node) / 2; |
1195 | 0 | uint8_t marker = BPLIST_DICT | (size < 15 ? size : 0xf); |
1196 | 0 | byte_array_append(bplist, &marker, sizeof(uint8_t)); |
1197 | 0 | if (size >= 15) { |
1198 | 0 | write_int(bplist, size); |
1199 | 0 | } |
1200 | |
|
1201 | 0 | for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) { |
1202 | 0 | uint64_t idx1 = *(uint64_t *) (hash_table_lookup(ref_table, cur)); |
1203 | 0 | idx1 = be64toh(idx1); |
1204 | 0 | byte_array_append(bplist, (uint8_t*)&idx1 + (sizeof(uint64_t) - ref_size), ref_size); |
1205 | 0 | } |
1206 | |
|
1207 | 0 | for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) { |
1208 | 0 | uint64_t idx2 = *(uint64_t *) (hash_table_lookup(ref_table, cur->next)); |
1209 | 0 | idx2 = be64toh(idx2); |
1210 | 0 | byte_array_append(bplist, (uint8_t*)&idx2 + (sizeof(uint64_t) - ref_size), ref_size); |
1211 | 0 | } |
1212 | 0 | } |
1213 | | |
1214 | | static void write_uid(bytearray_t * bplist, uint64_t val) |
1215 | 0 | { |
1216 | 0 | val = (uint32_t)val; |
1217 | 0 | int size = get_needed_bytes(val); |
1218 | 0 | uint8_t sz; |
1219 | | //do not write 3bytes int node |
1220 | 0 | if (size == 3) |
1221 | 0 | size++; |
1222 | 0 | sz = BPLIST_UID | (size-1); // yes, this is what Apple does... |
1223 | |
|
1224 | 0 | val = be64toh(val); |
1225 | 0 | byte_array_append(bplist, &sz, 1); |
1226 | 0 | byte_array_append(bplist, (uint8_t*)&val + (8-size), size); |
1227 | 0 | } |
1228 | | |
1229 | | static int is_ascii_string(char* s, int len) |
1230 | 0 | { |
1231 | 0 | int ret = 1, i = 0; |
1232 | 0 | for(i = 0; i < len; i++) |
1233 | 0 | { |
1234 | 0 | if ( !isascii( s[i] ) ) |
1235 | 0 | { |
1236 | 0 | ret = 0; |
1237 | 0 | break; |
1238 | 0 | } |
1239 | 0 | } |
1240 | 0 | return ret; |
1241 | 0 | } |
1242 | | |
1243 | | plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length) |
1244 | 0 | { |
1245 | 0 | ptrarray_t* objects = NULL; |
1246 | 0 | hashtable_t* ref_table = NULL; |
1247 | 0 | struct serialize_s ser_s; |
1248 | 0 | uint8_t offset_size = 0; |
1249 | 0 | uint8_t ref_size = 0; |
1250 | 0 | uint64_t num_objects = 0; |
1251 | 0 | uint64_t root_object = 0; |
1252 | 0 | uint64_t offset_table_index = 0; |
1253 | 0 | bytearray_t *bplist_buff = NULL; |
1254 | 0 | uint64_t i = 0; |
1255 | 0 | uint64_t buff_len = 0; |
1256 | 0 | uint64_t *offsets = NULL; |
1257 | 0 | bplist_trailer_t trailer; |
1258 | 0 | uint64_t objects_len = 0; |
1259 | | |
1260 | | //check for valid input |
1261 | 0 | if (!plist || !plist_bin || !length) { |
1262 | 0 | return PLIST_ERR_INVALID_ARG; |
1263 | 0 | } |
1264 | | |
1265 | | //list of objects |
1266 | 0 | objects = ptr_array_new(4096); |
1267 | 0 | if (!objects) { |
1268 | 0 | return PLIST_ERR_NO_MEM; |
1269 | 0 | } |
1270 | | //hashtable to write only once same nodes |
1271 | 0 | ref_table = hash_table_new(plist_data_hash, plist_data_compare, free); |
1272 | 0 | if (!ref_table) { |
1273 | 0 | ptr_array_free(objects); |
1274 | 0 | return PLIST_ERR_NO_MEM; |
1275 | 0 | } |
1276 | | |
1277 | | //serialize plist |
1278 | 0 | ser_s.objects = objects; |
1279 | 0 | ser_s.ref_table = ref_table; |
1280 | 0 | serialize_plist((node_t)plist, &ser_s); |
1281 | | |
1282 | | //now stream to output buffer |
1283 | 0 | offset_size = 0; //unknown yet |
1284 | 0 | objects_len = objects->len; |
1285 | 0 | ref_size = get_needed_bytes(objects_len); |
1286 | 0 | num_objects = objects->len; |
1287 | 0 | root_object = 0; //root is first in list |
1288 | 0 | offset_table_index = 0; //unknown yet |
1289 | | |
1290 | | //figure out the storage size required |
1291 | 0 | uint64_t req = 0; |
1292 | 0 | for (i = 0; i < num_objects; i++) |
1293 | 0 | { |
1294 | 0 | node_t node = (node_t)ptr_array_index(objects, i); |
1295 | 0 | plist_data_t data = plist_get_data(node); |
1296 | 0 | uint64_t size; |
1297 | 0 | uint8_t bsize; |
1298 | 0 | switch (data->type) |
1299 | 0 | { |
1300 | 0 | case PLIST_NULL: |
1301 | 0 | case PLIST_BOOLEAN: |
1302 | 0 | req += 1; |
1303 | 0 | break; |
1304 | 0 | case PLIST_KEY: |
1305 | 0 | case PLIST_STRING: |
1306 | 0 | req += 1; |
1307 | 0 | if (data->length >= 15) { |
1308 | 0 | bsize = get_needed_bytes(data->length); |
1309 | 0 | if (bsize == 3) bsize = 4; |
1310 | 0 | req += 1; |
1311 | 0 | req += bsize; |
1312 | 0 | } |
1313 | 0 | if ( is_ascii_string(data->strval, data->length) ) |
1314 | 0 | { |
1315 | 0 | req += data->length; |
1316 | 0 | } |
1317 | 0 | else |
1318 | 0 | { |
1319 | 0 | req += data->length * 2; |
1320 | 0 | } |
1321 | 0 | break; |
1322 | 0 | case PLIST_REAL: |
1323 | 0 | size = get_real_bytes(data->realval); |
1324 | 0 | req += 1; |
1325 | 0 | req += size; |
1326 | 0 | break; |
1327 | 0 | case PLIST_DATE: |
1328 | 0 | req += 9; |
1329 | 0 | break; |
1330 | 0 | case PLIST_ARRAY: |
1331 | 0 | size = node_n_children(node); |
1332 | 0 | req += 1; |
1333 | 0 | if (size >= 15) { |
1334 | 0 | bsize = get_needed_bytes(size); |
1335 | 0 | if (bsize == 3) bsize = 4; |
1336 | 0 | req += 1; |
1337 | 0 | req += bsize; |
1338 | 0 | } |
1339 | 0 | req += size * ref_size; |
1340 | 0 | break; |
1341 | 0 | case PLIST_DICT: |
1342 | 0 | size = node_n_children(node) / 2; |
1343 | 0 | req += 1; |
1344 | 0 | if (size >= 15) { |
1345 | 0 | bsize = get_needed_bytes(size); |
1346 | 0 | if (bsize == 3) bsize = 4; |
1347 | 0 | req += 1; |
1348 | 0 | req += bsize; |
1349 | 0 | } |
1350 | 0 | req += size * 2 * ref_size; |
1351 | 0 | break; |
1352 | 0 | default: |
1353 | 0 | size = data->length; |
1354 | 0 | req += 1; |
1355 | 0 | if (size >= 15) { |
1356 | 0 | bsize = get_needed_bytes(size); |
1357 | 0 | if (bsize == 3) bsize = 4; |
1358 | 0 | req += 1; |
1359 | 0 | req += bsize; |
1360 | 0 | } |
1361 | 0 | req += data->length; |
1362 | 0 | break; |
1363 | 0 | } |
1364 | 0 | } |
1365 | | // add size of magic |
1366 | 0 | req += BPLIST_MAGIC_SIZE; |
1367 | 0 | req += BPLIST_VERSION_SIZE; |
1368 | | // add size of offset table |
1369 | 0 | req += get_needed_bytes(req) * num_objects; |
1370 | | // add size of trailer |
1371 | 0 | req += sizeof(bplist_trailer_t); |
1372 | | |
1373 | | //setup a dynamic bytes array to store bplist in |
1374 | 0 | bplist_buff = byte_array_new(req); |
1375 | 0 | if (!bplist_buff) { |
1376 | 0 | ptr_array_free(objects); |
1377 | 0 | hash_table_destroy(ref_table); |
1378 | 0 | return PLIST_ERR_NO_MEM; |
1379 | 0 | } |
1380 | | |
1381 | | //set magic number and version |
1382 | 0 | byte_array_append(bplist_buff, BPLIST_MAGIC, BPLIST_MAGIC_SIZE); |
1383 | 0 | byte_array_append(bplist_buff, BPLIST_VERSION, BPLIST_VERSION_SIZE); |
1384 | | |
1385 | | //write objects and table |
1386 | 0 | offsets = (uint64_t *) malloc(num_objects * sizeof(uint64_t)); |
1387 | 0 | assert(offsets != NULL); |
1388 | 0 | for (i = 0; i < num_objects; i++) |
1389 | 0 | { |
1390 | |
|
1391 | 0 | plist_data_t data = plist_get_data(ptr_array_index(objects, i)); |
1392 | 0 | offsets[i] = bplist_buff->len; |
1393 | |
|
1394 | 0 | switch (data->type) |
1395 | 0 | { |
1396 | 0 | case PLIST_NULL: { |
1397 | 0 | uint8_t b = 0; |
1398 | 0 | byte_array_append(bplist_buff, &b, 1); |
1399 | 0 | break; |
1400 | 0 | } |
1401 | 0 | case PLIST_BOOLEAN: { |
1402 | 0 | uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE; |
1403 | 0 | byte_array_append(bplist_buff, &b, 1); |
1404 | 0 | break; |
1405 | 0 | } |
1406 | 0 | case PLIST_INT: |
1407 | 0 | if (data->length == 16) { |
1408 | 0 | write_uint(bplist_buff, data->intval); |
1409 | 0 | } else { |
1410 | 0 | write_int(bplist_buff, data->intval); |
1411 | 0 | } |
1412 | 0 | break; |
1413 | | |
1414 | 0 | case PLIST_REAL: |
1415 | 0 | write_real(bplist_buff, data->realval); |
1416 | 0 | break; |
1417 | | |
1418 | 0 | case PLIST_KEY: |
1419 | 0 | case PLIST_STRING: |
1420 | 0 | if ( is_ascii_string(data->strval, data->length) ) |
1421 | 0 | { |
1422 | 0 | write_string(bplist_buff, data->strval, data->length); |
1423 | 0 | } |
1424 | 0 | else |
1425 | 0 | { |
1426 | 0 | write_unicode(bplist_buff, data->strval, data->length); |
1427 | 0 | } |
1428 | 0 | break; |
1429 | 0 | case PLIST_DATA: |
1430 | 0 | write_data(bplist_buff, data->buff, data->length); |
1431 | 0 | break; |
1432 | 0 | case PLIST_ARRAY: |
1433 | 0 | write_array(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size); |
1434 | 0 | break; |
1435 | 0 | case PLIST_DICT: |
1436 | 0 | write_dict(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size); |
1437 | 0 | break; |
1438 | 0 | case PLIST_DATE: |
1439 | 0 | write_date(bplist_buff, data->realval); |
1440 | 0 | break; |
1441 | 0 | case PLIST_UID: |
1442 | 0 | write_uid(bplist_buff, data->intval); |
1443 | 0 | break; |
1444 | 0 | default: |
1445 | 0 | break; |
1446 | 0 | } |
1447 | 0 | } |
1448 | | |
1449 | | //free intermediate objects |
1450 | 0 | ptr_array_free(objects); |
1451 | 0 | hash_table_destroy(ref_table); |
1452 | | |
1453 | | //write offsets |
1454 | 0 | buff_len = bplist_buff->len; |
1455 | 0 | offset_size = get_needed_bytes(buff_len); |
1456 | 0 | offset_table_index = bplist_buff->len; |
1457 | 0 | for (i = 0; i < num_objects; i++) { |
1458 | 0 | uint64_t offset = be64toh(offsets[i]); |
1459 | 0 | byte_array_append(bplist_buff, (uint8_t*)&offset + (sizeof(uint64_t) - offset_size), offset_size); |
1460 | 0 | } |
1461 | 0 | free(offsets); |
1462 | | |
1463 | | //setup trailer |
1464 | 0 | memset(trailer.unused, '\0', sizeof(trailer.unused)); |
1465 | 0 | trailer.offset_size = offset_size; |
1466 | 0 | trailer.ref_size = ref_size; |
1467 | 0 | trailer.num_objects = be64toh(num_objects); |
1468 | 0 | trailer.root_object_index = be64toh(root_object); |
1469 | 0 | trailer.offset_table_offset = be64toh(offset_table_index); |
1470 | |
|
1471 | 0 | byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t)); |
1472 | | |
1473 | | //set output buffer and size |
1474 | 0 | *plist_bin = (char*)bplist_buff->data; |
1475 | 0 | *length = bplist_buff->len; |
1476 | |
|
1477 | 0 | bplist_buff->data = NULL; // make sure we don't free the output buffer |
1478 | 0 | byte_array_free(bplist_buff); |
1479 | |
|
1480 | 0 | return PLIST_ERR_SUCCESS; |
1481 | 0 | } |