/* $NetBSD$ */ /*- * Copyright (c) 2006 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include "prop_object_impl.h" #include "prop_codec_impl.h" #if defined(_KERNEL) #include #elif defined(_STANDALONE) #include #include #else #include #include #include #endif boolean_t _prop_object_externalize_start_tag( struct _prop_object_externalize_context *, const char *); boolean_t _prop_object_externalize_end_tag( struct _prop_object_externalize_context *, const char *); boolean_t _prop_object_externalize_empty_tag( struct _prop_object_externalize_context *, const char *); boolean_t _prop_object_externalize_append_encoded_cstring( struct _prop_object_externalize_context *, const char *); boolean_t _prop_object_externalize_header( struct _prop_object_externalize_context *); boolean_t _prop_object_externalize_footer( struct _prop_object_externalize_context *); static boolean_t _prop_object_externalize( struct _prop_object_externalize_context *, prop_object_t); typedef enum { _PROP_TAG_TYPE_START, /* e.g. */ _PROP_TAG_TYPE_END, /* e.g. */ _PROP_TAG_TYPE_EITHER } _prop_tag_type_t; struct _prop_object_internalize_context { const char *poic_xml; const char *poic_cp; const char *poic_tag_start; const char *poic_tagname; size_t poic_tagname_len; const char *poic_tagattr; size_t poic_tagattr_len; const char *poic_tagattrval; size_t poic_tagattrval_len; boolean_t poic_is_empty_element; _prop_tag_type_t poic_tag_type; }; #define _PROP_EOF(c) ((c) == '\0') #define _PROP_ISSPACE(c) \ ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || \ _PROP_EOF(c)) #define _PROP_TAG_MATCH(ctx, t) \ _prop_object_internalize_match((ctx)->poic_tagname, \ (ctx)->poic_tagname_len, \ (t), strlen(t)) #define _PROP_TAGATTR_MATCH(ctx, a) \ _prop_object_internalize_match((ctx)->poic_tagattr, \ (ctx)->poic_tagattr_len, \ (a), strlen(a)) #define _PROP_TAGATTRVAL_MATCH(ctx, a) \ _prop_object_internalize_match((ctx)->poic_tagattrval, \ (ctx)->poic_tagattrval_len,\ (a), strlen(a)) boolean_t _prop_object_internalize_find_tag( struct _prop_object_internalize_context *, const char *, _prop_tag_type_t); boolean_t _prop_object_internalize_match(const char *, size_t, const char *, size_t); prop_object_t _prop_object_internalize_by_tag( struct _prop_object_internalize_context *); boolean_t _prop_object_internalize_decode_string( struct _prop_object_internalize_context *, char *, size_t, size_t *, const char **); struct _prop_object_internalize_context * _prop_object_internalize_context_alloc(const char *); void _prop_object_internalize_context_free( struct _prop_object_internalize_context *); prop_object_t _prop_array_internalize( struct _prop_object_internalize_context *); prop_object_t _prop_bool_internalize( struct _prop_object_internalize_context *); prop_object_t _prop_data_internalize( struct _prop_object_internalize_context *); prop_object_t _prop_dictionary_internalize( struct _prop_object_internalize_context *); prop_object_t _prop_number_internalize( struct _prop_object_internalize_context *); prop_object_t _prop_string_internalize( struct _prop_object_internalize_context *); static char *prop_dictionary_externalize_xml(prop_dictionary_t); static char *prop_array_externalize_xml(prop_array_t); static prop_dictionary_t prop_dictionary_internalize_xml(const char *); static prop_array_t prop_array_internalize_xml(const char *); const struct _prop_codec prop_codec_xml = { .codec_name = "xml", .codec_sense = (const u_char *)"<", .codec_dictionary_externalize = prop_dictionary_externalize_xml, .codec_array_externalize = prop_array_externalize_xml, .codec_dictionary_internalize = prop_dictionary_internalize_xml, .codec_array_internalize = prop_array_internalize_xml, }; static boolean_t _prop_array_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_array_t pa = v; struct _prop_object *po; prop_object_iterator_t pi; unsigned int i; boolean_t rv = FALSE; _PROP_RWLOCK_RDLOCK(pa->pa_rwlock); if (pa->pa_count == 0) { _PROP_RWLOCK_UNLOCK(pa->pa_rwlock); return (_prop_object_externalize_empty_tag(ctx, "array")); } /* XXXJRT Hint "count" for the internalize step? */ if (_prop_object_externalize_start_tag(ctx, "array") == FALSE || _prop_object_externalize_append_char(ctx, '\n') == FALSE) goto out; pi = prop_array_iterator(pa); if (pi == NULL) goto out; ctx->poec_depth++; _PROP_ASSERT(ctx->poec_depth != 0); while ((po = prop_object_iterator_next(pi)) != NULL) { if (_prop_object_externalize(ctx, po) == FALSE) { prop_object_iterator_release(pi); goto out; } } prop_object_iterator_release(pi); ctx->poec_depth--; for (i = 0; i < ctx->poec_depth; i++) { if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) goto out; } if (_prop_object_externalize_end_tag(ctx, "array") == FALSE) goto out; rv = TRUE; out: _PROP_RWLOCK_UNLOCK(pa->pa_rwlock); return (rv); } /* * prop_array_externalize -- * Externalize an array, return a NUL-terminated buffer * containing the XML-style representation. The buffer is allocated * with the M_TEMP memory type. */ static char * prop_array_externalize_xml(prop_array_t pa) { struct _prop_object_externalize_context *ctx; char *cp; ctx = _prop_object_externalize_context_alloc(); if (ctx == NULL) return (NULL); if (_prop_object_externalize_header(ctx) == FALSE || _prop_object_externalize(ctx, &pa->pa_obj) == FALSE || _prop_object_externalize_footer(ctx) == FALSE) { /* We are responsible for releasing the buffer. */ _PROP_FREE(ctx->poec_buf, M_TEMP); _prop_object_externalize_context_free(ctx); return (NULL); } cp = ctx->poec_buf; _prop_object_externalize_context_free(ctx); return (cp); } /* * _prop_array_internalize -- * Parse an ... and return the object created from the * external representation. */ prop_object_t _prop_array_internalize(struct _prop_object_internalize_context *ctx) { prop_array_t array; prop_object_t obj; /* We don't currently understand any attributes. */ if (ctx->poic_tagattr != NULL) return (NULL); array = prop_array_create(); if (array == NULL) return (NULL); if (ctx->poic_is_empty_element) return (array); for (;;) { /* Fetch the next tag. */ if (_prop_object_internalize_find_tag(ctx, NULL, _PROP_TAG_TYPE_EITHER) == FALSE) goto bad; /* Check to see if this is the end of the array. */ if (_PROP_TAG_MATCH(ctx, "array") && ctx->poic_tag_type == _PROP_TAG_TYPE_END) break; /* Fetch the object. */ obj = _prop_object_internalize_by_tag(ctx); if (obj == NULL) goto bad; if (prop_array_add(array, obj) == FALSE) { prop_object_release(obj); goto bad; } prop_object_release(obj); } return (array); bad: prop_object_release(array); return (NULL); } /* * prop_array_internalize -- * Create an array by parsing the XML-style representation. */ static prop_array_t prop_array_internalize_xml(const char *xml) { prop_array_t array = NULL; struct _prop_object_internalize_context *ctx; ctx = _prop_object_internalize_context_alloc(xml); if (ctx == NULL) return (NULL); /* We start with a tag. */ if (_prop_object_internalize_find_tag(ctx, "plist", _PROP_TAG_TYPE_START) == FALSE) goto out; /* Plist elements cannot be empty. */ if (ctx->poic_is_empty_element) goto out; /* * We don't understand any plist attributes, but Apple XML * property lists often have a "version" attribute. If we * see that one, we simply ignore it. */ if (ctx->poic_tagattr != NULL && !_PROP_TAGATTR_MATCH(ctx, "version")) goto out; /* Next we expect to see . */ if (_prop_object_internalize_find_tag(ctx, "array", _PROP_TAG_TYPE_START) == FALSE) goto out; array = _prop_array_internalize(ctx); if (array == NULL) goto out; /* We've advanced past . Now we want . */ if (_prop_object_internalize_find_tag(ctx, "plist", _PROP_TAG_TYPE_END) == FALSE) { prop_object_release(array); array = NULL; } out: _prop_object_internalize_context_free(ctx); return (array); } static boolean_t _prop_bool_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_bool_t pb = v; return (_prop_object_externalize_empty_tag(ctx, prop_bool_true(pb) ? "true" : "false")); } /* * _prop_bool_internalize -- * Parse a or and return the object created from * the external representation. */ prop_object_t _prop_bool_internalize(struct _prop_object_internalize_context *ctx) { boolean_t val; /* No attributes, and it must be an empty element. */ if (ctx->poic_tagattr != NULL || ctx->poic_is_empty_element == FALSE) return (NULL); if (_PROP_TAG_MATCH(ctx, "true")) val = TRUE; else { _PROP_ASSERT(_PROP_TAG_MATCH(ctx, "false")); val = FALSE; } return (prop_bool_create(val)); } static const char _prop_data_base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char _prop_data_pad64 = '='; static boolean_t _prop_data_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_data_t pd = v; size_t i, srclen; const uint8_t *src; uint8_t output[4]; uint8_t input[3]; if (pd->pd_size == 0) return (_prop_object_externalize_empty_tag(ctx, "data")); if (_prop_object_externalize_start_tag(ctx, "data") == FALSE) return (FALSE); for (src = pd->pd_immutable, srclen = pd->pd_size; srclen > 2; srclen -= 3) { input[0] = *src++; input[1] = *src++; input[2] = *src++; output[0] = (uint32_t)input[0] >> 2; output[1] = ((uint32_t)(input[0] & 0x03) << 4) + ((uint32_t)input[1] >> 4); output[2] = ((u_int32_t)(input[1] & 0x0f) << 2) + ((uint32_t)input[2] >> 6); output[3] = input[2] & 0x3f; _PROP_ASSERT(output[0] < 64); _PROP_ASSERT(output[1] < 64); _PROP_ASSERT(output[2] < 64); _PROP_ASSERT(output[3] < 64); if (_prop_object_externalize_append_char(ctx, _prop_data_base64[output[0]]) == FALSE || _prop_object_externalize_append_char(ctx, _prop_data_base64[output[1]]) == FALSE || _prop_object_externalize_append_char(ctx, _prop_data_base64[output[2]]) == FALSE || _prop_object_externalize_append_char(ctx, _prop_data_base64[output[3]]) == FALSE) return (FALSE); } if (srclen != 0) { input[0] = input[1] = input[2] = '\0'; for (i = 0; i < srclen; i++) input[i] = *src++; output[0] = (uint32_t)input[0] >> 2; output[1] = ((uint32_t)(input[0] & 0x03) << 4) + ((uint32_t)input[1] >> 4); output[2] = ((u_int32_t)(input[1] & 0x0f) << 2) + ((uint32_t)input[2] >> 6); _PROP_ASSERT(output[0] < 64); _PROP_ASSERT(output[1] < 64); _PROP_ASSERT(output[2] < 64); if (_prop_object_externalize_append_char(ctx, _prop_data_base64[output[0]]) == FALSE || _prop_object_externalize_append_char(ctx, _prop_data_base64[output[1]]) == FALSE || _prop_object_externalize_append_char(ctx, srclen == 1 ? _prop_data_pad64 : _prop_data_base64[output[2]]) == FALSE || _prop_object_externalize_append_char(ctx, _prop_data_pad64) == FALSE) return (FALSE); } if (_prop_object_externalize_end_tag(ctx, "data") == FALSE) return (FALSE); return (TRUE); } static boolean_t _prop_data_internalize_decode(struct _prop_object_internalize_context *ctx, uint8_t *target, size_t targsize, size_t *sizep, const char **cpp) { const char *src; size_t tarindex; int state, ch; const char *pos; state = 0; tarindex = 0; src = ctx->poic_cp; for (;;) { ch = (unsigned char) *src++; if (_PROP_EOF(ch)) return (FALSE); if (_PROP_ISSPACE(ch)) continue; if (ch == '<') { src--; break; } if (ch == _prop_data_pad64) break; pos = strchr(_prop_data_base64, ch); if (pos == NULL) return (FALSE); switch (state) { case 0: if (target) { if (tarindex >= targsize) return (FALSE); target[tarindex] = (uint8_t)((pos - _prop_data_base64) << 2); } state = 1; break; case 1: if (target) { if (tarindex + 1 >= targsize) return (FALSE); target[tarindex] |= (uint32_t)(pos - _prop_data_base64) >> 4; target[tarindex + 1] = (uint8_t)(((pos - _prop_data_base64) & 0xf) << 4); } tarindex++; state = 2; break; case 2: if (target) { if (tarindex + 1 >= targsize) return (FALSE); target[tarindex] |= (uint32_t)(pos - _prop_data_base64) >> 2; target[tarindex + 1] = (uint8_t)(((pos - _prop_data_base64) & 0x3) << 6); } tarindex++; state = 3; break; case 3: if (target) { if (tarindex >= targsize) return (FALSE); target[tarindex] |= (uint8_t) (pos - _prop_data_base64); } tarindex++; state = 0; break; default: _PROP_ASSERT(/*CONSTCOND*/0); } } /* * We are done decoding the Base64 characters. Let's see if we * ended up on a byte boundary and/or with unrecognized trailing * characters. */ if (ch == _prop_data_pad64) { ch = (unsigned char) *src; /* src already advanced */ if (_PROP_EOF(ch)) return (FALSE); switch (state) { case 0: /* Invalid = in first position */ case 1: /* Invalid = in second position */ return (FALSE); case 2: /* Valid, one byte of info */ /* Skip whitespace */ for (ch = (unsigned char) *src++; ch != '<'; ch = (unsigned char) *src++) { if (_PROP_EOF(ch)) return (FALSE); if (!_PROP_ISSPACE(ch)) break; } /* Make sure there is another trailing = */ if (ch != _prop_data_pad64) return (FALSE); ch = (unsigned char) *src; /* FALLTHROUGH */ case 3: /* Valid, two bytes of info */ /* * We know this char is a =. Is there anything but * whitespace after it? */ for (; ch != '<'; ch = (unsigned char) *src++) { if (_PROP_EOF(ch)) return (FALSE); if (!_PROP_ISSPACE(ch)) return (FALSE); } } } else { /* * We ended by seeing the end of the Base64 string. Make * sure there are no partial bytes lying around. */ if (state != 0) return (FALSE); } _PROP_ASSERT(*src == '<'); if (sizep != NULL) *sizep = tarindex; if (cpp != NULL) *cpp = src; return (TRUE); } /* * _prop_data_internalize -- * Parse a ... and return the object created from the * external representation. */ prop_object_t _prop_data_internalize(struct _prop_object_internalize_context *ctx) { prop_data_t data; uint8_t *buf; size_t len, alen; /* We don't accept empty elements. */ if (ctx->poic_is_empty_element) return (NULL); /* * If we got a "size" attribute, get the size of the data blob * from that. Otherwise, we have to figure it out from the base64. */ if (ctx->poic_tagattr != NULL) { char *cp; if (!_PROP_TAGATTR_MATCH(ctx, "size") || ctx->poic_tagattrval_len == 0) return (NULL); #ifndef _KERNEL errno = 0; #endif /* XXX Assumes size_t and unsigned long are the same size. */ len = strtoul(ctx->poic_tagattrval, &cp, 0); #ifndef _KERNEL /* XXX can't check for ERANGE in the kernel */ if (len == ULONG_MAX && errno == ERANGE) return (NULL); #endif if (cp != ctx->poic_tagattrval + ctx->poic_tagattrval_len) return (NULL); _PROP_ASSERT(*cp == '\"'); } else if (_prop_data_internalize_decode(ctx, NULL, 0, &len, NULL) == FALSE) return (NULL); /* * Always allocate one extra in case we don't land on an even byte * boundary during the decode. */ buf = _PROP_MALLOC(len + 1, M_PROP_DATA); if (buf == NULL) return (NULL); if (_prop_data_internalize_decode(ctx, buf, len + 1, &alen, &ctx->poic_cp) == FALSE) { _PROP_FREE(buf, M_PROP_DATA); return (NULL); } if (alen != len) { _PROP_FREE(buf, M_PROP_DATA); return (NULL); } if (_prop_object_internalize_find_tag(ctx, "data", _PROP_TAG_TYPE_END) == FALSE) { _PROP_FREE(buf, M_PROP_DATA); return (NULL); } data = _prop_data_alloc(); if (data == NULL) { _PROP_FREE(buf, M_PROP_DATA); return (NULL); } data->pd_mutable = buf; data->pd_size = len; return (data); } static boolean_t _prop_dict_keysym_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_dictionary_keysym_t pdk = v; const char *s = prop_dictionary_keysym_cstring_nocopy(pdk); /* We externalize these as strings, and they're never empty. */ _PROP_ASSERT(s[0] != '\0'); if (_prop_object_externalize_start_tag(ctx, "string") == FALSE || _prop_object_externalize_append_encoded_cstring(ctx, s) == FALSE || _prop_object_externalize_end_tag(ctx, "string") == FALSE) return (FALSE); return (TRUE); } static boolean_t _prop_dictionary_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_dictionary_t pd = v; prop_dictionary_keysym_t pdk; struct _prop_object *po; prop_object_iterator_t pi; unsigned int i; boolean_t rv = FALSE; _PROP_RWLOCK_RDLOCK(pd->pd_rwlock); if (pd->pd_count == 0) { _PROP_RWLOCK_UNLOCK(pd->pd_rwlock); return (_prop_object_externalize_empty_tag(ctx, "dict")); } if (_prop_object_externalize_start_tag(ctx, "dict") == FALSE || _prop_object_externalize_append_char(ctx, '\n') == FALSE) goto out; pi = prop_dictionary_iterator(pd); if (pi == NULL) goto out; ctx->poec_depth++; _PROP_ASSERT(ctx->poec_depth != 0); while ((pdk = prop_object_iterator_next(pi)) != NULL) { po = prop_dictionary_get_keysym(pd, pdk); if (po == NULL || _prop_object_externalize_start_tag(ctx, "key") == FALSE || _prop_object_externalize_append_encoded_cstring(ctx, prop_dictionary_keysym_cstring_nocopy(pdk)) == FALSE || _prop_object_externalize_end_tag(ctx, "key") == FALSE || _prop_object_externalize(ctx, po) == FALSE) { prop_object_iterator_release(pi); goto out; } } prop_object_iterator_release(pi); ctx->poec_depth--; for (i = 0; i < ctx->poec_depth; i++) { if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) goto out; } if (_prop_object_externalize_end_tag(ctx, "dict") == FALSE) goto out; rv = TRUE; out: _PROP_RWLOCK_UNLOCK(pd->pd_rwlock); return (rv); } /* * prop_dictionary_externalize -- * Externalize a dictionary, returning a NUL-terminated buffer * containing the XML-style representation. The buffer is allocated * with the M_TEMP memory type. */ static char * prop_dictionary_externalize_xml(prop_dictionary_t pd) { struct _prop_object_externalize_context *ctx; char *cp; ctx = _prop_object_externalize_context_alloc(); if (ctx == NULL) return (NULL); if (_prop_object_externalize_header(ctx) == FALSE || _prop_object_externalize(ctx, &pd->pd_obj) == FALSE || _prop_object_externalize_footer(ctx) == FALSE) { /* We are responsible for releasing the buffer. */ _PROP_FREE(ctx->poec_buf, M_TEMP); _prop_object_externalize_context_free(ctx); return (NULL); } cp = ctx->poec_buf; _prop_object_externalize_context_free(ctx); return (cp); } /* * _prop_dictionary_internalize -- * Parse a ... and return the object created from the * external representation. */ prop_object_t _prop_dictionary_internalize(struct _prop_object_internalize_context *ctx) { prop_dictionary_t dict; prop_object_t val; size_t keylen; char *tmpkey; /* We don't currently understand any attributes. */ if (ctx->poic_tagattr != NULL) return (NULL); dict = prop_dictionary_create(); if (dict == NULL) return (NULL); if (ctx->poic_is_empty_element) return (dict); tmpkey = _PROP_MALLOC(_PROP_PDK_MAXKEY + 1, M_TEMP); if (tmpkey == NULL) goto bad; for (;;) { /* Fetch the next tag. */ if (_prop_object_internalize_find_tag(ctx, NULL, _PROP_TAG_TYPE_EITHER) == FALSE) goto bad; /* Check to see if this is the end of the dictionary. */ if (_PROP_TAG_MATCH(ctx, "dict") && ctx->poic_tag_type == _PROP_TAG_TYPE_END) break; /* Ok, it must be a non-empty key start tag. */ if (!_PROP_TAG_MATCH(ctx, "key") || ctx->poic_tag_type != _PROP_TAG_TYPE_START || ctx->poic_is_empty_element) goto bad; if (_prop_object_internalize_decode_string(ctx, tmpkey, _PROP_PDK_MAXKEY, &keylen, &ctx->poic_cp) == FALSE) goto bad; _PROP_ASSERT(keylen <= _PROP_PDK_MAXKEY); tmpkey[keylen] = '\0'; if (_prop_object_internalize_find_tag(ctx, "key", _PROP_TAG_TYPE_END) == FALSE) goto bad; /* ..and now the beginning of the value. */ if (_prop_object_internalize_find_tag(ctx, NULL, _PROP_TAG_TYPE_START) == FALSE) goto bad; val = _prop_object_internalize_by_tag(ctx); if (val == NULL) goto bad; if (prop_dictionary_set(dict, tmpkey, val) == FALSE) { prop_object_release(val); goto bad; } prop_object_release(val); } _PROP_FREE(tmpkey, M_TEMP); return (dict); bad: if (tmpkey != NULL) _PROP_FREE(tmpkey, M_TEMP); prop_object_release(dict); return (NULL); } /* * prop_dictionary_internalize -- * Create a dictionary by parsing the NUL-terminated XML-style * representation. */ static prop_dictionary_t prop_dictionary_internalize_xml(const char *xml) { prop_dictionary_t dict = NULL; struct _prop_object_internalize_context *ctx; ctx = _prop_object_internalize_context_alloc(xml); if (ctx == NULL) return (NULL); /* We start with a tag. */ if (_prop_object_internalize_find_tag(ctx, "plist", _PROP_TAG_TYPE_START) == FALSE) goto out; /* Plist elements cannot be empty. */ if (ctx->poic_is_empty_element) goto out; /* * We don't understand any plist attributes, but Apple XML * property lists often have a "version" attribute. If we * see that one, we simply ignore it. */ if (ctx->poic_tagattr != NULL && !_PROP_TAGATTR_MATCH(ctx, "version")) goto out; /* Next we expect to see . */ if (_prop_object_internalize_find_tag(ctx, "dict", _PROP_TAG_TYPE_START) == FALSE) goto out; dict = _prop_dictionary_internalize(ctx); if (dict == NULL) goto out; /* We've advanced past . Now we want . */ if (_prop_object_internalize_find_tag(ctx, "plist", _PROP_TAG_TYPE_END) == FALSE) { prop_object_release(dict); dict = NULL; } out: _prop_object_internalize_context_free(ctx); return (dict); } static boolean_t _prop_number_externalize(struct _prop_object_externalize_context *ctx, void *v) { prop_number_t pn = v; char tmpstr[32]; /* * For unsigned numbers, we output in hex. For signed numbers, * we output in decimal. */ if (prop_number_unsigned(pn)) sprintf(tmpstr, "0x%" PRIx64, prop_number_unsigned_integer_value(pn)); else sprintf(tmpstr, "%" PRIi64, prop_number_integer_value(pn)); if (_prop_object_externalize_start_tag(ctx, "integer") == FALSE || _prop_object_externalize_append_cstring(ctx, tmpstr) == FALSE || _prop_object_externalize_end_tag(ctx, "integer") == FALSE) return (FALSE); return (TRUE); } static boolean_t _prop_number_internalize_unsigned(struct _prop_object_internalize_context *ctx, struct _prop_number_value *pnv) { char *cp; _PROP_ASSERT(/*CONSTCOND*/sizeof(unsigned long long) == sizeof(uint64_t)); #ifndef _KERNEL errno = 0; #endif pnv->pnv_unsigned = (uint64_t) strtoull(ctx->poic_cp, &cp, 0); #ifndef _KERNEL /* XXX can't check for ERANGE in the kernel */ if (pnv->pnv_unsigned == UINT64_MAX && errno == ERANGE) return (FALSE); #endif pnv->pnv_is_unsigned = TRUE; ctx->poic_cp = cp; return (TRUE); } static boolean_t _prop_number_internalize_signed(struct _prop_object_internalize_context *ctx, struct _prop_number_value *pnv) { char *cp; _PROP_ASSERT(/*CONSTCOND*/sizeof(long long) == sizeof(int64_t)); #ifndef _KERNEL errno = 0; #endif pnv->pnv_signed = (int64_t) strtoll(ctx->poic_cp, &cp, 0); #ifndef _KERNEL /* XXX can't check for ERANGE in the kernel */ if ((pnv->pnv_signed == INT64_MAX || pnv->pnv_signed == INT64_MIN) && errno == ERANGE) return (FALSE); #endif pnv->pnv_is_unsigned = FALSE; ctx->poic_cp = cp; return (TRUE); } /* * _prop_number_internalize -- * Parse a ... and return the object created from * the external representation. */ prop_object_t _prop_number_internalize(struct _prop_object_internalize_context *ctx) { struct _prop_number_value pnv; memset(&pnv, 0, sizeof(pnv)); /* No attributes, no empty elements. */ if (ctx->poic_tagattr != NULL || ctx->poic_is_empty_element) return (NULL); /* * If the first character is '-', then we treat as signed. * If the first two characters are "0x" (i.e. the number is * in hex), then we treat as unsigned. Otherwise, we try * signed first, and if that fails (presumably due to ERANGE), * then we switch to unsigned. */ if (ctx->poic_cp[0] == '-') { if (_prop_number_internalize_signed(ctx, &pnv) == FALSE) return (NULL); } else if (ctx->poic_cp[0] == '0' && ctx->poic_cp[1] == 'x') { if (_prop_number_internalize_unsigned(ctx, &pnv) == FALSE) return (NULL); } else { if (_prop_number_internalize_signed(ctx, &pnv) == FALSE && _prop_number_internalize_unsigned(ctx, &pnv) == FALSE) return (NULL); } if (_prop_object_internalize_find_tag(ctx, "integer", _PROP_TAG_TYPE_END) == FALSE) return (NULL); return (_prop_number_alloc(&pnv)); } /* * _prop_object_externalize_start_tag -- * Append an XML-style start tag to the externalize buffer. */ boolean_t _prop_object_externalize_start_tag( struct _prop_object_externalize_context *ctx, const char *tag) { unsigned int i; for (i = 0; i < ctx->poec_depth; i++) { if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) return (FALSE); } if (_prop_object_externalize_append_char(ctx, '<') == FALSE || _prop_object_externalize_append_cstring(ctx, tag) == FALSE || _prop_object_externalize_append_char(ctx, '>') == FALSE) return (FALSE); return (TRUE); } /* * _prop_object_externalize_end_tag -- * Append an XML-style end tag to the externalize buffer. */ boolean_t _prop_object_externalize_end_tag( struct _prop_object_externalize_context *ctx, const char *tag) { if (_prop_object_externalize_append_char(ctx, '<') == FALSE || _prop_object_externalize_append_char(ctx, '/') == FALSE || _prop_object_externalize_append_cstring(ctx, tag) == FALSE || _prop_object_externalize_append_char(ctx, '>') == FALSE || _prop_object_externalize_append_char(ctx, '\n') == FALSE) return (FALSE); return (TRUE); } /* * _prop_object_externalize_empty_tag -- * Append an XML-style empty tag to the externalize buffer. */ boolean_t _prop_object_externalize_empty_tag( struct _prop_object_externalize_context *ctx, const char *tag) { unsigned int i; for (i = 0; i < ctx->poec_depth; i++) { if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) return (FALSE); } if (_prop_object_externalize_append_char(ctx, '<') == FALSE || _prop_object_externalize_append_cstring(ctx, tag) == FALSE || _prop_object_externalize_append_char(ctx, '/') == FALSE || _prop_object_externalize_append_char(ctx, '>') == FALSE || _prop_object_externalize_append_char(ctx, '\n') == FALSE) return (FALSE); return (TRUE); } /* * _prop_object_externalize_append_encoded_cstring -- * Append an encoded C string to the externalize buffer. */ boolean_t _prop_object_externalize_append_encoded_cstring( struct _prop_object_externalize_context *ctx, const char *cp) { while (*cp != '\0') { switch (*cp) { case '<': if (_prop_object_externalize_append_cstring(ctx, "<") == FALSE) return (FALSE); break; case '>': if (_prop_object_externalize_append_cstring(ctx, ">") == FALSE) return (FALSE); break; case '&': if (_prop_object_externalize_append_cstring(ctx, "&") == FALSE) return (FALSE); break; default: if (_prop_object_externalize_append_char(ctx, (unsigned char) *cp) == FALSE) return (FALSE); break; } cp++; } return (TRUE); } /* * _prop_object_externalize_header -- * Append the standard XML header to the externalize buffer. */ boolean_t _prop_object_externalize_header(struct _prop_object_externalize_context *ctx) { static const char _plist_xml_header[] = "\n" "\n"; if (_prop_object_externalize_append_cstring(ctx, _plist_xml_header) == FALSE || _prop_object_externalize_start_tag(ctx, "plist version=\"1.0\"") == FALSE || _prop_object_externalize_append_char(ctx, '\n') == FALSE) return (FALSE); return (TRUE); } /* * _prop_object_externalize_footer -- * Append the standard XML footer to the externalize buffer. This * also NUL-terminates the buffer. */ boolean_t _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx) { if (_prop_object_externalize_end_tag(ctx, "plist") == FALSE || _prop_object_externalize_append_char(ctx, '\0') == FALSE) return (FALSE); return (TRUE); } /* * _prop_object_internalize_skip_comment -- * Skip the body and end tag of a comment. */ static boolean_t _prop_object_internalize_skip_comment( struct _prop_object_internalize_context *ctx) { const char *cp = ctx->poic_cp; while (!_PROP_EOF(*cp)) { if (cp[0] == '-' && cp[1] == '-' && cp[2] == '>') { ctx->poic_cp = cp + 3; return (TRUE); } cp++; } return (FALSE); /* ran out of buffer */ } /* * _prop_object_internalize_find_tag -- * Find the next tag in an XML stream. Optionally compare the found * tag to an expected tag name. State of the context is undefined * if this routine returns FALSE. Upon success, the context points * to the first octet after the tag. */ boolean_t _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx, const char *tag, _prop_tag_type_t type) { const char *cp; size_t taglen; if (tag != NULL) taglen = strlen(tag); else taglen = 0; start_over: cp = ctx->poic_cp; /* * Find the start of the tag. */ while (_PROP_ISSPACE(*cp)) cp++; if (_PROP_EOF(*cp)) return (FALSE); if (*cp != '<') return (FALSE); ctx->poic_tag_start = cp++; if (_PROP_EOF(*cp)) return (FALSE); if (*cp == '!') { if (cp[1] != '-' || cp[2] != '-') return (FALSE); /* * Comment block -- only allowed if we are allowed to * return a start tag. */ if (type == _PROP_TAG_TYPE_END) return (FALSE); ctx->poic_cp = cp + 3; if (_prop_object_internalize_skip_comment(ctx) == FALSE) return (FALSE); goto start_over; } if (*cp == '/') { if (type != _PROP_TAG_TYPE_END && type != _PROP_TAG_TYPE_EITHER) return (FALSE); cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tag_type = _PROP_TAG_TYPE_END; } else { if (type != _PROP_TAG_TYPE_START && type != _PROP_TAG_TYPE_EITHER) return (FALSE); ctx->poic_tag_type = _PROP_TAG_TYPE_START; } ctx->poic_tagname = cp; while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tagname_len = cp - ctx->poic_tagname; /* Make sure this is the tag we're looking for. */ if (tag != NULL && (taglen != ctx->poic_tagname_len || memcmp(tag, ctx->poic_tagname, taglen) != 0)) return (FALSE); /* Check for empty tag. */ if (*cp == '/') { if (ctx->poic_tag_type != _PROP_TAG_TYPE_START) return(FALSE); /* only valid on start tags */ ctx->poic_is_empty_element = TRUE; cp++; if (_PROP_EOF(*cp) || *cp != '>') return (FALSE); } else ctx->poic_is_empty_element = FALSE; /* Easy case of no arguments. */ if (*cp == '>') { ctx->poic_tagattr = NULL; ctx->poic_tagattr_len = 0; ctx->poic_tagattrval = NULL; ctx->poic_tagattrval_len = 0; ctx->poic_cp = cp + 1; return (TRUE); } _PROP_ASSERT(!_PROP_EOF(*cp)); cp++; if (_PROP_EOF(*cp)) return (FALSE); while (_PROP_ISSPACE(*cp)) cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tagattr = cp; while (!_PROP_ISSPACE(*cp) && *cp != '=') cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tagattr_len = cp - ctx->poic_tagattr; cp++; if (*cp != '\"') return (FALSE); cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tagattrval = cp; while (*cp != '\"') cp++; if (_PROP_EOF(*cp)) return (FALSE); ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval; cp++; if (*cp != '>') return (FALSE); ctx->poic_cp = cp + 1; return (TRUE); } /* * _prop_object_internalize_decode_string -- * Decode an encoded string. */ boolean_t _prop_object_internalize_decode_string( struct _prop_object_internalize_context *ctx, char *target, size_t targsize, size_t *sizep, const char **cpp) { const char *src; size_t tarindex; char c; tarindex = 0; src = ctx->poic_cp; for (;;) { if (_PROP_EOF(*src)) return (FALSE); if (*src == '<') { break; } if ((c = *src) == '&') { if (src[1] == 'a' && src[2] == 'm' && src[3] == 'p' && src[4] == ';') { c = '&'; src += 5; } else if (src[1] == 'l' && src[2] == 't' && src[3] == ';') { c = '<'; src += 4; } else if (src[1] == 'g' && src[2] == 't' && src[3] == ';') { c = '>'; src += 4; } else if (src[1] == 'a' && src[2] == 'p' && src[3] == 'o' && src[4] == 's' && src[5] == ';') { c = '\''; src += 6; } else if (src[1] == 'q' && src[2] == 'u' && src[3] == 'o' && src[4] == 't' && src[5] == ';') { c = '\"'; src += 6; } else return (FALSE); } else src++; if (target) { if (tarindex >= targsize) return (FALSE); target[tarindex] = c; } tarindex++; } _PROP_ASSERT(*src == '<'); if (sizep != NULL) *sizep = tarindex; if (cpp != NULL) *cpp = src; return (TRUE); } /* * _prop_object_internalize_match -- * Returns true if the two character streams match. */ boolean_t _prop_object_internalize_match(const char *str1, size_t len1, const char *str2, size_t len2) { return (len1 == len2 && memcmp(str1, str2, len1) == 0); } #define INTERNALIZER(t, f) \ { t, sizeof(t) - 1, f } static const struct _prop_object_internalizer { const char *poi_tag; size_t poi_taglen; prop_object_t (*poi_intern)( struct _prop_object_internalize_context *); } _prop_object_internalizer_table[] = { INTERNALIZER("array", _prop_array_internalize), INTERNALIZER("true", _prop_bool_internalize), INTERNALIZER("false", _prop_bool_internalize), INTERNALIZER("data", _prop_data_internalize), INTERNALIZER("dict", _prop_dictionary_internalize), INTERNALIZER("integer", _prop_number_internalize), INTERNALIZER("string", _prop_string_internalize), { 0, 0, NULL } }; #undef INTERNALIZER /* * _prop_object_internalize_by_tag -- * Determine the object type from the tag in the context and * internalize it. */ prop_object_t _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx) { const struct _prop_object_internalizer *poi; for (poi = _prop_object_internalizer_table; poi->poi_tag != NULL; poi++) { if (_prop_object_internalize_match(ctx->poic_tagname, ctx->poic_tagname_len, poi->poi_tag, poi->poi_taglen)) return ((*poi->poi_intern)(ctx)); } return (NULL); } /* * _prop_object_internalize_context_alloc -- * Allocate an internalize context. */ struct _prop_object_internalize_context * _prop_object_internalize_context_alloc(const char *xml) { struct _prop_object_internalize_context *ctx; ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context), M_TEMP); if (ctx == NULL) return (NULL); ctx->poic_xml = ctx->poic_cp = xml; /* * Skip any whitespace and XML preamble stuff that we don't * know about / care about. */ for (;;) { while (_PROP_ISSPACE(*xml)) xml++; if (_PROP_EOF(*xml) || *xml != '<') goto bad; #define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0) /* * Skip over the XML preamble that Apple XML property * lists usually include at the top of the file. */ if (MATCH("?xml ") || MATCH("!DOCTYPE plist")) { while (*xml != '>' && !_PROP_EOF(*xml)) xml++; if (_PROP_EOF(*xml)) goto bad; xml++; /* advance past the '>' */ continue; } if (MATCH("