/*      $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 <prop/proplib.h>
#include "prop_object_impl.h"
#include "prop_codec_impl.h"

#if defined(_KERNEL)
#include <sys/systm.h>
#elif defined(_STANDALONE)
#include <sys/param.h>
#include <lib/libkern/libkern.h>
#else
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#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. <dict> */
       _PROP_TAG_TYPE_END,                     /* e.g. </dict> */
       _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 <array>...</array> 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 <plist> 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 <array>. */
       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 </array>.  Now we want </plist>. */
       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 <true/> or <false/> 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 <data>...</data> 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 <dict>...</dict> 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 <plist> 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 <dict>. */
       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 </dict>.  Now we want </plist>. */
       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 <number>...</number> 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,
                                       "&lt;") == FALSE)
                               return (FALSE);
                       break;
               case '>':
                       if (_prop_object_externalize_append_cstring(ctx,
                                       "&gt;") == FALSE)
                               return (FALSE);
                       break;
               case '&':
                       if (_prop_object_externalize_append_cstring(ctx,
                                       "&amp;") == 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[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\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("<!--")) {
                       ctx->poic_cp = xml + 4;
                       if (_prop_object_internalize_skip_comment(ctx) == FALSE)
                               goto bad;
                       xml = ctx->poic_cp;
                       continue;
               }

#undef MATCH

               /*
                * We don't think we should skip it, so let's hope we can
                * parse it.
                */
               break;
       }

       ctx->poic_cp = xml;
       return (ctx);
bad:
       _PROP_FREE(ctx, M_TEMP);
       return (NULL);
}

/*
* _prop_object_internalize_context_free --
*      Free an internalize context.
*/
void
_prop_object_internalize_context_free(
               struct _prop_object_internalize_context *ctx)
{

       _PROP_FREE(ctx, M_TEMP);
}

static boolean_t
_prop_string_externalize(struct _prop_object_externalize_context *ctx,
                        void *v)
{
       prop_string_t ps = v;

       if (ps->ps_size == 0)
               return (_prop_object_externalize_empty_tag(ctx, "string"));

       if (_prop_object_externalize_start_tag(ctx, "string") == FALSE ||
           _prop_object_externalize_append_encoded_cstring(ctx,
                                               ps->ps_immutable) == FALSE ||
           _prop_object_externalize_end_tag(ctx, "string") == FALSE)
               return (FALSE);

       return (TRUE);
}

/*
* _prop_string_internalize --
*      Parse a <string>...</string> and return the object created from the
*      external representation.
*/
prop_object_t
_prop_string_internalize(struct _prop_object_internalize_context *ctx)
{
       prop_string_t string;
       char *str;
       size_t len, alen;

       if (ctx->poic_is_empty_element)
               return (prop_string_create());

       /* No attributes recognized here. */
       if (ctx->poic_tagattr != NULL)
               return (NULL);

       /* Compute the length of the result. */
       if (_prop_object_internalize_decode_string(ctx, NULL, 0, &len,
                                                  NULL) == FALSE)
               return (NULL);

       str = _PROP_MALLOC(len + 1, M_PROP_STRING);
       if (str == NULL)
               return (NULL);

       if (_prop_object_internalize_decode_string(ctx, str, len, &alen,
                                                  &ctx->poic_cp) == FALSE ||
           alen != len) {
               _PROP_FREE(str, M_PROP_STRING);
               return (NULL);
       }
       str[len] = '\0';

       if (_prop_object_internalize_find_tag(ctx, "string",
                                             _PROP_TAG_TYPE_END) == FALSE) {
               _PROP_FREE(str, M_PROP_STRING);
               return (NULL);
       }

       string = _prop_string_alloc();
       if (string == NULL) {
               _PROP_FREE(str, M_PROP_STRING);
               return (NULL);
       }

       string->ps_mutable = str;
       string->ps_size = len;

       return (string);
}

static boolean_t
_prop_object_externalize(struct _prop_object_externalize_context *ctx,
   prop_object_t o)
{
       switch (prop_object_type(o)) {
       case PROP_TYPE_BOOL:
               return _prop_bool_externalize(ctx, o);
       case PROP_TYPE_NUMBER:
               return _prop_number_externalize(ctx, o);
       case PROP_TYPE_STRING:
               return _prop_string_externalize(ctx, o);
       case PROP_TYPE_DATA:
               return _prop_data_externalize(ctx, o);
       case PROP_TYPE_ARRAY:
               return _prop_array_externalize(ctx, o);
       case PROP_TYPE_DICTIONARY:
               return _prop_dictionary_externalize(ctx, o);
       case PROP_TYPE_DICT_KEYSYM:
               return _prop_dict_keysym_externalize(ctx, o);
       default:
               return (FALSE);
       }
}