/*-
* Copyright (c) 2006, 2007, 2020, 2025 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.
*
* 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.
*/
/*
* We implement these like arrays, but we keep them sorted by key.
* This allows us to binary-search as well as keep externalized output
* sane-looking for human eyes.
*/
#define EXPAND_STEP 16
/*
* prop_dictionary_keysym_t is allocated with space at the end to hold the
* key. This must be a regular object so that we can maintain sane iterator
* semantics -- we don't want to require that the caller release the result
* of prop_object_iterator_next().
*
* We'd like to have some small'ish keysym objects for up-to-16 characters
* in a key, some for up-to-32 characters in a key, and then a final bucket
* for up-to-128 characters in a key (not including NUL). Keys longer than
* 128 characters are not allowed.
*/
struct _prop_dictionary_keysym {
struct _prop_object pdk_obj;
size_t pdk_size;
struct rb_node pdk_link;
char pdk_key[1];
/* actually variable length */
};
/* pdk_key[1] takes care of the NUL */
#define PDK_SIZE_16 (sizeof(struct _prop_dictionary_keysym) + 16)
#define PDK_SIZE_32 (sizeof(struct _prop_dictionary_keysym) + 32)
#define PDK_SIZE_128 (sizeof(struct _prop_dictionary_keysym) + 128)
/*
* Dictionary key symbols are immutable, and we are likely to have many
* duplicated key symbols. So, to save memory, we unique'ify key symbols
* so we only have to have one copy of each string.
*/
/*
* There is only ever one copy of a keysym at any given time,
* so we can reduce this to a simple pointer equality check.
*/
if (pdk1 == pdk2)
return _PROP_OBJECT_EQUALS_TRUE;
else
return _PROP_OBJECT_EQUALS_FALSE;
}
/*
* Check to see if this already exists in the tree. If it does,
* we just retain it and return it.
*/
_PROP_MUTEX_LOCK(_prop_dict_keysym_tree_mutex);
opdk = rb_tree_find_node(&_prop_dict_keysym_tree, key);
if (opdk != NULL) {
prop_object_retain(opdk);
_PROP_MUTEX_UNLOCK(_prop_dict_keysym_tree_mutex);
return (opdk);
}
_PROP_MUTEX_UNLOCK(_prop_dict_keysym_tree_mutex);
/*
* We dropped the mutex when we allocated the new object, so
* we have to check again if it is in the tree.
*/
_PROP_MUTEX_LOCK(_prop_dict_keysym_tree_mutex);
opdk = rb_tree_find_node(&_prop_dict_keysym_tree, key);
if (opdk != NULL) {
prop_object_retain(opdk);
_PROP_MUTEX_UNLOCK(_prop_dict_keysym_tree_mutex);
_prop_dict_keysym_put(pdk);
return (opdk);
}
rpdk = rb_tree_insert_node(&_prop_dict_keysym_tree, pdk);
_PROP_ASSERT(rpdk == pdk);
_PROP_MUTEX_UNLOCK(_prop_dict_keysym_tree_mutex);
return (rpdk);
}
/*
* prop_dictionary_create_with_capacity --
* Create a dictionary with the capacity to store N objects.
*/
_PROP_EXPORT prop_dictionary_t
prop_dictionary_create_with_capacity(unsigned int capacity)
{
return (_prop_dictionary_alloc(capacity));
}
/*
* prop_dictionary_copy --
* Copy a dictionary. The new dictionary has an initial capacity equal
* to the number of objects stored int the original dictionary. The new
* dictionary contains references to the original dictionary's objects,
* not copies of those objects (i.e. a shallow copy).
*/
_PROP_EXPORT prop_dictionary_t
prop_dictionary_copy(prop_dictionary_t opd)
{
prop_dictionary_t pd;
prop_dictionary_keysym_t pdk;
prop_object_t po;
unsigned int idx;
if (! prop_object_is_dictionary(opd))
return (NULL);
_PROP_RWLOCK_RDLOCK(opd->pd_rwlock);
pd = _prop_dictionary_alloc(opd->pd_count);
if (pd != NULL) {
for (idx = 0; idx < opd->pd_count; idx++) {
pdk = opd->pd_array[idx].pde_key;
po = opd->pd_array[idx].pde_objref;
/*
* prop_dictionary_copy_mutable --
* Like prop_dictionary_copy(), but the resulting dictionary is
* mutable.
*/
_PROP_EXPORT prop_dictionary_t
prop_dictionary_copy_mutable(prop_dictionary_t opd)
{
prop_dictionary_t pd;
if (! prop_object_is_dictionary(opd))
return (NULL);
pd = prop_dictionary_copy(opd);
if (pd != NULL)
pd->pd_flags &= ~PD_F_IMMUTABLE;
return (pd);
}
/*
* prop_dictionary_make_immutable --
* Set the immutable flag on that dictionary.
*/
_PROP_EXPORT void
prop_dictionary_make_immutable(prop_dictionary_t pd)
{
_PROP_RWLOCK_WRLOCK(pd->pd_rwlock);
if (prop_dictionary_is_immutable(pd) == false)
pd->pd_flags |= PD_F_IMMUTABLE;
_PROP_RWLOCK_UNLOCK(pd->pd_rwlock);
}
/*
* prop_dictionary_count --
* Return the number of objects stored in the dictionary.
*/
_PROP_EXPORT unsigned int
prop_dictionary_count(prop_dictionary_t pd)
{
unsigned int rv;
/*
* prop_dictionary_ensure_capacity --
* Ensure that the dictionary has the capacity to store the specified
* total number of objects (including the objects already stored in
* the dictionary).
*/
_PROP_EXPORT bool
prop_dictionary_ensure_capacity(prop_dictionary_t pd, unsigned int capacity)
{
bool rv;
if (! prop_object_is_dictionary(pd))
return (false);
/*
* prop_dictionary_iterator --
* Return an iterator for the dictionary. The dictionary is retained by
* the iterator.
*/
_PROP_EXPORT prop_object_iterator_t
prop_dictionary_iterator(prop_dictionary_t pd)
{
struct _prop_dictionary_iterator *pdi;
/*
* prop_dictionary_all_keys --
* Return an array containing a snapshot of all of the keys
* in the dictionary.
*/
_PROP_EXPORT prop_array_t
prop_dictionary_all_keys(prop_dictionary_t pd)
{
prop_array_t array;
unsigned int idx;
bool rv = true;
if (! prop_object_is_dictionary(pd))
return (NULL);
/* There is no pressing need to lock the dictionary for this. */
array = prop_array_create_with_capacity(pd->pd_count);
_PROP_RWLOCK_RDLOCK(pd->pd_rwlock);
for (idx = 0; idx < pd->pd_count; idx++) {
rv = prop_array_add(array, pd->pd_array[idx].pde_key);
if (rv == false)
break;
}
/*
* prop_dictionary_get_keysym --
* Return the object stored at the location encoded by the keysym.
*/
_PROP_EXPORT prop_object_t
prop_dictionary_get_keysym(prop_dictionary_t pd, prop_dictionary_keysym_t pdk)
{
/*
* prop_dictionary_set --
* Store a reference to an object at with the specified key.
* If the key already exist, the original object is released.
*/
_PROP_EXPORT bool
prop_dictionary_set(prop_dictionary_t pd, const char *key, prop_object_t po)
{
struct _prop_dict_entry *pde;
prop_dictionary_keysym_t pdk;
unsigned int idx;
bool rv = false;
if (! prop_object_is_dictionary(pd))
return (false);
_PROP_ASSERT(pd->pd_count <= pd->pd_capacity);
if (prop_dictionary_is_immutable(pd))
return (false);
if (strcmp(key, pde->pde_key->pdk_key) < 0) {
/*
* key < pdk_key: insert to the left. This is the same as
* inserting to the right, except we decrement the current
* index first.
*
* Because we're unsigned, we have to special case 0
* (grumble).
*/
if (idx == 0) {
memmove(&pd->pd_array[1], &pd->pd_array[0],
pd->pd_count * sizeof(*pde));
pd->pd_array[0].pde_key = pdk;
pd->pd_array[0].pde_objref = po;
pd->pd_count++;
pd->pd_version++;
rv = true;
goto out;
}
idx--;
}
/*
* prop_dictionary_set_keysym --
* Replace the object in the dictionary at the location encoded by
* the keysym.
*/
_PROP_EXPORT bool
prop_dictionary_set_keysym(prop_dictionary_t pd, prop_dictionary_keysym_t pdk,
prop_object_t po)
{
if (! (prop_object_is_dictionary(pd) &&
prop_object_is_dictionary_keysym(pdk)))
return (false);
/*
* prop_dictionary_remove --
* Remove the reference to an object with the specified key from
* the dictionary.
*/
_PROP_EXPORT void
prop_dictionary_remove(prop_dictionary_t pd, const char *key)
{
struct _prop_dict_entry *pde;
unsigned int idx;
if (! prop_object_is_dictionary(pd))
return;
_PROP_RWLOCK_WRLOCK(pd->pd_rwlock);
/* XXX Should this be a _PROP_ASSERT()? */
if (prop_dictionary_is_immutable(pd))
goto out;
pde = _prop_dict_lookup(pd, key, &idx);
/* XXX Should this be a _PROP_ASSERT()? */
if (pde == NULL)
goto out;
/*
* prop_dictionary_remove_keysym --
* Remove a reference to an object stored in the dictionary at the
* location encoded by the keysym.
*/
_PROP_EXPORT void
prop_dictionary_remove_keysym(prop_dictionary_t pd,
prop_dictionary_keysym_t pdk)
{
if (! (prop_object_is_dictionary(pd) &&
prop_object_is_dictionary_keysym(pdk)))
return;
prop_dictionary_remove(pd, pdk->pdk_key);
}
/*
* prop_dictionary_equals --
* Return true if the two dictionaries are equivalent. Note we do a
* by-value comparison of the objects in the dictionary.
*/
_PROP_EXPORT bool
prop_dictionary_equals(prop_dictionary_t dict1, prop_dictionary_t dict2)
{
if (!prop_object_is_dictionary(dict1) ||
!prop_object_is_dictionary(dict2))
return (false);
return (prop_object_equals(dict1, dict2));
}
/*
* prop_dictionary_keysym_value --
* Return a reference to the keysym's value.
*/
_PROP_EXPORT const char *
prop_dictionary_keysym_value(prop_dictionary_keysym_t pdk)
{
if (! prop_object_is_dictionary_keysym(pdk))
return (NULL);
return (pdk->pdk_key);
}
_PROP_DEPRECATED(prop_dictionary_keysym_cstring_nocopy,
"this program uses prop_dictionary_keysym_cstring_nocopy(), "
"which is deprecated; use prop_dictionary_keysym_value() instead.")
_PROP_EXPORT const char *
prop_dictionary_keysym_cstring_nocopy(prop_dictionary_keysym_t pdk)
{
if (! prop_object_is_dictionary_keysym(pdk))
return (NULL);
return (pdk->pdk_key);
}
/*
* prop_dictionary_keysym_equals --
* Return true if the two dictionary key symbols are equivalent.
* Note: We do not compare the object references.
*/
_PROP_EXPORT bool
prop_dictionary_keysym_equals(prop_dictionary_keysym_t pdk1,
prop_dictionary_keysym_t pdk2)
{
if (!prop_object_is_dictionary_keysym(pdk1) ||
!prop_object_is_dictionary_keysym(pdk2))
return (false);
return (prop_object_equals(pdk1, pdk2));
}
/*
* prop_dictionary_externalize --
* Externalize a dictionary in XML format.
*/
_PROP_EXPORT char *
prop_dictionary_externalize(prop_dictionary_t pd)
{
return _prop_object_externalize(&pd->pd_obj, PROP_FORMAT_XML);
}
/*
* _prop_dictionary_internalize --
* Parse a <dict>...</dict> and return the object created from the
* external representation.
*
* Internal state in via rec_data is the storage area for the last processed
* key.
* _prop_dictionary_internalize_body is the upper half of the parse loop.
* It is responsible for parsing the key directly and storing it in the area
* referenced by rec_data.
* _prop_dictionary_internalize_cont is the lower half and called with the value
* associated with the key.
*/
static bool _prop_dictionary_internalize_body(prop_stack_t,
prop_object_t *, struct _prop_object_internalize_context *, char *);
*obj = dict;
/*
* Opening tag is found, storage for key allocated and
* now continue to the first element.
*/
return _prop_dictionary_internalize_body(stack, obj, ctx, tmpkey);
}
/*
* key, value was added, now continue looking for the next key
* or the closing tag. For JSON, we'll skip the comma separator,
* if present.
*
* By doing this here, we correctly error out if a separator
* is found other than after an element, but this does mean
* that we do allow a trailing comma after the final element
* which isn't allowed in the JSON spec, but seems pretty
* harmless (and there are other JSON parsers that also allow
* it).
*
* Conversely, we don't want to *require* the separator if the
* spec doesn't require it, and we don't know what's next in
* the buffer, so we basically treat the separator as completely
* optional. Since there does not appear to be any ambiguity,
* this also seems pretty harmless.
*
* (FWIW, RFC 8259 section 9 seems to specifically allow this.)
*/
if (ctx->poic_format == PROP_FORMAT_JSON) {
ctx->poic_cp = _prop_intern_skip_whitespace(ctx->poic_cp);
if (*ctx->poic_cp == ',') {
ctx->poic_cp++;
}
}
return _prop_dictionary_internalize_body(stack, obj, ctx, tmpkey);
}
if (ctx->poic_format == PROP_FORMAT_JSON) {
ctx->poic_cp = _prop_intern_skip_whitespace(ctx->poic_cp);
/* Check to see if this is the end of the dictionary. */
if (*ctx->poic_cp == '}') {
/* It is, so don't iterate any further. */
ctx->poic_cp++;
return true;
}
/* It must be the key. */
if (*ctx->poic_cp != '"') {
goto bad;
}
ctx->poic_cp++;
/* Empty keys are not allowed. */
if (*ctx->poic_cp == '"') {
goto bad;
}
} else {
/* Fetch the next tag. */
if (_prop_xml_intern_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) {
_PROP_FREE(tmpkey, M_TEMP);
return (true);
}
/* 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_intern_decode_string(ctx, tmpkey, PDK_MAXKEY, &keylen,
&ctx->poic_cp) == false)
goto bad;
if (ctx->poic_format == PROP_FORMAT_JSON) {
if (*ctx->poic_cp != '"') {
goto bad;
}
ctx->poic_cp++;
/*
* Next thing we counter needs to be the key/value
* separator.
*/
ctx->poic_cp = _prop_intern_skip_whitespace(ctx->poic_cp);
if (*ctx->poic_cp != ':') {
goto bad;
}
ctx->poic_cp++;
} else {
if (_prop_xml_intern_find_tag(ctx, "key",
_PROP_TAG_TYPE_END) == false)
goto bad;
/* ..and now the beginning of the value. */
if (_prop_xml_intern_find_tag(ctx, NULL,
_PROP_TAG_TYPE_START) == false)
goto bad;
}
/*
* Key is found, now wait for value to be parsed.
*/
if (_prop_stack_push(stack, *obj,
_prop_dictionary_internalize_continue,
tmpkey, NULL))
return (false);