diff --git a/configure.ac b/configure.ac index 1f6e87d41ef0..de78e8b183aa 100644 --- a/configure.ac +++ b/configure.ac @@ -1,163 +1,163 @@ m4_define([maj_ver], [0]) m4_define([med_ver], [4]) -m4_define([min_ver], [0]) -m4_define([so_version], [maj_ver:med_ver]) +m4_define([min_ver], [1]) +m4_define([so_version], [1:0:0]) m4_define([ucl_version], [maj_ver.med_ver.min_ver]) AC_INIT([libucl],[ucl_version],[https://github.com/vstakhov/libucl],[libucl]) AC_CONFIG_SRCDIR([configure.ac]) AM_INIT_AUTOMAKE([1.11 foreign silent-rules -Wall -Wportability no-dist-gzip dist-xz]) UCL_VERSION=ucl_version SO_VERSION=so_version AC_SUBST(UCL_VERSION) AC_SUBST(SO_VERSION) AC_PROG_CC_C99 AM_PROG_CC_C_O AM_PROG_AR LT_INIT AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([config.h]) AC_C_CONST AC_TYPE_SIZE_T AC_CHECK_HEADERS_ONCE([fcntl.h unistd.h]) AC_TYPE_OFF_T AC_FUNC_MMAP AC_CHECK_HEADERS_ONCE([fcntl.h]) AC_CHECK_HEADERS_ONCE([sys/types.h]) AC_CHECK_HEADERS_ONCE([sys/stat.h]) AC_CHECK_HEADERS_ONCE([sys/param.h]) AC_CHECK_HEADERS_ONCE([sys/mman.h]) AC_CHECK_HEADERS_ONCE([stdlib.h]) AC_CHECK_HEADERS_ONCE([stddef.h]) AC_CHECK_HEADERS_ONCE([stdarg.h]) AC_CHECK_HEADERS_ONCE([stdbool.h]) AC_CHECK_HEADERS_ONCE([stdint.h]) AC_CHECK_HEADERS_ONCE([string.h]) AC_CHECK_HEADERS_ONCE([unistd.h]) AC_CHECK_HEADERS_ONCE([ctype.h]) AC_CHECK_HEADERS_ONCE([errno.h]) AC_CHECK_HEADERS_ONCE([limits.h]) AC_CHECK_HEADERS_ONCE([libgen.h]) AC_CHECK_HEADERS_ONCE([stdio.h]) AC_CHECK_HEADERS_ONCE([float.h]) AC_CHECK_HEADERS_ONCE([math.h]) dnl Example of default-disabled feature AC_ARG_ENABLE([urls], AS_HELP_STRING([--enable-urls], [Enable URLs fetch (requires libfetch or libcurl) @<:@default=no@:>@]), [], [enable_urls=no]) AC_ARG_ENABLE([regex], AS_HELP_STRING([--enable-regex], [Enable regex checking for schema @<:@default=yes@:>@]), [], [enable_regex=yes]) AC_ARG_ENABLE([signatures], AS_HELP_STRING([--enable-signatures], [Enable signatures check (requires openssl) @<:@default=no@:>@]), [], [enable_signatures=no]) AC_ARG_ENABLE([utils], AS_HELP_STRING([--enable-utils], [Build and install utils @<:@default=no@:>@]), [case "${enableval}" in yes) utils=true ;; no) utils=false ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-utils]) ;; esac],[utils=false]) AM_CONDITIONAL([UTILS], [test x$utils = xtrue]) AS_IF([test "x$enable_signatures" = "xyes"], [ AC_SEARCH_LIBS([EVP_MD_CTX_create], [crypto], [ AC_DEFINE(HAVE_OPENSSL, 1, [Define to 1 if you have the 'crypto' library (-lcrypto).]) LIBCRYPTO_LIB="-lcrypto" LIBS_EXTRA="${LIBS_EXTRA} -lcrypto" ], [AC_MSG_ERROR([unable to find the EVP_MD_CTX_create() function])]) ]) AC_SUBST(LIBCRYPTO_LIB) AC_PATH_PROG(PANDOC, pandoc, [/non/existent]) AC_SEARCH_LIBS([clock_gettime], [rt], [], [ AC_CHECK_HEADER([mach/mach_time.h], [ AC_DEFINE(HAVE_MACH_MACH_TIME_H, 1, [Define to 1 on Darwin]) ], [AC_MSG_ERROR([unable to find clock_gettime or mach_absolute_time])]) ]) AC_SEARCH_LIBS([remainder], [m], [], [AC_MSG_ERROR([unable to find remainder() function])]) AS_IF([test "x$enable_regex" = "xyes"], [ AC_CHECK_HEADER([regex.h], [ AC_DEFINE(HAVE_REGEX_H, 1, [Define to 1 if you have the header file.]) AC_SEARCH_LIBS([regexec], [regex], [ AS_IF([test "x$ac_cv_search_regexec" = "x-lregex"], [ LIBREGEX_LIB="-lregex" LIBS_EXTRA="${LIBS_EXTRA} -lregex" ] )], [AC_MSG_ERROR([unable to find the regexec() function])])], [AC_MSG_ERROR([unable to find the regex.h header]) ], [#include ]) ]) AC_SUBST(LIBREGEX_LIB) AS_IF([test "x$enable_urls" = "xyes"], [ AC_CHECK_HEADER([fetch.h], [ AC_DEFINE(HAVE_FETCH_H, 1, [Define to 1 if you have the header file.]) AC_CHECK_LIB(fetch, fetchXGet, [ AC_DEFINE(HAVE_LIBFETCH, 1, [Define to 1 if you have the 'fetch' library (-lfetch).]) LIBFETCH_LIBS="-lfetch" have_libfetch="yes" LIBS_EXTRA="${LIBS_EXTRA} -lfetch" ]) ], [],[ #include #ifdef HAVE_SYS_PARAM_H #include #endif ]) AC_SUBST(LIBFETCH_LIBS) AS_IF([ test "x$have_libfetch" != "xyes"], [ dnl Fallback to libcurl PKG_CHECK_MODULES([CURL], [libcurl], [ AC_DEFINE(CURL_FOUND, 1, [Use libcurl]) LIBS_EXTRA="${LIBS_EXTRA} -lcurl"], [AC_MSG_ERROR([unable to find neither libfetch nor libcurl])]) ]) AC_SUBST(CURL_FOUND) AC_SUBST(CURL_LIBS) AC_SUBST(CURL_CFLAGS) ]) AC_SUBST(LIBS_EXTRA) AC_MSG_CHECKING(for GCC atomic builtins) AC_LINK_IFELSE([ AC_LANG_SOURCE([[ int main() { volatile unsigned long val = 1; __sync_synchronize(); __sync_val_compare_and_swap(&val, 1, 0); __sync_add_and_fetch(&val, 1); __sync_sub_and_fetch(&val, 1); return 0; } ]]) ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_ATOMIC_BUILTINS], [1], [Has gcc/MSVC atomic intrinsics]) ], [ AC_MSG_RESULT([no]) AC_DEFINE([HAVE_ATOMIC_BUILTINS], [0], [Has gcc/MSVC atomic intrinsics]) AC_MSG_WARN([Libucl references could be thread-unsafe because atomic builtins are missing]) ]) AC_CONFIG_FILES(Makefile \ src/Makefile \ tests/Makefile \ utils/Makefile \ doc/Makefile \ libucl.pc) AC_CONFIG_FILES([stamp-h], [echo timestamp > stamp-h]) AC_OUTPUT diff --git a/include/ucl.h b/include/ucl.h index 08039b29001e..3eeea9a7df0e 100644 --- a/include/ucl.h +++ b/include/ucl.h @@ -1,847 +1,893 @@ /* Copyright (c) 2013, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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. */ #ifndef UCL_H_ #define UCL_H_ #include #include #include #include #include #include #include #ifdef _WIN32 # define UCL_EXTERN __declspec(dllexport) #else # define UCL_EXTERN #endif /** * @mainpage * This is a reference manual for UCL API. You may find the description of UCL format by following this * [github repository](https://github.com/vstakhov/libucl). * * This manual has several main sections: * - @ref structures * - @ref utils * - @ref parser * - @ref emitter */ /** * @file ucl.h * @brief UCL parsing and emitting functions * * UCL is universal configuration language, which is a form of * JSON with less strict rules that make it more comfortable for * using as a configuration language */ #ifdef __cplusplus extern "C" { #endif /* * Memory allocation utilities * UCL_ALLOC(size) - allocate memory for UCL * UCL_FREE(size, ptr) - free memory of specified size at ptr * Default: malloc and free */ #ifndef UCL_ALLOC #define UCL_ALLOC(size) malloc(size) #endif #ifndef UCL_FREE #define UCL_FREE(size, ptr) free(ptr) #endif #if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) #define UCL_WARN_UNUSED_RESULT \ __attribute__((warn_unused_result)) #else #define UCL_WARN_UNUSED_RESULT #endif #ifdef __GNUC__ #define UCL_DEPRECATED(func) func __attribute__ ((deprecated)) #elif defined(_MSC_VER) #define UCL_DEPRECATED(func) __declspec(deprecated) func #else #define UCL_DEPRECATED(func) func #endif /** * @defgroup structures Structures and types * UCL defines several enumeration types used for error reporting or specifying flags and attributes. * * @{ */ /** * The common error codes returned by ucl parser */ typedef enum ucl_error { UCL_EOK = 0, /**< No error */ UCL_ESYNTAX, /**< Syntax error occurred during parsing */ UCL_EIO, /**< IO error occurred during parsing */ UCL_ESTATE, /**< Invalid state machine state */ UCL_ENESTED, /**< Input has too many recursion levels */ UCL_EMACRO, /**< Error processing a macro */ UCL_EINTERNAL, /**< Internal unclassified error */ UCL_ESSL /**< SSL error */ } ucl_error_t; /** * #ucl_object_t may have one of specified types, some types are compatible with each other and some are not. * For example, you can always convert #UCL_TIME to #UCL_FLOAT. Also you can convert #UCL_FLOAT to #UCL_INTEGER * by loosing floating point. Every object may be converted to a string by #ucl_object_tostring_forced() function. * */ typedef enum ucl_type { UCL_OBJECT = 0, /**< UCL object - key/value pairs */ UCL_ARRAY, /**< UCL array */ UCL_INT, /**< Integer number */ UCL_FLOAT, /**< Floating point number */ UCL_STRING, /**< Null terminated string */ UCL_BOOLEAN, /**< Boolean value */ UCL_TIME, /**< Time value (floating point number of seconds) */ UCL_USERDATA, /**< Opaque userdata pointer (may be used in macros) */ UCL_NULL /**< Null value */ } ucl_type_t; /** * You can use one of these types to serialise #ucl_object_t by using ucl_object_emit(). */ typedef enum ucl_emitter { UCL_EMIT_JSON = 0, /**< Emit fine formatted JSON */ UCL_EMIT_JSON_COMPACT, /**< Emit compacted JSON */ UCL_EMIT_CONFIG, /**< Emit human readable config format */ UCL_EMIT_YAML /**< Emit embedded YAML format */ } ucl_emitter_t; /** * These flags defines parser behaviour. If you specify #UCL_PARSER_ZEROCOPY you must ensure * that the input memory is not freed if an object is in use. Moreover, if you want to use * zero-terminated keys and string values then you should not use zero-copy mode, as in this case * UCL still has to perform copying implicitly. */ typedef enum ucl_parser_flags { UCL_PARSER_KEY_LOWERCASE = 0x1, /**< Convert all keys to lower case */ UCL_PARSER_ZEROCOPY = 0x2, /**< Parse input in zero-copy mode if possible */ UCL_PARSER_NO_TIME = 0x4 /**< Do not parse time and treat time values as strings */ } ucl_parser_flags_t; /** * String conversion flags, that are used in #ucl_object_fromstring_common function. */ typedef enum ucl_string_flags { UCL_STRING_ESCAPE = 0x1, /**< Perform JSON escape */ UCL_STRING_TRIM = 0x2, /**< Trim leading and trailing whitespaces */ UCL_STRING_PARSE_BOOLEAN = 0x4, /**< Parse passed string and detect boolean */ UCL_STRING_PARSE_INT = 0x8, /**< Parse passed string and detect integer number */ UCL_STRING_PARSE_DOUBLE = 0x10, /**< Parse passed string and detect integer or float number */ UCL_STRING_PARSE_TIME = 0x20, /**< Parse time strings */ UCL_STRING_PARSE_NUMBER = UCL_STRING_PARSE_INT|UCL_STRING_PARSE_DOUBLE|UCL_STRING_PARSE_TIME, /**< Parse passed string and detect number */ UCL_STRING_PARSE = UCL_STRING_PARSE_BOOLEAN|UCL_STRING_PARSE_NUMBER, /**< Parse passed string (and detect booleans and numbers) */ UCL_STRING_PARSE_BYTES = 0x40 /**< Treat numbers as bytes */ } ucl_string_flags_t; /** * Basic flags for an object */ typedef enum ucl_object_flags { UCL_OBJECT_ALLOCATED_KEY = 1, /**< An object has key allocated internally */ UCL_OBJECT_ALLOCATED_VALUE = 2, /**< An object has a string value allocated internally */ UCL_OBJECT_NEED_KEY_ESCAPE = 4 /**< The key of an object need to be escaped on output */ } ucl_object_flags_t; /** * UCL object structure. Please mention that the most of fields should not be touched by * UCL users. In future, this structure may be converted to private one. */ typedef struct ucl_object_s { /** * Variant value type */ union { int64_t iv; /**< Int value of an object */ const char *sv; /**< String value of an object */ double dv; /**< Double value of an object */ struct ucl_object_s *av; /**< Array */ void *ov; /**< Object */ void* ud; /**< Opaque user data */ } value; const char *key; /**< Key of an object */ struct ucl_object_s *next; /**< Array handle */ struct ucl_object_s *prev; /**< Array handle */ unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */ unsigned keylen; /**< Lenght of a key */ unsigned len; /**< Size of an object */ enum ucl_type type; /**< Real type */ uint16_t ref; /**< Reference count */ uint16_t flags; /**< Object flags */ } ucl_object_t; /** @} */ /** * @defgroup utils Utility functions * A number of utility functions simplify handling of UCL objects * * @{ */ /** * Copy and return a key of an object, returned key is zero-terminated * @param obj CL object * @return zero terminated key */ UCL_EXTERN char* ucl_copy_key_trash (const ucl_object_t *obj); /** * Copy and return a string value of an object, returned key is zero-terminated * @param obj CL object * @return zero terminated string representation of object value */ UCL_EXTERN char* ucl_copy_value_trash (const ucl_object_t *obj); /** * Creates a new object * @return new object */ UCL_EXTERN ucl_object_t* ucl_object_new (void) UCL_WARN_UNUSED_RESULT; /** * Create new object with type specified * @param type type of a new object * @return new object */ -UCL_EXTERN ucl_object_t* ucl_object_typed_new (unsigned int type) UCL_WARN_UNUSED_RESULT; +UCL_EXTERN ucl_object_t* ucl_object_typed_new (ucl_type_t type) UCL_WARN_UNUSED_RESULT; + +/** + * Return the type of an object + * @return the object type + */ +UCL_EXTERN ucl_type_t ucl_object_type (const ucl_object_t *obj); /** * Convert any string to an ucl object making the specified transformations * @param str fixed size or NULL terminated string * @param len length (if len is zero, than str is treated as NULL terminated) * @param flags conversion flags * @return new object */ UCL_EXTERN ucl_object_t * ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags flags) UCL_WARN_UNUSED_RESULT; /** * Create a UCL object from the specified string * @param str NULL terminated string, will be json escaped * @return new object */ UCL_EXTERN ucl_object_t *ucl_object_fromstring (const char *str) UCL_WARN_UNUSED_RESULT; /** * Create a UCL object from the specified string * @param str fixed size string, will be json escaped * @param len length of a string * @return new object */ UCL_EXTERN ucl_object_t *ucl_object_fromlstring (const char *str, size_t len) UCL_WARN_UNUSED_RESULT; /** * Create an object from an integer number * @param iv number * @return new object */ UCL_EXTERN ucl_object_t* ucl_object_fromint (int64_t iv) UCL_WARN_UNUSED_RESULT; /** * Create an object from a float number * @param dv number * @return new object */ UCL_EXTERN ucl_object_t* ucl_object_fromdouble (double dv) UCL_WARN_UNUSED_RESULT; /** * Create an object from a boolean * @param bv bool value * @return new object */ UCL_EXTERN ucl_object_t* ucl_object_frombool (bool bv) UCL_WARN_UNUSED_RESULT; /** * Insert a object 'elt' to the hash 'top' and associate it with key 'key' * @param top destination object (will be created automatically if top is NULL) * @param elt element to insert (must NOT be NULL) * @param key key to associate with this object (either const or preallocated) * @param keylen length of the key (or 0 for NULL terminated keys) * @param copy_key make an internal copy of key * @return true if key has been inserted */ UCL_EXTERN bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key); /** * Replace a object 'elt' to the hash 'top' and associate it with key 'key', old object will be unrefed, * if no object has been found this function works like ucl_object_insert_key() * @param top destination object (will be created automatically if top is NULL) * @param elt element to insert (must NOT be NULL) * @param key key to associate with this object (either const or preallocated) * @param keylen length of the key (or 0 for NULL terminated keys) * @param copy_key make an internal copy of key * @return true if key has been inserted */ UCL_EXTERN bool ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key); /** * Delete a object associated with key 'key', old object will be unrefered, * @param top object * @param key key associated to the object to remove * @param keylen length of the key (or 0 for NULL terminated keys) */ UCL_EXTERN bool ucl_object_delete_keyl (ucl_object_t *top, const char *key, size_t keylen); /** * Delete a object associated with key 'key', old object will be unrefered, * @param top object * @param key key associated to the object to remove */ UCL_EXTERN bool ucl_object_delete_key (ucl_object_t *top, const char *key); /** * Delete key from `top` object returning the object deleted. This object is not * released * @param top object * @param key key to remove * @param keylen length of the key (or 0 for NULL terminated keys) * @return removed object or NULL if object has not been found */ UCL_EXTERN ucl_object_t* ucl_object_pop_keyl (ucl_object_t *top, const char *key, size_t keylen) UCL_WARN_UNUSED_RESULT; /** * Delete key from `top` object returning the object deleted. This object is not * released * @param top object * @param key key to remove * @return removed object or NULL if object has not been found */ UCL_EXTERN ucl_object_t* ucl_object_pop_key (ucl_object_t *top, const char *key) UCL_WARN_UNUSED_RESULT; /** * Insert a object 'elt' to the hash 'top' and associate it with key 'key', if the specified key exist, * try to merge its content * @param top destination object (will be created automatically if top is NULL) * @param elt element to insert (must NOT be NULL) * @param key key to associate with this object (either const or preallocated) * @param keylen length of the key (or 0 for NULL terminated keys) * @param copy_key make an internal copy of key * @return true if key has been inserted */ UCL_EXTERN bool ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key); /** * Append an element to the front of array object * @param top destination object (will be created automatically if top is NULL) * @param elt element to append (must NOT be NULL) * @return true if value has been inserted */ UCL_EXTERN bool ucl_array_append (ucl_object_t *top, ucl_object_t *elt); /** * Append an element to the start of array object * @param top destination object (will be created automatically if top is NULL) * @param elt element to append (must NOT be NULL) * @return true if value has been inserted */ UCL_EXTERN bool ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt); /** * Removes an element `elt` from the array `top`. Caller must unref the returned object when it is not * needed. * @param top array ucl object * @param elt element to remove * @return removed element or NULL if `top` is NULL or not an array */ UCL_EXTERN ucl_object_t* ucl_array_delete (ucl_object_t *top, ucl_object_t *elt); /** * Returns the first element of the array `top` * @param top array ucl object * @return element or NULL if `top` is NULL or not an array */ UCL_EXTERN const ucl_object_t* ucl_array_head (const ucl_object_t *top); /** * Returns the last element of the array `top` * @param top array ucl object * @return element or NULL if `top` is NULL or not an array */ UCL_EXTERN const ucl_object_t* ucl_array_tail (const ucl_object_t *top); /** * Removes the last element from the array `top`. Caller must unref the returned object when it is not * needed. * @param top array ucl object * @return removed element or NULL if `top` is NULL or not an array */ UCL_EXTERN ucl_object_t* ucl_array_pop_last (ucl_object_t *top); +/** + * Return object identified by an index of the array `top` + * @param obj object to get a key from (must be of type UCL_ARRAY) + * @param index index to return + * @return object at the specified index or NULL if index is not found + */ +UCL_EXTERN const ucl_object_t* ucl_array_find_index (const ucl_object_t *top, + unsigned int index); + /** * Removes the first element from the array `top`. Caller must unref the returned object when it is not * needed. * @param top array ucl object * @return removed element or NULL if `top` is NULL or not an array */ UCL_EXTERN ucl_object_t* ucl_array_pop_first (ucl_object_t *top); /** * Append a element to another element forming an implicit array * @param head head to append (may be NULL) * @param elt new element * @return true if element has been inserted */ UCL_EXTERN ucl_object_t * ucl_elt_append (ucl_object_t *head, ucl_object_t *elt); /** * Converts an object to double value * @param obj CL object * @param target target double variable * @return true if conversion was successful */ UCL_EXTERN bool ucl_object_todouble_safe (const ucl_object_t *obj, double *target); /** * Unsafe version of \ref ucl_obj_todouble_safe * @param obj CL object * @return double value */ UCL_EXTERN double ucl_object_todouble (const ucl_object_t *obj); /** * Converts an object to integer value * @param obj CL object * @param target target integer variable * @return true if conversion was successful */ UCL_EXTERN bool ucl_object_toint_safe (const ucl_object_t *obj, int64_t *target); /** * Unsafe version of \ref ucl_obj_toint_safe * @param obj CL object * @return int value */ UCL_EXTERN int64_t ucl_object_toint (const ucl_object_t *obj); /** * Converts an object to boolean value * @param obj CL object * @param target target boolean variable * @return true if conversion was successful */ UCL_EXTERN bool ucl_object_toboolean_safe (const ucl_object_t *obj, bool *target); /** * Unsafe version of \ref ucl_obj_toboolean_safe * @param obj CL object * @return boolean value */ UCL_EXTERN bool ucl_object_toboolean (const ucl_object_t *obj); /** * Converts an object to string value * @param obj CL object * @param target target string variable, no need to free value * @return true if conversion was successful */ UCL_EXTERN bool ucl_object_tostring_safe (const ucl_object_t *obj, const char **target); /** * Unsafe version of \ref ucl_obj_tostring_safe * @param obj CL object * @return string value */ UCL_EXTERN const char* ucl_object_tostring (const ucl_object_t *obj); /** * Convert any object to a string in JSON notation if needed * @param obj CL object * @return string value */ UCL_EXTERN const char* ucl_object_tostring_forced (const ucl_object_t *obj); /** * Return string as char * and len, string may be not zero terminated, more efficient that \ref ucl_obj_tostring as it * allows zero-copy (if #UCL_PARSER_ZEROCOPY has been used during parsing) * @param obj CL object * @param target target string variable, no need to free value * @param tlen target length * @return true if conversion was successful */ UCL_EXTERN bool ucl_object_tolstring_safe (const ucl_object_t *obj, const char **target, size_t *tlen); /** * Unsafe version of \ref ucl_obj_tolstring_safe * @param obj CL object * @return string value */ UCL_EXTERN const char* ucl_object_tolstring (const ucl_object_t *obj, size_t *tlen); /** * Return object identified by a key in the specified object * @param obj object to get a key from (must be of type UCL_OBJECT) * @param key key to search * @return object matched the specified key or NULL if key is not found */ UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj, const char *key); /** * Return object identified by a fixed size key in the specified object * @param obj object to get a key from (must be of type UCL_OBJECT) * @param key key to search * @param klen length of a key * @return object matched the specified key or NULL if key is not found */ UCL_EXTERN const ucl_object_t* ucl_object_find_keyl (const ucl_object_t *obj, const char *key, size_t klen); +/** + * Return object identified by dot notation string + * @param obj object to search in + * @param path dot.notation.path to the path to lookup. May use numeric .index on arrays + * @return object matched the specified path or NULL if path is not found + */ +UCL_EXTERN const ucl_object_t *ucl_lookup_path (const ucl_object_t *obj, + const char *path); + /** * Returns a key of an object as a NULL terminated string * @param obj CL object * @return key or NULL if there is no key */ UCL_EXTERN const char* ucl_object_key (const ucl_object_t *obj); /** * Returns a key of an object as a fixed size string (may be more efficient) * @param obj CL object * @param len target key length * @return key pointer */ UCL_EXTERN const char* ucl_object_keyl (const ucl_object_t *obj, size_t *len); /** * Increase reference count for an object * @param obj object to ref */ UCL_EXTERN ucl_object_t* ucl_object_ref (const ucl_object_t *obj); /** * Free ucl object * @param obj ucl object to free */ UCL_DEPRECATED(UCL_EXTERN void ucl_object_free (ucl_object_t *obj)); /** * Decrease reference count for an object * @param obj object to unref */ UCL_EXTERN void ucl_object_unref (ucl_object_t *obj); /** * Compare objects `o1` and `o2` * @param o1 the first object * @param o2 the second object * @return values >0, 0 and <0 if `o1` is more than, equal and less than `o2`. * The order of comparison: * 1) Type of objects * 2) Size of objects * 3) Content of objects */ UCL_EXTERN int ucl_object_compare (const ucl_object_t *o1, const ucl_object_t *o2); /** * Sort UCL array using `cmp` compare function * @param ar * @param cmp */ UCL_EXTERN void ucl_object_array_sort (ucl_object_t *ar, int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2)); /** * Opaque iterator object */ typedef void* ucl_object_iter_t; /** * Get next key from an object * @param obj object to iterate * @param iter opaque iterator, must be set to NULL on the first call: * ucl_object_iter_t it = NULL; * while ((cur = ucl_iterate_object (obj, &it)) != NULL) ... * @return the next object or NULL */ UCL_EXTERN const ucl_object_t* ucl_iterate_object (const ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_values); /** @} */ /** * @defgroup parser Parsing functions * These functions are used to parse UCL objects * * @{ */ /** * Macro handler for a parser * @param data the content of macro * @param len the length of content * @param ud opaque user data * @param err error pointer * @return true if macro has been parsed */ typedef bool (*ucl_macro_handler) (const unsigned char *data, size_t len, void* ud); /* Opaque parser */ struct ucl_parser; /** * Creates new parser object * @param pool pool to allocate memory from * @return new parser object */ UCL_EXTERN struct ucl_parser* ucl_parser_new (int flags); /** * Register new handler for a macro * @param parser parser object * @param macro macro name (without leading dot) * @param handler handler (it is called immediately after macro is parsed) * @param ud opaque user data for a handler */ UCL_EXTERN void ucl_parser_register_macro (struct ucl_parser *parser, const char *macro, ucl_macro_handler handler, void* ud); +/** + * Handler to detect unregistered variables + * @param data variable data + * @param len length of variable + * @param replace (out) replace value for variable + * @param replace_len (out) replace length for variable + * @param need_free (out) UCL will free `dest` after usage + * @param ud opaque userdata + * @return true if variable + */ +typedef bool (*ucl_variable_handler) (const unsigned char *data, size_t len, + unsigned char **replace, size_t *replace_len, bool *need_free, void* ud); + /** * Register new parser variable * @param parser parser object * @param var variable name * @param value variable value */ UCL_EXTERN void ucl_parser_register_variable (struct ucl_parser *parser, const char *var, const char *value); +/** + * Set handler for unknown variables + * @param parser parser structure + * @param handler desired handler + * @param ud opaque data for the handler + */ +UCL_EXTERN void ucl_parser_set_variables_handler (struct ucl_parser *parser, + ucl_variable_handler handler, void *ud); + /** * Load new chunk to a parser * @param parser parser structure * @param data the pointer to the beginning of a chunk * @param len the length of a chunk * @param err if *err is NULL it is set to parser error * @return true if chunk has been added and false in case of error */ UCL_EXTERN bool ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data, size_t len); /** * Load ucl object from a string * @param parser parser structure * @param data the pointer to the string * @param len the length of the string, if `len` is 0 then `data` must be zero-terminated string * @return true if string has been added and false in case of error */ UCL_EXTERN bool ucl_parser_add_string (struct ucl_parser *parser, const char *data,size_t len); /** * Load and add data from a file * @param parser parser structure * @param filename the name of file * @param err if *err is NULL it is set to parser error * @return true if chunk has been added and false in case of error */ UCL_EXTERN bool ucl_parser_add_file (struct ucl_parser *parser, const char *filename); /** * Load and add data from a file descriptor * @param parser parser structure * @param filename the name of file * @param err if *err is NULL it is set to parser error * @return true if chunk has been added and false in case of error */ UCL_EXTERN bool ucl_parser_add_fd (struct ucl_parser *parser, int fd); /** * Get a top object for a parser (refcount is increased) * @param parser parser structure * @param err if *err is NULL it is set to parser error * @return top parser object or NULL */ UCL_EXTERN ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser); /** * Get the error string if failing * @param parser parser object */ UCL_EXTERN const char *ucl_parser_get_error(struct ucl_parser *parser); /** * Free ucl parser object * @param parser parser object */ UCL_EXTERN void ucl_parser_free (struct ucl_parser *parser); /** * Add new public key to parser for signatures check * @param parser parser object * @param key PEM representation of a key * @param len length of the key * @param err if *err is NULL it is set to parser error * @return true if a key has been successfully added */ UCL_EXTERN bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len); /** * Set FILENAME and CURDIR variables in parser * @param parser parser object * @param filename filename to set or NULL to set FILENAME to "undef" and CURDIR to getcwd() * @param need_expand perform realpath() if this variable is true and filename is not NULL * @return true if variables has been set */ UCL_EXTERN bool ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool need_expand); /** @} */ /** * @defgroup emitter Emitting functions * These functions are used to serialise UCL objects to some string representation. * * @{ */ /** * Structure using for emitter callbacks */ struct ucl_emitter_functions { /** Append a single character */ int (*ucl_emitter_append_character) (unsigned char c, size_t nchars, void *ud); /** Append a string of a specified length */ int (*ucl_emitter_append_len) (unsigned const char *str, size_t len, void *ud); /** Append a 64 bit integer */ int (*ucl_emitter_append_int) (int64_t elt, void *ud); /** Append floating point element */ int (*ucl_emitter_append_double) (double elt, void *ud); /** Opaque userdata pointer */ void *ud; }; /** * Emit object to a string * @param obj object * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is * #UCL_EMIT_CONFIG then emit config like object * @return dump of an object (must be freed after using) or NULL in case of error */ UCL_EXTERN unsigned char *ucl_object_emit (const ucl_object_t *obj, enum ucl_emitter emit_type); /** * Emit object to a string * @param obj object * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is * #UCL_EMIT_CONFIG then emit config like object * @return dump of an object (must be freed after using) or NULL in case of error */ UCL_EXTERN bool ucl_object_emit_full (const ucl_object_t *obj, enum ucl_emitter emit_type, struct ucl_emitter_functions *emitter); /** @} */ /** * @defgroup schema Schema functions * These functions are used to validate UCL objects using json schema format * * @{ */ /** * Used to define UCL schema error */ enum ucl_schema_error_code { UCL_SCHEMA_OK = 0, /**< no error */ UCL_SCHEMA_TYPE_MISMATCH, /**< type of object is incorrect */ UCL_SCHEMA_INVALID_SCHEMA, /**< schema is invalid */ UCL_SCHEMA_MISSING_PROPERTY,/**< one or more missing properties */ UCL_SCHEMA_CONSTRAINT, /**< constraint found */ UCL_SCHEMA_MISSING_DEPENDENCY, /**< missing dependency */ UCL_SCHEMA_UNKNOWN /**< generic error */ }; /** * Generic ucl schema error */ struct ucl_schema_error { enum ucl_schema_error_code code; /**< error code */ char msg[128]; /**< error message */ const ucl_object_t *obj; /**< object where error occured */ }; /** * Validate object `obj` using schema object `schema`. * @param schema schema object * @param obj object to validate * @param err error pointer, if this parameter is not NULL and error has been * occured, then `err` is filled with the exact error definition. * @return true if `obj` is valid using `schema` */ UCL_EXTERN bool ucl_object_validate (const ucl_object_t *schema, const ucl_object_t *obj, struct ucl_schema_error *err); /** @} */ #ifdef __cplusplus } #endif /* * XXX: Poorly named API functions, need to replace them with the appropriate * named function. All API functions *must* use naming ucl_object_*. Usage of * ucl_obj* should be avoided. */ #define ucl_obj_todouble_safe ucl_object_todouble_safe #define ucl_obj_todouble ucl_object_todouble #define ucl_obj_tostring ucl_object_tostring #define ucl_obj_tostring_safe ucl_object_tostring_safe #define ucl_obj_tolstring ucl_object_tolstring #define ucl_obj_tolstring_safe ucl_object_tolstring_safe #define ucl_obj_toint ucl_object_toint #define ucl_obj_toint_safe ucl_object_toint_safe #define ucl_obj_toboolean ucl_object_toboolean #define ucl_obj_toboolean_safe ucl_object_toboolean_safe #define ucl_obj_get_key ucl_object_find_key #define ucl_obj_get_keyl ucl_object_find_keyl #define ucl_obj_unref ucl_object_unref #define ucl_obj_ref ucl_object_ref #define ucl_obj_free ucl_object_free #endif /* UCL_H_ */ diff --git a/src/ucl_internal.h b/src/ucl_internal.h index 9a35dcec40be..0e3ecd012cc7 100644 --- a/src/ucl_internal.h +++ b/src/ucl_internal.h @@ -1,350 +1,352 @@ /* Copyright (c) 2013, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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. */ #ifndef UCL_INTERNAL_H_ #define UCL_INTERNAL_H_ #ifdef HAVE_CONFIG_H #include "config.h" #else /* Help embedded builds */ #define HAVE_SYS_TYPES_H #define HAVE_SYS_MMAN_H #define HAVE_SYS_STAT_H #define HAVE_SYS_PARAM_H #define HAVE_LIMITS_H #define HAVE_FCNTL_H #define HAVE_ERRNO_H #define HAVE_UNISTD_H #define HAVE_CTYPE_H #define HAVE_STDIO_H #define HAVE_STRING_H #define HAVE_FLOAT_H #define HAVE_LIBGEN_H #define HAVE_MATH_H #define HAVE_STDBOOL_H #define HAVE_STDINT_H #define HAVE_STDARG_H #ifndef _WIN32 # define HAVE_REGEX_H #endif #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_MMAN_H # ifndef _WIN32 # include # endif #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_CTYPE_H #include #endif #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_STRING_H #include #endif #include "utlist.h" #include "utstring.h" #include "uthash.h" #include "ucl.h" #include "ucl_hash.h" #include "xxhash.h" #ifdef HAVE_OPENSSL #include #endif #ifndef __DECONST #define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif /** * @file rcl_internal.h * Internal structures and functions of UCL library */ #define UCL_MAX_RECURSION 16 #define UCL_TRASH_KEY 0 #define UCL_TRASH_VALUE 1 enum ucl_parser_state { UCL_STATE_INIT = 0, UCL_STATE_OBJECT, UCL_STATE_ARRAY, UCL_STATE_KEY, UCL_STATE_VALUE, UCL_STATE_AFTER_VALUE, UCL_STATE_ARRAY_VALUE, UCL_STATE_SCOMMENT, UCL_STATE_MCOMMENT, UCL_STATE_MACRO_NAME, UCL_STATE_MACRO, UCL_STATE_ERROR }; enum ucl_character_type { UCL_CHARACTER_DENIED = 0, UCL_CHARACTER_KEY = 1, UCL_CHARACTER_KEY_START = 1 << 1, UCL_CHARACTER_WHITESPACE = 1 << 2, UCL_CHARACTER_WHITESPACE_UNSAFE = 1 << 3, UCL_CHARACTER_VALUE_END = 1 << 4, UCL_CHARACTER_VALUE_STR = 1 << 5, UCL_CHARACTER_VALUE_DIGIT = 1 << 6, UCL_CHARACTER_VALUE_DIGIT_START = 1 << 7, UCL_CHARACTER_ESCAPE = 1 << 8, UCL_CHARACTER_KEY_SEP = 1 << 9, UCL_CHARACTER_JSON_UNSAFE = 1 << 10, UCL_CHARACTER_UCL_UNSAFE = 1 << 11 }; struct ucl_macro { char *name; ucl_macro_handler handler; void* ud; UT_hash_handle hh; }; struct ucl_stack { ucl_object_t *obj; struct ucl_stack *next; int level; }; struct ucl_chunk { const unsigned char *begin; const unsigned char *end; const unsigned char *pos; size_t remain; unsigned int line; unsigned int column; struct ucl_chunk *next; }; #ifdef HAVE_OPENSSL struct ucl_pubkey { EVP_PKEY *key; struct ucl_pubkey *next; }; #else struct ucl_pubkey { struct ucl_pubkey *next; }; #endif struct ucl_variable { char *var; char *value; size_t var_len; size_t value_len; struct ucl_variable *next; }; struct ucl_parser { enum ucl_parser_state state; enum ucl_parser_state prev_state; unsigned int recursion; int flags; ucl_object_t *top_obj; ucl_object_t *cur_obj; struct ucl_macro *macroes; struct ucl_stack *stack; struct ucl_chunk *chunks; struct ucl_pubkey *keys; struct ucl_variable *variables; + ucl_variable_handler var_handler; + void *var_data; UT_string *err; }; /** * Unescape json string inplace * @param str */ size_t ucl_unescape_json_string (char *str, size_t len); /** * Handle include macro * @param data include data * @param len length of data * @param ud user data * @param err error ptr * @return */ bool ucl_include_handler (const unsigned char *data, size_t len, void* ud); bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud); /** * Handle includes macro * @param data include data * @param len length of data * @param ud user data * @param err error ptr * @return */ bool ucl_includes_handler (const unsigned char *data, size_t len, void* ud); size_t ucl_strlcpy (char *dst, const char *src, size_t siz); size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz); size_t ucl_strlcpy_tolower (char *dst, const char *src, size_t siz); #ifdef __GNUC__ static inline void ucl_create_err (UT_string **err, const char *fmt, ...) __attribute__ (( format( printf, 2, 3) )); #endif static inline void ucl_create_err (UT_string **err, const char *fmt, ...) { if (*err == NULL) { utstring_new (*err); va_list ap; va_start (ap, fmt); utstring_printf_va (*err, fmt, ap); va_end (ap); } } /** * Check whether a given string contains a boolean value * @param obj object to set * @param start start of a string * @param len length of a string * @return true if a string is a boolean value */ static inline bool ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t len) { const unsigned char *p = start; bool ret = false, val = false; if (len == 5) { if ((p[0] == 'f' || p[0] == 'F') && strncasecmp (p, "false", 5) == 0) { ret = true; val = false; } } else if (len == 4) { if ((p[0] == 't' || p[0] == 'T') && strncasecmp (p, "true", 4) == 0) { ret = true; val = true; } } else if (len == 3) { if ((p[0] == 'y' || p[0] == 'Y') && strncasecmp (p, "yes", 3) == 0) { ret = true; val = true; } else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "off", 3) == 0) { ret = true; val = false; } } else if (len == 2) { if ((p[0] == 'n' || p[0] == 'N') && strncasecmp (p, "no", 2) == 0) { ret = true; val = false; } else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "on", 2) == 0) { ret = true; val = true; } } if (ret) { obj->type = UCL_BOOLEAN; obj->value.iv = val; } return ret; } /** * Check numeric string * @param obj object to set if a string is numeric * @param start start of string * @param end end of string * @param pos position where parsing has stopped * @param allow_double allow parsing of floating point values * @return 0 if string is numeric and error code (EINVAL or ERANGE) in case of conversion error */ int ucl_maybe_parse_number (ucl_object_t *obj, const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes, bool allow_time); static inline const ucl_object_t * ucl_hash_search_obj (ucl_hash_t* hashlin, ucl_object_t *obj) { return (const ucl_object_t *)ucl_hash_search (hashlin, obj->key, obj->keylen); } static inline ucl_hash_t * ucl_hash_insert_object (ucl_hash_t *hashlin, const ucl_object_t *obj) UCL_WARN_UNUSED_RESULT; static inline ucl_hash_t * ucl_hash_insert_object (ucl_hash_t *hashlin, const ucl_object_t *obj) { if (hashlin == NULL) { hashlin = ucl_hash_create (); } ucl_hash_insert (hashlin, obj, obj->key, obj->keylen); return hashlin; } /** * Emit a single object to string * @param obj * @return */ unsigned char * ucl_object_emit_single_json (const ucl_object_t *obj); #endif /* UCL_INTERNAL_H_ */ diff --git a/src/ucl_parser.c b/src/ucl_parser.c index d5a085ebf829..b4fe5afc8246 100644 --- a/src/ucl_parser.c +++ b/src/ucl_parser.c @@ -1,1926 +1,1969 @@ /* Copyright (c) 2013, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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 "ucl.h" #include "ucl_internal.h" #include "ucl_chartable.h" /** * @file rcl_parser.c * The implementation of rcl parser */ struct ucl_parser_saved_state { unsigned int line; unsigned int column; size_t remain; const unsigned char *pos; }; /** * Move up to len characters * @param parser * @param begin * @param len * @return new position in chunk */ #define ucl_chunk_skipc(chunk, p) do{ \ if (*(p) == '\n') { \ (chunk)->line ++; \ (chunk)->column = 0; \ } \ else (chunk)->column ++; \ (p++); \ (chunk)->pos ++; \ (chunk)->remain --; \ } while (0) static inline void ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err) { if (chunk->pos < chunk->end) { if (isgraph (*chunk->pos)) { ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'", chunk->line, chunk->column, str, *chunk->pos); } else { ucl_create_err (err, "error on line %d at column %d: '%s', character: '0x%02x'", chunk->line, chunk->column, str, (int)*chunk->pos); } } else { ucl_create_err (err, "error at the end of chunk: %s", str); } } /** * Skip all comments from the current pos resolving nested and multiline comments * @param parser * @return */ static bool ucl_skip_comments (struct ucl_parser *parser) { struct ucl_chunk *chunk = parser->chunks; const unsigned char *p; int comments_nested = 0; p = chunk->pos; start: if (*p == '#') { if (parser->state != UCL_STATE_SCOMMENT && parser->state != UCL_STATE_MCOMMENT) { while (p < chunk->end) { if (*p == '\n') { ucl_chunk_skipc (chunk, p); goto start; } ucl_chunk_skipc (chunk, p); } } } else if (*p == '/' && chunk->remain >= 2) { if (p[1] == '*') { ucl_chunk_skipc (chunk, p); comments_nested ++; ucl_chunk_skipc (chunk, p); while (p < chunk->end) { if (*p == '*') { ucl_chunk_skipc (chunk, p); if (*p == '/') { comments_nested --; if (comments_nested == 0) { ucl_chunk_skipc (chunk, p); goto start; } } ucl_chunk_skipc (chunk, p); } else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') { comments_nested ++; ucl_chunk_skipc (chunk, p); ucl_chunk_skipc (chunk, p); continue; } ucl_chunk_skipc (chunk, p); } if (comments_nested != 0) { ucl_set_err (chunk, UCL_ENESTED, "unfinished multiline comment", &parser->err); return false; } } } return true; } /** * Return multiplier for a character * @param c multiplier character * @param is_bytes if true use 1024 multiplier * @return multiplier */ static inline unsigned long ucl_lex_num_multiplier (const unsigned char c, bool is_bytes) { const struct { char c; long mult_normal; long mult_bytes; } multipliers[] = { {'m', 1000 * 1000, 1024 * 1024}, {'k', 1000, 1024}, {'g', 1000 * 1000 * 1000, 1024 * 1024 * 1024} }; int i; for (i = 0; i < 3; i ++) { if (tolower (c) == multipliers[i].c) { if (is_bytes) { return multipliers[i].mult_bytes; } return multipliers[i].mult_normal; } } return 1; } /** * Return multiplier for time scaling * @param c * @return */ static inline double ucl_lex_time_multiplier (const unsigned char c) { const struct { char c; double mult; } multipliers[] = { {'m', 60}, {'h', 60 * 60}, {'d', 60 * 60 * 24}, {'w', 60 * 60 * 24 * 7}, {'y', 60 * 60 * 24 * 7 * 365} }; int i; for (i = 0; i < 5; i ++) { if (tolower (c) == multipliers[i].c) { return multipliers[i].mult; } } return 1; } /** * Return true if a character is a end of an atom * @param c * @return */ static inline bool ucl_lex_is_atom_end (const unsigned char c) { return ucl_test_character (c, UCL_CHARACTER_VALUE_END); } static inline bool ucl_lex_is_comment (const unsigned char c1, const unsigned char c2) { if (c1 == '/') { if (c2 == '*') { return true; } } else if (c1 == '#') { return true; } return false; } /** * Check variable found * @param parser * @param ptr * @param remain * @param out_len * @param strict * @param found * @return */ static inline const char * ucl_check_variable_safe (struct ucl_parser *parser, const char *ptr, size_t remain, size_t *out_len, bool strict, bool *found) { struct ucl_variable *var; + unsigned char *dst; + size_t dstlen; + bool need_free = false; LL_FOREACH (parser->variables, var) { if (strict) { if (remain == var->var_len) { if (memcmp (ptr, var->var, var->var_len) == 0) { *out_len += var->value_len; *found = true; return (ptr + var->var_len); } } } else { if (remain >= var->var_len) { if (memcmp (ptr, var->var, var->var_len) == 0) { *out_len += var->value_len; *found = true; return (ptr + var->var_len); } } } } + /* XXX: can only handle ${VAR} */ + if (!(*found) && parser->var_handler != NULL && strict) { + /* Call generic handler */ + if (parser->var_handler (ptr, remain, &dst, &dstlen, &need_free, + parser->var_data)) { + *found = true; + if (need_free) { + free (dst); + } + return (ptr + remain); + } + } + return ptr; } /** * Check for a variable in a given string * @param parser * @param ptr * @param remain * @param out_len * @param vars_found * @return */ static const char * -ucl_check_variable (struct ucl_parser *parser, const char *ptr, size_t remain, size_t *out_len, bool *vars_found) +ucl_check_variable (struct ucl_parser *parser, const char *ptr, + size_t remain, size_t *out_len, bool *vars_found) { const char *p, *end, *ret = ptr; bool found = false; if (*ptr == '{') { /* We need to match the variable enclosed in braces */ p = ptr + 1; end = ptr + remain; while (p < end) { if (*p == '}') { - ret = ucl_check_variable_safe (parser, ptr + 1, p - ptr - 1, out_len, true, &found); + ret = ucl_check_variable_safe (parser, ptr + 1, p - ptr - 1, + out_len, true, &found); if (found) { /* {} must be excluded actually */ ret ++; if (!*vars_found) { *vars_found = true; } } else { *out_len += 2; } break; } p ++; } } else if (*ptr != '$') { /* Not count escaped dollar sign */ ret = ucl_check_variable_safe (parser, ptr, remain, out_len, false, &found); if (found && !*vars_found) { *vars_found = true; } if (!found) { (*out_len) ++; } } else { ret ++; (*out_len) ++; } return ret; } /** * Expand a single variable * @param parser * @param ptr * @param remain * @param dest * @return */ static const char * ucl_expand_single_variable (struct ucl_parser *parser, const char *ptr, size_t remain, unsigned char **dest) { - unsigned char *d = *dest; + unsigned char *d = *dest, *dst; const char *p = ptr + 1, *ret; struct ucl_variable *var; + size_t dstlen; + bool need_free = false; bool found = false; + bool strict = false; ret = ptr + 1; remain --; if (*p == '$') { *d++ = *p++; *dest = d; return p; } else if (*p == '{') { p ++; + strict = true; ret += 2; remain -= 2; } LL_FOREACH (parser->variables, var) { if (remain >= var->var_len) { if (memcmp (p, var->var, var->var_len) == 0) { memcpy (d, var->value, var->value_len); ret += var->var_len; d += var->value_len; found = true; break; } } } if (!found) { - memcpy (d, ptr, 2); - d += 2; - ret --; + if (strict && parser->var_handler != NULL) { + if (parser->var_handler (ptr, remain, &dst, &dstlen, &need_free, + parser->var_data)) { + memcpy (d, dst, dstlen); + ret += dstlen; + d += remain; + found = true; + } + } + + /* Leave variable as is */ + if (!found) { + memcpy (d, ptr, 2); + d += 2; + ret --; + } } *dest = d; return ret; } /** * Expand variables in string * @param parser * @param dst * @param src * @param in_len * @return */ static ssize_t ucl_expand_variable (struct ucl_parser *parser, unsigned char **dst, const char *src, size_t in_len) { const char *p, *end = src + in_len; unsigned char *d; size_t out_len = 0; bool vars_found = false; p = src; while (p != end) { if (*p == '$') { p = ucl_check_variable (parser, p + 1, end - p - 1, &out_len, &vars_found); } else { p ++; out_len ++; } } if (!vars_found) { /* Trivial case */ *dst = NULL; return in_len; } *dst = UCL_ALLOC (out_len + 1); if (*dst == NULL) { return in_len; } d = *dst; p = src; while (p != end) { if (*p == '$') { p = ucl_expand_single_variable (parser, p, end - p, &d); } else { *d++ = *p++; } } *d = '\0'; return out_len; } /** * Store or copy pointer to the trash stack * @param parser parser object * @param src src string * @param dst destination buffer (trash stack pointer) * @param dst_const const destination pointer (e.g. value of object) * @param in_len input length * @param need_unescape need to unescape source (and copy it) * @param need_lowercase need to lowercase value (and copy) * @param need_expand need to expand variables (and copy as well) * @return output length (excluding \0 symbol) */ static inline ssize_t ucl_copy_or_store_ptr (struct ucl_parser *parser, const unsigned char *src, unsigned char **dst, const char **dst_const, size_t in_len, bool need_unescape, bool need_lowercase, bool need_expand) { ssize_t ret = -1, tret; unsigned char *tmp; if (need_unescape || need_lowercase || (need_expand && parser->variables != NULL) || !(parser->flags & UCL_PARSER_ZEROCOPY)) { /* Copy string */ *dst = UCL_ALLOC (in_len + 1); if (*dst == NULL) { ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", &parser->err); return false; } if (need_lowercase) { ret = ucl_strlcpy_tolower (*dst, src, in_len + 1); } else { ret = ucl_strlcpy_unsafe (*dst, src, in_len + 1); } if (need_unescape) { ret = ucl_unescape_json_string (*dst, ret); } if (need_expand) { tmp = *dst; tret = ret; ret = ucl_expand_variable (parser, dst, tmp, ret); if (*dst == NULL) { /* Nothing to expand */ *dst = tmp; ret = tret; } } *dst_const = *dst; } else { *dst_const = src; ret = in_len; } return ret; } /** * Create and append an object at the specified level * @param parser * @param is_array * @param level * @return */ static inline ucl_object_t * ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_array, int level) { struct ucl_stack *st; if (!is_array) { if (obj == NULL) { obj = ucl_object_typed_new (UCL_OBJECT); } else { obj->type = UCL_OBJECT; } obj->value.ov = ucl_hash_create (); parser->state = UCL_STATE_KEY; } else { if (obj == NULL) { obj = ucl_object_typed_new (UCL_ARRAY); } else { obj->type = UCL_ARRAY; } parser->state = UCL_STATE_VALUE; } st = UCL_ALLOC (sizeof (struct ucl_stack)); if (st == NULL) { ucl_set_err (parser->chunks, 0, "cannot allocate memory for an object", &parser->err); return NULL; } st->obj = obj; st->level = level; LL_PREPEND (parser->stack, st); parser->cur_obj = obj; return obj; } int ucl_maybe_parse_number (ucl_object_t *obj, const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes, bool allow_time) { const char *p = start, *c = start; char *endptr; bool got_dot = false, got_exp = false, need_double = false, is_time = false, valid_start = false, is_hex = false, is_neg = false; double dv = 0; int64_t lv = 0; if (*p == '-') { is_neg = true; c ++; p ++; } while (p < end) { if (is_hex && isxdigit (*p)) { p ++; } else if (isdigit (*p)) { valid_start = true; p ++; } else if (!is_hex && (*p == 'x' || *p == 'X')) { is_hex = true; allow_double = false; c = p + 1; } else if (allow_double) { if (p == c) { /* Empty digits sequence, not a number */ *pos = start; return EINVAL; } else if (*p == '.') { if (got_dot) { /* Double dots, not a number */ *pos = start; return EINVAL; } else { got_dot = true; need_double = true; p ++; } } else if (*p == 'e' || *p == 'E') { if (got_exp) { /* Double exp, not a number */ *pos = start; return EINVAL; } else { got_exp = true; need_double = true; p ++; if (p >= end) { *pos = start; return EINVAL; } if (!isdigit (*p) && *p != '+' && *p != '-') { /* Wrong exponent sign */ *pos = start; return EINVAL; } else { p ++; } } } else { /* Got the end of the number, need to check */ break; } } else { break; } } if (!valid_start) { *pos = start; return EINVAL; } errno = 0; if (need_double) { dv = strtod (c, &endptr); } else { if (is_hex) { lv = strtoimax (c, &endptr, 16); } else { lv = strtoimax (c, &endptr, 10); } } if (errno == ERANGE) { *pos = start; return ERANGE; } /* Now check endptr */ if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0' || ucl_test_character (*endptr, UCL_CHARACTER_WHITESPACE_UNSAFE)) { p = endptr; goto set_obj; } if (endptr < end && endptr != start) { p = endptr; switch (*p) { case 'm': case 'M': case 'g': case 'G': case 'k': case 'K': if (end - p >= 2) { if (p[1] == 's' || p[1] == 'S') { /* Milliseconds */ if (!need_double) { need_double = true; dv = lv; } is_time = true; if (p[0] == 'm' || p[0] == 'M') { dv /= 1000.; } else { dv *= ucl_lex_num_multiplier (*p, false); } p += 2; goto set_obj; } else if (number_bytes || (p[1] == 'b' || p[1] == 'B')) { /* Bytes */ if (need_double) { need_double = false; lv = dv; } lv *= ucl_lex_num_multiplier (*p, true); p += 2; goto set_obj; } else if (ucl_lex_is_atom_end (p[1])) { if (need_double) { dv *= ucl_lex_num_multiplier (*p, false); } else { lv *= ucl_lex_num_multiplier (*p, number_bytes); } p ++; goto set_obj; } else if (allow_time && end - p >= 3) { if (tolower (p[0]) == 'm' && tolower (p[1]) == 'i' && tolower (p[2]) == 'n') { /* Minutes */ if (!need_double) { need_double = true; dv = lv; } is_time = true; dv *= 60.; p += 3; goto set_obj; } } } else { if (need_double) { dv *= ucl_lex_num_multiplier (*p, false); } else { lv *= ucl_lex_num_multiplier (*p, number_bytes); } p ++; goto set_obj; } break; case 'S': case 's': if (allow_time && (p == end - 1 || ucl_lex_is_atom_end (p[1]))) { if (!need_double) { need_double = true; dv = lv; } p ++; is_time = true; goto set_obj; } break; case 'h': case 'H': case 'd': case 'D': case 'w': case 'W': case 'Y': case 'y': if (allow_time && (p == end - 1 || ucl_lex_is_atom_end (p[1]))) { if (!need_double) { need_double = true; dv = lv; } is_time = true; dv *= ucl_lex_time_multiplier (*p); p ++; goto set_obj; } break; } } *pos = c; return EINVAL; set_obj: if (allow_double && (need_double || is_time)) { if (!is_time) { obj->type = UCL_FLOAT; } else { obj->type = UCL_TIME; } obj->value.dv = is_neg ? (-dv) : dv; } else { obj->type = UCL_INT; obj->value.iv = is_neg ? (-lv) : lv; } *pos = p; return 0; } /** * Parse possible number * @param parser * @param chunk * @return true if a number has been parsed */ static bool ucl_lex_number (struct ucl_parser *parser, struct ucl_chunk *chunk, ucl_object_t *obj) { const unsigned char *pos; int ret; ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos, true, false, ((parser->flags & UCL_PARSER_NO_TIME) == 0)); if (ret == 0) { chunk->remain -= pos - chunk->pos; chunk->column += pos - chunk->pos; chunk->pos = pos; return true; } else if (ret == ERANGE) { ucl_set_err (chunk, ERANGE, "numeric value out of range", &parser->err); } return false; } /** * Parse quoted string with possible escapes * @param parser * @param chunk * @return true if a string has been parsed */ static bool ucl_lex_json_string (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *need_unescape, bool *ucl_escape, bool *var_expand) { const unsigned char *p = chunk->pos; unsigned char c; int i; while (p < chunk->end) { c = *p; if (c < 0x1F) { /* Unmasked control character */ if (c == '\n') { ucl_set_err (chunk, UCL_ESYNTAX, "unexpected newline", &parser->err); } else { ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", &parser->err); } return false; } else if (c == '\\') { ucl_chunk_skipc (chunk, p); c = *p; if (p >= chunk->end) { ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err); return false; } else if (ucl_test_character (c, UCL_CHARACTER_ESCAPE)) { if (c == 'u') { ucl_chunk_skipc (chunk, p); for (i = 0; i < 4 && p < chunk->end; i ++) { if (!isxdigit (*p)) { ucl_set_err (chunk, UCL_ESYNTAX, "invalid utf escape", &parser->err); return false; } ucl_chunk_skipc (chunk, p); } if (p >= chunk->end) { ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err); return false; } } else { ucl_chunk_skipc (chunk, p); } } *need_unescape = true; *ucl_escape = true; continue; } else if (c == '"') { ucl_chunk_skipc (chunk, p); return true; } else if (ucl_test_character (c, UCL_CHARACTER_UCL_UNSAFE)) { *ucl_escape = true; } else if (c == '$') { *var_expand = true; } ucl_chunk_skipc (chunk, p); } ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", &parser->err); return false; } /** * Parse a key in an object * @param parser * @param chunk * @return true if a key has been parsed */ static bool ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_key, bool *end_of_object) { const unsigned char *p, *c = NULL, *end, *t; const char *key = NULL; bool got_quote = false, got_eq = false, got_semicolon = false, need_unescape = false, ucl_escape = false, var_expand = false, got_content = false, got_sep = false; ucl_object_t *nobj, *tobj; ucl_hash_t *container; ssize_t keylen; p = chunk->pos; if (*p == '.') { /* It is macro actually */ ucl_chunk_skipc (chunk, p); parser->prev_state = parser->state; parser->state = UCL_STATE_MACRO_NAME; return true; } while (p < chunk->end) { /* * A key must start with alpha, number, '/' or '_' and end with space character */ if (c == NULL) { if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) { if (!ucl_skip_comments (parser)) { return false; } p = chunk->pos; } else if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { ucl_chunk_skipc (chunk, p); } else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) { /* The first symbol */ c = p; ucl_chunk_skipc (chunk, p); got_content = true; } else if (*p == '"') { /* JSON style key */ c = p + 1; got_quote = true; got_content = true; ucl_chunk_skipc (chunk, p); } else if (*p == '}') { /* We have actually end of an object */ *end_of_object = true; return true; } else if (*p == '.') { ucl_chunk_skipc (chunk, p); parser->prev_state = parser->state; parser->state = UCL_STATE_MACRO_NAME; return true; } else { /* Invalid identifier */ ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", &parser->err); return false; } } else { /* Parse the body of a key */ if (!got_quote) { if (ucl_test_character (*p, UCL_CHARACTER_KEY)) { got_content = true; ucl_chunk_skipc (chunk, p); } else if (ucl_test_character (*p, UCL_CHARACTER_KEY_SEP)) { end = p; break; } else { ucl_set_err (chunk, UCL_ESYNTAX, "invalid character in a key", &parser->err); return false; } } else { /* We need to parse json like quoted string */ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) { return false; } /* Always escape keys obtained via json */ end = chunk->pos - 1; p = chunk->pos; break; } } } if (p >= chunk->end && got_content) { ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err); return false; } else if (!got_content) { return true; } *end_of_object = false; /* We are now at the end of the key, need to parse the rest */ while (p < chunk->end) { if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) { ucl_chunk_skipc (chunk, p); } else if (*p == '=') { if (!got_eq && !got_semicolon) { ucl_chunk_skipc (chunk, p); got_eq = true; } else { ucl_set_err (chunk, UCL_ESYNTAX, "unexpected '=' character", &parser->err); return false; } } else if (*p == ':') { if (!got_eq && !got_semicolon) { ucl_chunk_skipc (chunk, p); got_semicolon = true; } else { ucl_set_err (chunk, UCL_ESYNTAX, "unexpected ':' character", &parser->err); return false; } } else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) { /* Check for comment */ if (!ucl_skip_comments (parser)) { return false; } p = chunk->pos; } else { /* Start value */ break; } } if (p >= chunk->end && got_content) { ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err); return false; } got_sep = got_semicolon || got_eq; if (!got_sep) { /* * Maybe we have more keys nested, so search for termination character. * Possible choices: * 1) key1 key2 ... keyN [:=] value <- we treat that as error * 2) key1 ... keyN {} or [] <- we treat that as nested objects * 3) key1 value[;,\n] <- we treat that as linear object */ t = p; *next_key = false; while (ucl_test_character (*t, UCL_CHARACTER_WHITESPACE)) { t ++; } /* Check first non-space character after a key */ if (*t != '{' && *t != '[') { while (t < chunk->end) { if (*t == ',' || *t == ';' || *t == '\n' || *t == '\r') { break; } else if (*t == '{' || *t == '[') { *next_key = true; break; } t ++; } } } /* Create a new object */ nobj = ucl_object_new (); keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY], &key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE, false); if (keylen == -1) { ucl_object_unref (nobj); return false; } else if (keylen == 0) { ucl_set_err (chunk, UCL_ESYNTAX, "empty keys are not allowed", &parser->err); ucl_object_unref (nobj); return false; } container = parser->stack->obj->value.ov; nobj->key = key; nobj->keylen = keylen; tobj = __DECONST (ucl_object_t *, ucl_hash_search_obj (container, nobj)); if (tobj == NULL) { container = ucl_hash_insert_object (container, nobj); nobj->prev = nobj; nobj->next = NULL; parser->stack->obj->len ++; } else { DL_APPEND (tobj, nobj); } if (ucl_escape) { nobj->flags |= UCL_OBJECT_NEED_KEY_ESCAPE; } parser->stack->obj->value.ov = container; parser->cur_obj = nobj; return true; } /** * Parse a cl string * @param parser * @param chunk * @return true if a key has been parsed */ static bool ucl_parse_string_value (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *var_expand, bool *need_unescape) { const unsigned char *p; enum { UCL_BRACE_ROUND = 0, UCL_BRACE_SQUARE, UCL_BRACE_FIGURE }; int braces[3][2] = {{0, 0}, {0, 0}, {0, 0}}; p = chunk->pos; while (p < chunk->end) { /* Skip pairs of figure braces */ if (*p == '{') { braces[UCL_BRACE_FIGURE][0] ++; } else if (*p == '}') { braces[UCL_BRACE_FIGURE][1] ++; if (braces[UCL_BRACE_FIGURE][1] <= braces[UCL_BRACE_FIGURE][0]) { /* This is not a termination symbol, continue */ ucl_chunk_skipc (chunk, p); continue; } } /* Skip pairs of square braces */ else if (*p == '[') { braces[UCL_BRACE_SQUARE][0] ++; } else if (*p == ']') { braces[UCL_BRACE_SQUARE][1] ++; if (braces[UCL_BRACE_SQUARE][1] <= braces[UCL_BRACE_SQUARE][0]) { /* This is not a termination symbol, continue */ ucl_chunk_skipc (chunk, p); continue; } } else if (*p == '$') { *var_expand = true; } else if (*p == '\\') { *need_unescape = true; ucl_chunk_skipc (chunk, p); if (p < chunk->end) { ucl_chunk_skipc (chunk, p); } continue; } if (ucl_lex_is_atom_end (*p) || (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) { break; } ucl_chunk_skipc (chunk, p); } if (p >= chunk->end) { ucl_set_err (chunk, UCL_ESYNTAX, "unfinished value", &parser->err); return false; } return true; } /** * Parse multiline string ending with \n{term}\n * @param parser * @param chunk * @param term * @param term_len * @return size of multiline string or 0 in case of error */ static int ucl_parse_multiline_string (struct ucl_parser *parser, struct ucl_chunk *chunk, const unsigned char *term, int term_len, unsigned char const **beg, bool *var_expand) { const unsigned char *p, *c; bool newline = false; int len = 0; p = chunk->pos; c = p; while (p < chunk->end) { if (newline) { if (chunk->end - p < term_len) { return 0; } else if (memcmp (p, term, term_len) == 0 && (p[term_len] == '\n' || p[term_len] == '\r')) { len = p - c; chunk->remain -= term_len; chunk->pos = p + term_len; chunk->column = term_len; *beg = c; break; } } if (*p == '\n') { newline = true; } else { if (*p == '$') { *var_expand = true; } newline = false; } ucl_chunk_skipc (chunk, p); } return len; } static ucl_object_t* ucl_get_value_object (struct ucl_parser *parser) { ucl_object_t *t, *obj = NULL; if (parser->stack->obj->type == UCL_ARRAY) { /* Object must be allocated */ obj = ucl_object_new (); t = parser->stack->obj->value.av; DL_APPEND (t, obj); parser->cur_obj = obj; parser->stack->obj->value.av = t; parser->stack->obj->len ++; } else { /* Object has been already allocated */ obj = parser->cur_obj; } return obj; } /** * Handle value data * @param parser * @param chunk * @return */ static bool ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk) { const unsigned char *p, *c; ucl_object_t *obj = NULL; unsigned int stripped_spaces; int str_len; bool need_unescape = false, ucl_escape = false, var_expand = false; p = chunk->pos; /* Skip any spaces and comments */ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) || (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) { while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { ucl_chunk_skipc (chunk, p); } if (!ucl_skip_comments (parser)) { return false; } p = chunk->pos; } while (p < chunk->end) { c = p; switch (*p) { case '"': obj = ucl_get_value_object (parser); ucl_chunk_skipc (chunk, p); if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) { return false; } str_len = chunk->pos - c - 2; obj->type = UCL_STRING; if ((str_len = ucl_copy_or_store_ptr (parser, c + 1, &obj->trash_stack[UCL_TRASH_VALUE], &obj->value.sv, str_len, need_unescape, false, var_expand)) == -1) { return false; } obj->len = str_len; parser->state = UCL_STATE_AFTER_VALUE; p = chunk->pos; return true; break; case '{': obj = ucl_get_value_object (parser); /* We have a new object */ obj = ucl_add_parser_stack (obj, parser, false, parser->stack->level); if (obj == NULL) { return false; } ucl_chunk_skipc (chunk, p); return true; break; case '[': obj = ucl_get_value_object (parser); /* We have a new array */ obj = ucl_add_parser_stack (obj, parser, true, parser->stack->level); if (obj == NULL) { return false; } ucl_chunk_skipc (chunk, p); return true; break; case ']': /* We have the array ending */ if (parser->stack && parser->stack->obj->type == UCL_ARRAY) { parser->state = UCL_STATE_AFTER_VALUE; return true; } else { goto parse_string; } break; case '<': obj = ucl_get_value_object (parser); /* We have something like multiline value, which must be <<[A-Z]+\n */ if (chunk->end - p > 3) { if (memcmp (p, "<<", 2) == 0) { p += 2; /* We allow only uppercase characters in multiline definitions */ while (p < chunk->end && *p >= 'A' && *p <= 'Z') { p ++; } if (*p =='\n') { /* Set chunk positions and start multiline parsing */ c += 2; chunk->remain -= p - c; chunk->pos = p + 1; chunk->column = 0; chunk->line ++; if ((str_len = ucl_parse_multiline_string (parser, chunk, c, p - c, &c, &var_expand)) == 0) { ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", &parser->err); return false; } obj->type = UCL_STRING; if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE], &obj->value.sv, str_len - 1, false, false, var_expand)) == -1) { return false; } obj->len = str_len; parser->state = UCL_STATE_AFTER_VALUE; return true; } } } /* Fallback to ordinary strings */ default: parse_string: if (obj == NULL) { obj = ucl_get_value_object (parser); } /* Parse atom */ if (ucl_test_character (*p, UCL_CHARACTER_VALUE_DIGIT_START)) { if (!ucl_lex_number (parser, chunk, obj)) { if (parser->state == UCL_STATE_ERROR) { return false; } } else { parser->state = UCL_STATE_AFTER_VALUE; return true; } /* Fallback to normal string */ } if (!ucl_parse_string_value (parser, chunk, &var_expand, &need_unescape)) { return false; } /* Cut trailing spaces */ stripped_spaces = 0; while (ucl_test_character (*(chunk->pos - 1 - stripped_spaces), UCL_CHARACTER_WHITESPACE)) { stripped_spaces ++; } str_len = chunk->pos - c - stripped_spaces; if (str_len <= 0) { ucl_set_err (chunk, 0, "string value must not be empty", &parser->err); return false; } else if (str_len == 4 && memcmp (c, "null", 4) == 0) { obj->len = 0; obj->type = UCL_NULL; } else if (!ucl_maybe_parse_boolean (obj, c, str_len)) { obj->type = UCL_STRING; if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE], &obj->value.sv, str_len, need_unescape, false, var_expand)) == -1) { return false; } obj->len = str_len; } parser->state = UCL_STATE_AFTER_VALUE; p = chunk->pos; return true; break; } } return true; } /** * Handle after value data * @param parser * @param chunk * @return */ static bool ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk) { const unsigned char *p; bool got_sep = false; struct ucl_stack *st; p = chunk->pos; while (p < chunk->end) { if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) { /* Skip whitespaces */ ucl_chunk_skipc (chunk, p); } else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) { /* Skip comment */ if (!ucl_skip_comments (parser)) { return false; } /* Treat comment as a separator */ got_sep = true; p = chunk->pos; } else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) { if (*p == '}' || *p == ']') { if (parser->stack == NULL) { ucl_set_err (chunk, UCL_ESYNTAX, "end of array or object detected without corresponding start", &parser->err); return false; } if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) || (*p == ']' && parser->stack->obj->type == UCL_ARRAY)) { /* Pop all nested objects from a stack */ st = parser->stack; parser->stack = st->next; UCL_FREE (sizeof (struct ucl_stack), st); while (parser->stack != NULL) { st = parser->stack; if (st->next == NULL || st->next->level == st->level) { break; } parser->stack = st->next; UCL_FREE (sizeof (struct ucl_stack), st); } } else { ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", &parser->err); return false; } if (parser->stack == NULL) { /* Ignore everything after a top object */ return true; } else { ucl_chunk_skipc (chunk, p); } got_sep = true; } else { /* Got a separator */ got_sep = true; ucl_chunk_skipc (chunk, p); } } else { /* Anything else */ if (!got_sep) { ucl_set_err (chunk, UCL_ESYNTAX, "delimiter is missing", &parser->err); return false; } return true; } } return true; } /** * Handle macro data * @param parser * @param chunk * @return */ static bool ucl_parse_macro_value (struct ucl_parser *parser, struct ucl_chunk *chunk, struct ucl_macro *macro, unsigned char const **macro_start, size_t *macro_len) { const unsigned char *p, *c; bool need_unescape = false, ucl_escape = false, var_expand = false; p = chunk->pos; switch (*p) { case '"': /* We have macro value encoded in quotes */ c = p; ucl_chunk_skipc (chunk, p); if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) { return false; } *macro_start = c + 1; *macro_len = chunk->pos - c - 2; p = chunk->pos; break; case '{': /* We got a multiline macro body */ ucl_chunk_skipc (chunk, p); /* Skip spaces at the beginning */ while (p < chunk->end) { if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { ucl_chunk_skipc (chunk, p); } else { break; } } c = p; while (p < chunk->end) { if (*p == '}') { break; } ucl_chunk_skipc (chunk, p); } *macro_start = c; *macro_len = p - c; ucl_chunk_skipc (chunk, p); break; default: /* Macro is not enclosed in quotes or braces */ c = p; while (p < chunk->end) { if (ucl_lex_is_atom_end (*p)) { break; } ucl_chunk_skipc (chunk, p); } *macro_start = c; *macro_len = p - c; break; } /* We are at the end of a macro */ /* Skip ';' and space characters and return to previous state */ while (p < chunk->end) { if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) && *p != ';') { break; } ucl_chunk_skipc (chunk, p); } return true; } /** * Handle the main states of rcl parser * @param parser parser structure * @param data the pointer to the beginning of a chunk * @param len the length of a chunk * @return true if chunk has been parsed and false in case of error */ static bool ucl_state_machine (struct ucl_parser *parser) { ucl_object_t *obj; struct ucl_chunk *chunk = parser->chunks; const unsigned char *p, *c = NULL, *macro_start = NULL; unsigned char *macro_escaped; size_t macro_len = 0; struct ucl_macro *macro = NULL; bool next_key = false, end_of_object = false; if (parser->top_obj == NULL) { if (*chunk->pos == '[') { obj = ucl_add_parser_stack (NULL, parser, true, 0); } else { obj = ucl_add_parser_stack (NULL, parser, false, 0); } if (obj == NULL) { return false; } parser->top_obj = obj; parser->cur_obj = obj; parser->state = UCL_STATE_INIT; } p = chunk->pos; while (chunk->pos < chunk->end) { switch (parser->state) { case UCL_STATE_INIT: /* * At the init state we can either go to the parse array or object * if we got [ or { correspondingly or can just treat new data as * a key of newly created object */ obj = parser->cur_obj; if (!ucl_skip_comments (parser)) { parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } else { p = chunk->pos; if (*p == '[') { parser->state = UCL_STATE_VALUE; ucl_chunk_skipc (chunk, p); } else { parser->state = UCL_STATE_KEY; if (*p == '{') { ucl_chunk_skipc (chunk, p); } } } break; case UCL_STATE_KEY: /* Skip any spaces */ while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { ucl_chunk_skipc (chunk, p); } if (*p == '}') { /* We have the end of an object */ parser->state = UCL_STATE_AFTER_VALUE; continue; } if (parser->stack == NULL) { /* No objects are on stack, but we want to parse a key */ ucl_set_err (chunk, UCL_ESYNTAX, "top object is finished but the parser " "expects a key", &parser->err); parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } if (!ucl_parse_key (parser, chunk, &next_key, &end_of_object)) { parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } if (end_of_object) { p = chunk->pos; parser->state = UCL_STATE_AFTER_VALUE; continue; } else if (parser->state != UCL_STATE_MACRO_NAME) { if (next_key && parser->stack->obj->type == UCL_OBJECT) { /* Parse more keys and nest objects accordingly */ obj = ucl_add_parser_stack (parser->cur_obj, parser, false, parser->stack->level + 1); if (obj == NULL) { return false; } } else { parser->state = UCL_STATE_VALUE; } } else { c = chunk->pos; } p = chunk->pos; break; case UCL_STATE_VALUE: /* We need to check what we do have */ if (!ucl_parse_value (parser, chunk)) { parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } /* State is set in ucl_parse_value call */ p = chunk->pos; break; case UCL_STATE_AFTER_VALUE: if (!ucl_parse_after_value (parser, chunk)) { parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } if (parser->stack != NULL) { if (parser->stack->obj->type == UCL_OBJECT) { parser->state = UCL_STATE_KEY; } else { /* Array */ parser->state = UCL_STATE_VALUE; } } else { /* Skip everything at the end */ return true; } p = chunk->pos; break; case UCL_STATE_MACRO_NAME: if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { ucl_chunk_skipc (chunk, p); } else if (p - c > 0) { /* We got macro name */ macro_len = (size_t)(p - c); HASH_FIND (hh, parser->macroes, c, macro_len, macro); if (macro == NULL) { ucl_create_err (&parser->err, "error on line %d at column %d: " "unknown macro: '%.*s', character: '%c'", chunk->line, chunk->column, (int)(p - c), c, *chunk->pos); parser->state = UCL_STATE_ERROR; return false; } /* Now we need to skip all spaces */ while (p < chunk->end) { if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) { if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) { /* Skip comment */ if (!ucl_skip_comments (parser)) { return false; } p = chunk->pos; } break; } ucl_chunk_skipc (chunk, p); } parser->state = UCL_STATE_MACRO; } break; case UCL_STATE_MACRO: if (!ucl_parse_macro_value (parser, chunk, macro, ¯o_start, ¯o_len)) { parser->prev_state = parser->state; parser->state = UCL_STATE_ERROR; return false; } macro_len = ucl_expand_variable (parser, ¯o_escaped, macro_start, macro_len); parser->state = parser->prev_state; if (macro_escaped == NULL) { if (!macro->handler (macro_start, macro_len, macro->ud)) { return false; } } else { if (!macro->handler (macro_escaped, macro_len, macro->ud)) { UCL_FREE (macro_len + 1, macro_escaped); return false; } UCL_FREE (macro_len + 1, macro_escaped); } p = chunk->pos; break; default: /* TODO: add all states */ ucl_set_err (chunk, UCL_EINTERNAL, "internal error: parser is in an unknown state", &parser->err); parser->state = UCL_STATE_ERROR; return false; } } return true; } struct ucl_parser* ucl_parser_new (int flags) { struct ucl_parser *new; new = UCL_ALLOC (sizeof (struct ucl_parser)); if (new == NULL) { return NULL; } memset (new, 0, sizeof (struct ucl_parser)); ucl_parser_register_macro (new, "include", ucl_include_handler, new); ucl_parser_register_macro (new, "try_include", ucl_try_include_handler, new); ucl_parser_register_macro (new, "includes", ucl_includes_handler, new); new->flags = flags; /* Initial assumption about filevars */ ucl_parser_set_filevars (new, NULL, false); return new; } void ucl_parser_register_macro (struct ucl_parser *parser, const char *macro, ucl_macro_handler handler, void* ud) { struct ucl_macro *new; if (macro == NULL || handler == NULL) { return; } new = UCL_ALLOC (sizeof (struct ucl_macro)); if (new == NULL) { return; } memset (new, 0, sizeof (struct ucl_macro)); new->handler = handler; new->name = strdup (macro); new->ud = ud; HASH_ADD_KEYPTR (hh, parser->macroes, new->name, strlen (new->name), new); } void ucl_parser_register_variable (struct ucl_parser *parser, const char *var, const char *value) { struct ucl_variable *new = NULL, *cur; if (var == NULL) { return; } /* Find whether a variable already exists */ LL_FOREACH (parser->variables, cur) { if (strcmp (cur->var, var) == 0) { new = cur; break; } } if (value == NULL) { if (new != NULL) { /* Remove variable */ LL_DELETE (parser->variables, new); free (new->var); free (new->value); UCL_FREE (sizeof (struct ucl_variable), new); } else { /* Do nothing */ return; } } else { if (new == NULL) { new = UCL_ALLOC (sizeof (struct ucl_variable)); if (new == NULL) { return; } memset (new, 0, sizeof (struct ucl_variable)); new->var = strdup (var); new->var_len = strlen (var); new->value = strdup (value); new->value_len = strlen (value); LL_PREPEND (parser->variables, new); } else { free (new->value); new->value = strdup (value); new->value_len = strlen (value); } } } +void +ucl_parser_set_variables_handler (struct ucl_parser *parser, + ucl_variable_handler handler, void *ud) +{ + parser->var_handler = handler; + parser->var_data = ud; +} + bool ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data, size_t len) { struct ucl_chunk *chunk; if (data == NULL || len == 0) { ucl_create_err (&parser->err, "invalid chunk added"); return false; } if (parser->state != UCL_STATE_ERROR) { chunk = UCL_ALLOC (sizeof (struct ucl_chunk)); if (chunk == NULL) { ucl_create_err (&parser->err, "cannot allocate chunk structure"); return false; } chunk->begin = data; chunk->remain = len; chunk->pos = chunk->begin; chunk->end = chunk->begin + len; chunk->line = 1; chunk->column = 0; LL_PREPEND (parser->chunks, chunk); parser->recursion ++; if (parser->recursion > UCL_MAX_RECURSION) { ucl_create_err (&parser->err, "maximum include nesting limit is reached: %d", parser->recursion); return false; } return ucl_state_machine (parser); } ucl_create_err (&parser->err, "a parser is in an invalid state"); return false; } bool ucl_parser_add_string (struct ucl_parser *parser, const char *data, size_t len) { if (data == NULL) { ucl_create_err (&parser->err, "invalid string added"); return false; } if (len == 0) { len = strlen (data); } return ucl_parser_add_chunk (parser, (const unsigned char *)data, len); } diff --git a/src/ucl_util.c b/src/ucl_util.c index 9178795d9446..63f5e629826f 100644 --- a/src/ucl_util.c +++ b/src/ucl_util.c @@ -1,1889 +1,1958 @@ /* Copyright (c) 2013, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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 "ucl.h" #include "ucl_internal.h" #include "ucl_chartable.h" #ifdef HAVE_LIBGEN_H #include /* For dirname */ #endif #ifdef HAVE_OPENSSL #include #include #include #include #include #endif #ifdef CURL_FOUND #include #endif #ifdef HAVE_FETCH_H #include #endif #ifdef _WIN32 #include #ifndef PROT_READ #define PROT_READ 1 #endif #ifndef PROT_WRITE #define PROT_WRITE 2 #endif #ifndef PROT_READWRITE #define PROT_READWRITE 3 #endif #ifndef MAP_SHARED #define MAP_SHARED 1 #endif #ifndef MAP_PRIVATE #define MAP_PRIVATE 2 #endif #ifndef MAP_FAILED #define MAP_FAILED ((void *) -1) #endif static void *ucl_mmap(char *addr, size_t length, int prot, int access, int fd, off_t offset) { void *map = NULL; HANDLE handle = INVALID_HANDLE_VALUE; switch (prot) { default: case PROT_READ: { handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READONLY, 0, length, 0); if (!handle) break; map = (void *) MapViewOfFile(handle, FILE_MAP_READ, 0, 0, length); CloseHandle(handle); break; } case PROT_WRITE: { handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READWRITE, 0, length, 0); if (!handle) break; map = (void *) MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, length); CloseHandle(handle); break; } case PROT_READWRITE: { handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READWRITE, 0, length, 0); if (!handle) break; map = (void *) MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, length); CloseHandle(handle); break; } } if (map == (void *) NULL) { return (void *) MAP_FAILED; } return (void *) ((char *) map + offset); } static int ucl_munmap(void *map,size_t length) { if (!UnmapViewOfFile(map)) { return(-1); } return(0); } static char* ucl_realpath(const char *path, char *resolved_path) { char *p; char tmp[MAX_PATH + 1]; strncpy(tmp, path, sizeof(tmp)-1); p = tmp; while(*p) { if (*p == '/') *p = '\\'; p++; } return _fullpath(resolved_path, tmp, MAX_PATH); } #else #define ucl_mmap mmap #define ucl_munmap munmap #define ucl_realpath realpath #endif /** * @file rcl_util.c * Utilities for rcl parsing */ typedef void (*ucl_object_dtor) (ucl_object_t *obj); static void ucl_object_free_internal (ucl_object_t *obj, bool allow_rec, ucl_object_dtor dtor); static void ucl_object_dtor_unref (ucl_object_t *obj); static void ucl_object_dtor_free (ucl_object_t *obj) { if (obj->trash_stack[UCL_TRASH_KEY] != NULL) { UCL_FREE (obj->hh.keylen, obj->trash_stack[UCL_TRASH_KEY]); } if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) { UCL_FREE (obj->len, obj->trash_stack[UCL_TRASH_VALUE]); } UCL_FREE (sizeof (ucl_object_t), obj); } /* * This is a helper function that performs exactly the same as * `ucl_object_unref` but it doesn't iterate over elements allowing * to use it for individual elements of arrays and multiple values */ static void ucl_object_dtor_unref_single (ucl_object_t *obj) { if (obj != NULL) { #ifdef HAVE_ATOMIC_BUILTINS unsigned int rc = __sync_sub_and_fetch (&obj->ref, 1); if (rc == 0) { #else if (--obj->ref == 0) { #endif ucl_object_free_internal (obj, false, ucl_object_dtor_unref); } } } static void ucl_object_dtor_unref (ucl_object_t *obj) { if (obj->ref == 0) { ucl_object_dtor_free (obj); } else { /* This may cause dtor unref being called one more time */ ucl_object_dtor_unref_single (obj); } } static void ucl_object_free_internal (ucl_object_t *obj, bool allow_rec, ucl_object_dtor dtor) { ucl_object_t *sub, *tmp; while (obj != NULL) { if (obj->type == UCL_ARRAY) { sub = obj->value.av; while (sub != NULL) { tmp = sub->next; dtor (sub); sub = tmp; } } else if (obj->type == UCL_OBJECT) { if (obj->value.ov != NULL) { ucl_hash_destroy (obj->value.ov, (ucl_hash_free_func *)dtor); } } tmp = obj->next; dtor (obj); obj = tmp; if (!allow_rec) { break; } } } void ucl_object_free (ucl_object_t *obj) { ucl_object_free_internal (obj, true, ucl_object_dtor_free); } size_t ucl_unescape_json_string (char *str, size_t len) { char *t = str, *h = str; int i, uval; if (len <= 1) { return len; } /* t is target (tortoise), h is source (hare) */ while (len) { if (*h == '\\') { h ++; switch (*h) { case 'n': *t++ = '\n'; break; case 'r': *t++ = '\r'; break; case 'b': *t++ = '\b'; break; case 't': *t++ = '\t'; break; case 'f': *t++ = '\f'; break; case '\\': *t++ = '\\'; break; case '"': *t++ = '"'; break; case 'u': /* Unicode escape */ uval = 0; if (len > 3) { for (i = 0; i < 4; i++) { uval <<= 4; if (isdigit (h[i])) { uval += h[i] - '0'; } else if (h[i] >= 'a' && h[i] <= 'f') { uval += h[i] - 'a' + 10; } else if (h[i] >= 'A' && h[i] <= 'F') { uval += h[i] - 'A' + 10; } else { break; } } h += 3; len -= 3; /* Encode */ if(uval < 0x80) { t[0] = (char)uval; t ++; } else if(uval < 0x800) { t[0] = 0xC0 + ((uval & 0x7C0) >> 6); t[1] = 0x80 + ((uval & 0x03F)); t += 2; } else if(uval < 0x10000) { t[0] = 0xE0 + ((uval & 0xF000) >> 12); t[1] = 0x80 + ((uval & 0x0FC0) >> 6); t[2] = 0x80 + ((uval & 0x003F)); t += 3; } else if(uval <= 0x10FFFF) { t[0] = 0xF0 + ((uval & 0x1C0000) >> 18); t[1] = 0x80 + ((uval & 0x03F000) >> 12); t[2] = 0x80 + ((uval & 0x000FC0) >> 6); t[3] = 0x80 + ((uval & 0x00003F)); t += 4; } else { *t++ = '?'; } } else { *t++ = 'u'; } break; default: *t++ = *h; break; } h ++; len --; } else { *t++ = *h++; } len --; } *t = '\0'; return (t - str); } char * ucl_copy_key_trash (const ucl_object_t *obj) { ucl_object_t *deconst; if (obj == NULL) { return NULL; } if (obj->trash_stack[UCL_TRASH_KEY] == NULL && obj->key != NULL) { deconst = __DECONST (ucl_object_t *, obj); deconst->trash_stack[UCL_TRASH_KEY] = malloc (obj->keylen + 1); if (deconst->trash_stack[UCL_TRASH_KEY] != NULL) { memcpy (deconst->trash_stack[UCL_TRASH_KEY], obj->key, obj->keylen); deconst->trash_stack[UCL_TRASH_KEY][obj->keylen] = '\0'; } deconst->key = obj->trash_stack[UCL_TRASH_KEY]; deconst->flags |= UCL_OBJECT_ALLOCATED_KEY; } return obj->trash_stack[UCL_TRASH_KEY]; } char * ucl_copy_value_trash (const ucl_object_t *obj) { ucl_object_t *deconst; if (obj == NULL) { return NULL; } if (obj->trash_stack[UCL_TRASH_VALUE] == NULL) { deconst = __DECONST (ucl_object_t *, obj); if (obj->type == UCL_STRING) { /* Special case for strings */ deconst->trash_stack[UCL_TRASH_VALUE] = malloc (obj->len + 1); if (deconst->trash_stack[UCL_TRASH_VALUE] != NULL) { memcpy (deconst->trash_stack[UCL_TRASH_VALUE], obj->value.sv, obj->len); deconst->trash_stack[UCL_TRASH_VALUE][obj->len] = '\0'; deconst->value.sv = obj->trash_stack[UCL_TRASH_VALUE]; } } else { /* Just emit value in json notation */ deconst->trash_stack[UCL_TRASH_VALUE] = ucl_object_emit_single_json (obj); deconst->len = strlen (obj->trash_stack[UCL_TRASH_VALUE]); } deconst->flags |= UCL_OBJECT_ALLOCATED_VALUE; } return obj->trash_stack[UCL_TRASH_VALUE]; } UCL_EXTERN ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser) { if (parser->state != UCL_STATE_ERROR && parser->top_obj != NULL) { return ucl_object_ref (parser->top_obj); } return NULL; } UCL_EXTERN void ucl_parser_free (struct ucl_parser *parser) { struct ucl_stack *stack, *stmp; struct ucl_macro *macro, *mtmp; struct ucl_chunk *chunk, *ctmp; struct ucl_pubkey *key, *ktmp; struct ucl_variable *var, *vtmp; if (parser == NULL) { return; } if (parser->top_obj != NULL) { ucl_object_unref (parser->top_obj); } LL_FOREACH_SAFE (parser->stack, stack, stmp) { free (stack); } HASH_ITER (hh, parser->macroes, macro, mtmp) { free (macro->name); HASH_DEL (parser->macroes, macro); UCL_FREE (sizeof (struct ucl_macro), macro); } LL_FOREACH_SAFE (parser->chunks, chunk, ctmp) { UCL_FREE (sizeof (struct ucl_chunk), chunk); } LL_FOREACH_SAFE (parser->keys, key, ktmp) { UCL_FREE (sizeof (struct ucl_pubkey), key); } LL_FOREACH_SAFE (parser->variables, var, vtmp) { free (var->value); free (var->var); UCL_FREE (sizeof (struct ucl_variable), var); } if (parser->err != NULL) { utstring_free(parser->err); } UCL_FREE (sizeof (struct ucl_parser), parser); } UCL_EXTERN const char * ucl_parser_get_error(struct ucl_parser *parser) { if (parser == NULL) { return NULL; } if (parser->err == NULL) return NULL; return utstring_body(parser->err); } UCL_EXTERN bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len) { #ifndef HAVE_OPENSSL ucl_create_err (&parser->err, "cannot check signatures without openssl"); return false; #else # if (OPENSSL_VERSION_NUMBER < 0x10000000L) ucl_create_err (&parser->err, "cannot check signatures, openssl version is unsupported"); return EXIT_FAILURE; # else struct ucl_pubkey *nkey; BIO *mem; mem = BIO_new_mem_buf ((void *)key, len); nkey = UCL_ALLOC (sizeof (struct ucl_pubkey)); if (nkey == NULL) { ucl_create_err (&parser->err, "cannot allocate memory for key"); return false; } nkey->key = PEM_read_bio_PUBKEY (mem, &nkey->key, NULL, NULL); BIO_free (mem); if (nkey->key == NULL) { UCL_FREE (sizeof (struct ucl_pubkey), nkey); ucl_create_err (&parser->err, "%s", ERR_error_string (ERR_get_error (), NULL)); return false; } LL_PREPEND (parser->keys, nkey); # endif #endif return true; } #ifdef CURL_FOUND struct ucl_curl_cbdata { unsigned char *buf; size_t buflen; }; static size_t ucl_curl_write_callback (void* contents, size_t size, size_t nmemb, void* ud) { struct ucl_curl_cbdata *cbdata = ud; size_t realsize = size * nmemb; cbdata->buf = realloc (cbdata->buf, cbdata->buflen + realsize + 1); if (cbdata->buf == NULL) { return 0; } memcpy (&(cbdata->buf[cbdata->buflen]), contents, realsize); cbdata->buflen += realsize; cbdata->buf[cbdata->buflen] = 0; return realsize; } #endif /** * Fetch a url and save results to the memory buffer * @param url url to fetch * @param len length of url * @param buf target buffer * @param buflen target length * @return */ static bool ucl_fetch_url (const unsigned char *url, unsigned char **buf, size_t *buflen, UT_string **err, bool must_exist) { #ifdef HAVE_FETCH_H struct url *fetch_url; struct url_stat us; FILE *in; fetch_url = fetchParseURL (url); if (fetch_url == NULL) { ucl_create_err (err, "invalid URL %s: %s", url, strerror (errno)); return false; } if ((in = fetchXGet (fetch_url, &us, "")) == NULL) { if (!must_exist) { ucl_create_err (err, "cannot fetch URL %s: %s", url, strerror (errno)); } fetchFreeURL (fetch_url); return false; } *buflen = us.size; *buf = malloc (*buflen); if (*buf == NULL) { ucl_create_err (err, "cannot allocate buffer for URL %s: %s", url, strerror (errno)); fclose (in); fetchFreeURL (fetch_url); return false; } if (fread (*buf, *buflen, 1, in) != 1) { ucl_create_err (err, "cannot read URL %s: %s", url, strerror (errno)); fclose (in); fetchFreeURL (fetch_url); return false; } fetchFreeURL (fetch_url); return true; #elif defined(CURL_FOUND) CURL *curl; int r; struct ucl_curl_cbdata cbdata; curl = curl_easy_init (); if (curl == NULL) { ucl_create_err (err, "CURL interface is broken"); return false; } if ((r = curl_easy_setopt (curl, CURLOPT_URL, url)) != CURLE_OK) { ucl_create_err (err, "invalid URL %s: %s", url, curl_easy_strerror (r)); curl_easy_cleanup (curl); return false; } curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ucl_curl_write_callback); cbdata.buf = *buf; cbdata.buflen = *buflen; curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbdata); if ((r = curl_easy_perform (curl)) != CURLE_OK) { if (!must_exist) { ucl_create_err (err, "error fetching URL %s: %s", url, curl_easy_strerror (r)); } curl_easy_cleanup (curl); if (cbdata.buf) { free (cbdata.buf); } return false; } *buf = cbdata.buf; *buflen = cbdata.buflen; return true; #else ucl_create_err (err, "URL support is disabled"); return false; #endif } /** * Fetch a file and save results to the memory buffer * @param filename filename to fetch * @param len length of filename * @param buf target buffer * @param buflen target length * @return */ static bool ucl_fetch_file (const unsigned char *filename, unsigned char **buf, size_t *buflen, UT_string **err, bool must_exist) { int fd; struct stat st; if (stat (filename, &st) == -1 || !S_ISREG (st.st_mode)) { if (must_exist) { ucl_create_err (err, "cannot stat file %s: %s", filename, strerror (errno)); } return false; } if (st.st_size == 0) { /* Do not map empty files */ *buf = ""; *buflen = 0; } else { if ((fd = open (filename, O_RDONLY)) == -1) { ucl_create_err (err, "cannot open file %s: %s", filename, strerror (errno)); return false; } if ((*buf = ucl_mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { close (fd); ucl_create_err (err, "cannot mmap file %s: %s", filename, strerror (errno)); return false; } *buflen = st.st_size; close (fd); } return true; } #if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) static inline bool ucl_sig_check (const unsigned char *data, size_t datalen, const unsigned char *sig, size_t siglen, struct ucl_parser *parser) { struct ucl_pubkey *key; char dig[EVP_MAX_MD_SIZE]; unsigned int diglen; EVP_PKEY_CTX *key_ctx; EVP_MD_CTX *sign_ctx = NULL; sign_ctx = EVP_MD_CTX_create (); LL_FOREACH (parser->keys, key) { key_ctx = EVP_PKEY_CTX_new (key->key, NULL); if (key_ctx != NULL) { if (EVP_PKEY_verify_init (key_ctx) <= 0) { EVP_PKEY_CTX_free (key_ctx); continue; } if (EVP_PKEY_CTX_set_rsa_padding (key_ctx, RSA_PKCS1_PADDING) <= 0) { EVP_PKEY_CTX_free (key_ctx); continue; } if (EVP_PKEY_CTX_set_signature_md (key_ctx, EVP_sha256 ()) <= 0) { EVP_PKEY_CTX_free (key_ctx); continue; } EVP_DigestInit (sign_ctx, EVP_sha256 ()); EVP_DigestUpdate (sign_ctx, data, datalen); EVP_DigestFinal (sign_ctx, dig, &diglen); if (EVP_PKEY_verify (key_ctx, sig, siglen, dig, diglen) == 1) { EVP_MD_CTX_destroy (sign_ctx); EVP_PKEY_CTX_free (key_ctx); return true; } EVP_PKEY_CTX_free (key_ctx); } } EVP_MD_CTX_destroy (sign_ctx); return false; } #endif /** * Include an url to configuration * @param data * @param len * @param parser * @param err * @return */ static bool ucl_include_url (const unsigned char *data, size_t len, struct ucl_parser *parser, bool check_signature, bool must_exist) { bool res; unsigned char *buf = NULL; size_t buflen = 0; struct ucl_chunk *chunk; char urlbuf[PATH_MAX]; int prev_state; snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data); if (!ucl_fetch_url (urlbuf, &buf, &buflen, &parser->err, must_exist)) { return (!must_exist || false); } if (check_signature) { #if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) unsigned char *sigbuf = NULL; size_t siglen = 0; /* We need to check signature first */ snprintf (urlbuf, sizeof (urlbuf), "%.*s.sig", (int)len, data); if (!ucl_fetch_url (urlbuf, &sigbuf, &siglen, &parser->err, true)) { return false; } if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) { ucl_create_err (&parser->err, "cannot verify url %s: %s", urlbuf, ERR_error_string (ERR_get_error (), NULL)); if (siglen > 0) { ucl_munmap (sigbuf, siglen); } return false; } if (siglen > 0) { ucl_munmap (sigbuf, siglen); } #endif } prev_state = parser->state; parser->state = UCL_STATE_INIT; res = ucl_parser_add_chunk (parser, buf, buflen); if (res == true) { /* Remove chunk from the stack */ chunk = parser->chunks; if (chunk != NULL) { parser->chunks = chunk->next; UCL_FREE (sizeof (struct ucl_chunk), chunk); } } parser->state = prev_state; free (buf); return res; } /** * Include a file to configuration * @param data * @param len * @param parser * @param err * @return */ static bool ucl_include_file (const unsigned char *data, size_t len, struct ucl_parser *parser, bool check_signature, bool must_exist) { bool res; struct ucl_chunk *chunk; unsigned char *buf = NULL; size_t buflen; char filebuf[PATH_MAX], realbuf[PATH_MAX]; int prev_state; snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data); if (ucl_realpath (filebuf, realbuf) == NULL) { if (!must_exist) { return true; } ucl_create_err (&parser->err, "cannot open file %s: %s", filebuf, strerror (errno)); return false; } if (!ucl_fetch_file (realbuf, &buf, &buflen, &parser->err, must_exist)) { return (!must_exist || false); } if (check_signature) { #if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) unsigned char *sigbuf = NULL; size_t siglen = 0; /* We need to check signature first */ snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf); if (!ucl_fetch_file (filebuf, &sigbuf, &siglen, &parser->err, true)) { return false; } if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) { ucl_create_err (&parser->err, "cannot verify file %s: %s", filebuf, ERR_error_string (ERR_get_error (), NULL)); if (siglen > 0) { ucl_munmap (sigbuf, siglen); } return false; } if (siglen > 0) { ucl_munmap (sigbuf, siglen); } #endif } ucl_parser_set_filevars (parser, realbuf, false); prev_state = parser->state; parser->state = UCL_STATE_INIT; res = ucl_parser_add_chunk (parser, buf, buflen); if (res == true) { /* Remove chunk from the stack */ chunk = parser->chunks; if (chunk != NULL) { parser->chunks = chunk->next; UCL_FREE (sizeof (struct ucl_chunk), chunk); } } parser->state = prev_state; if (buflen > 0) { ucl_munmap (buf, buflen); } return res; } /** * Handle include macro * @param data include data * @param len length of data * @param ud user data * @param err error ptr * @return */ UCL_EXTERN bool ucl_include_handler (const unsigned char *data, size_t len, void* ud) { struct ucl_parser *parser = ud; if (*data == '/' || *data == '.') { /* Try to load a file */ return ucl_include_file (data, len, parser, false, true); } return ucl_include_url (data, len, parser, false, true); } /** * Handle includes macro * @param data include data * @param len length of data * @param ud user data * @param err error ptr * @return */ UCL_EXTERN bool ucl_includes_handler (const unsigned char *data, size_t len, void* ud) { struct ucl_parser *parser = ud; if (*data == '/' || *data == '.') { /* Try to load a file */ return ucl_include_file (data, len, parser, true, true); } return ucl_include_url (data, len, parser, true, true); } UCL_EXTERN bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud) { struct ucl_parser *parser = ud; if (*data == '/' || *data == '.') { /* Try to load a file */ return ucl_include_file (data, len, parser, false, false); } return ucl_include_url (data, len, parser, false, false); } UCL_EXTERN bool ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool need_expand) { char realbuf[PATH_MAX], *curdir; if (filename != NULL) { if (need_expand) { if (ucl_realpath (filename, realbuf) == NULL) { return false; } } else { ucl_strlcpy (realbuf, filename, sizeof (realbuf)); } /* Define variables */ ucl_parser_register_variable (parser, "FILENAME", realbuf); curdir = dirname (realbuf); ucl_parser_register_variable (parser, "CURDIR", curdir); } else { /* Set everything from the current dir */ curdir = getcwd (realbuf, sizeof (realbuf)); ucl_parser_register_variable (parser, "FILENAME", "undef"); ucl_parser_register_variable (parser, "CURDIR", curdir); } return true; } UCL_EXTERN bool ucl_parser_add_file (struct ucl_parser *parser, const char *filename) { unsigned char *buf; size_t len; bool ret; char realbuf[PATH_MAX]; if (ucl_realpath (filename, realbuf) == NULL) { ucl_create_err (&parser->err, "cannot open file %s: %s", filename, strerror (errno)); return false; } if (!ucl_fetch_file (realbuf, &buf, &len, &parser->err, true)) { return false; } ucl_parser_set_filevars (parser, realbuf, false); ret = ucl_parser_add_chunk (parser, buf, len); if (len > 0) { ucl_munmap (buf, len); } return ret; } size_t ucl_strlcpy (char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') { break; } } } if (n == 0 && siz != 0) { *d = '\0'; } return (s - src - 1); /* count does not include NUL */ } size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz) { memcpy (dst, src, siz - 1); dst[siz - 1] = '\0'; return siz - 1; } size_t ucl_strlcpy_tolower (char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = tolower (*s++)) == '\0') { break; } } } if (n == 0 && siz != 0) { *d = '\0'; } return (s - src); /* count does not include NUL */ } ucl_object_t * ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags flags) { ucl_object_t *obj; const char *start, *end, *p, *pos; char *dst, *d; size_t escaped_len; if (str == NULL) { return NULL; } obj = ucl_object_new (); if (obj) { if (len == 0) { len = strlen (str); } if (flags & UCL_STRING_TRIM) { /* Skip leading spaces */ for (start = str; (size_t)(start - str) < len; start ++) { if (!ucl_test_character (*start, UCL_CHARACTER_WHITESPACE_UNSAFE)) { break; } } /* Skip trailing spaces */ for (end = str + len - 1; end > start; end --) { if (!ucl_test_character (*end, UCL_CHARACTER_WHITESPACE_UNSAFE)) { break; } } end ++; } else { start = str; end = str + len; } obj->type = UCL_STRING; if (flags & UCL_STRING_ESCAPE) { for (p = start, escaped_len = 0; p < end; p ++, escaped_len ++) { if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) { escaped_len ++; } } dst = malloc (escaped_len + 1); if (dst != NULL) { for (p = start, d = dst; p < end; p ++, d ++) { if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) { switch (*p) { case '\n': *d++ = '\\'; *d = 'n'; break; case '\r': *d++ = '\\'; *d = 'r'; break; case '\b': *d++ = '\\'; *d = 'b'; break; case '\t': *d++ = '\\'; *d = 't'; break; case '\f': *d++ = '\\'; *d = 'f'; break; case '\\': *d++ = '\\'; *d = '\\'; break; case '"': *d++ = '\\'; *d = '"'; break; } } else { *d = *p; } } *d = '\0'; obj->value.sv = dst; obj->trash_stack[UCL_TRASH_VALUE] = dst; obj->len = escaped_len; } } else { dst = malloc (end - start + 1); if (dst != NULL) { ucl_strlcpy_unsafe (dst, start, end - start + 1); obj->value.sv = dst; obj->trash_stack[UCL_TRASH_VALUE] = dst; obj->len = end - start; } } if ((flags & UCL_STRING_PARSE) && dst != NULL) { /* Parse what we have */ if (flags & UCL_STRING_PARSE_BOOLEAN) { if (!ucl_maybe_parse_boolean (obj, dst, obj->len) && (flags & UCL_STRING_PARSE_NUMBER)) { ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos, flags & UCL_STRING_PARSE_DOUBLE, flags & UCL_STRING_PARSE_BYTES, flags & UCL_STRING_PARSE_TIME); } } else { ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos, flags & UCL_STRING_PARSE_DOUBLE, flags & UCL_STRING_PARSE_BYTES, flags & UCL_STRING_PARSE_TIME); } } } return obj; } static bool ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key, bool merge, bool replace) { ucl_object_t *found, *tmp; const ucl_object_t *cur; ucl_object_iter_t it = NULL; const char *p; int ret = true; if (elt == NULL || key == NULL) { return false; } if (top == NULL) { return false; } if (top->type != UCL_OBJECT) { /* It is possible to convert NULL type to an object */ if (top->type == UCL_NULL) { top->type = UCL_OBJECT; } else { /* Refuse converting of other object types */ return false; } } if (top->value.ov == NULL) { top->value.ov = ucl_hash_create (); } if (keylen == 0) { keylen = strlen (key); } for (p = key; p < key + keylen; p ++) { if (ucl_test_character (*p, UCL_CHARACTER_UCL_UNSAFE)) { elt->flags |= UCL_OBJECT_NEED_KEY_ESCAPE; break; } } elt->key = key; elt->keylen = keylen; if (copy_key) { ucl_copy_key_trash (elt); } found = __DECONST (ucl_object_t *, ucl_hash_search_obj (top->value.ov, elt)); if (!found) { top->value.ov = ucl_hash_insert_object (top->value.ov, elt); DL_APPEND (found, elt); top->len ++; if (replace) { ret = false; } } else { if (replace) { ucl_hash_delete (top->value.ov, found); ucl_object_unref (found); top->value.ov = ucl_hash_insert_object (top->value.ov, elt); found = NULL; DL_APPEND (found, elt); } else if (merge) { if (found->type != UCL_OBJECT && elt->type == UCL_OBJECT) { /* Insert old elt to new one */ ucl_object_insert_key_common (elt, found, found->key, found->keylen, copy_key, false, false); ucl_hash_delete (top->value.ov, found); top->value.ov = ucl_hash_insert_object (top->value.ov, elt); } else if (found->type == UCL_OBJECT && elt->type != UCL_OBJECT) { /* Insert new to old */ ucl_object_insert_key_common (found, elt, elt->key, elt->keylen, copy_key, false, false); } else if (found->type == UCL_OBJECT && elt->type == UCL_OBJECT) { /* Mix two hashes */ while ((cur = ucl_iterate_object (elt, &it, true)) != NULL) { tmp = ucl_object_ref (cur); ucl_object_insert_key_common (found, tmp, cur->key, cur->keylen, copy_key, false, false); } ucl_object_unref (elt); } else { /* Just make a list of scalars */ DL_APPEND (found, elt); } } else { DL_APPEND (found, elt); } } return ret; } bool ucl_object_delete_keyl (ucl_object_t *top, const char *key, size_t keylen) { ucl_object_t *found; if (top == NULL || key == NULL) { return false; } found = __DECONST (ucl_object_t *, ucl_object_find_keyl (top, key, keylen)); if (found == NULL) { return false; } ucl_hash_delete (top->value.ov, found); ucl_object_unref (found); top->len --; return true; } bool ucl_object_delete_key (ucl_object_t *top, const char *key) { return ucl_object_delete_keyl (top, key, strlen(key)); } ucl_object_t* ucl_object_pop_keyl (ucl_object_t *top, const char *key, size_t keylen) { const ucl_object_t *found; if (top == NULL || key == NULL) { return false; } found = ucl_object_find_keyl (top, key, keylen); if (found == NULL) { return NULL; } ucl_hash_delete (top->value.ov, found); top->len --; return __DECONST (ucl_object_t *, found); } ucl_object_t* ucl_object_pop_key (ucl_object_t *top, const char *key) { return ucl_object_pop_keyl (top, key, strlen(key)); } bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key) { return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, false); } bool ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key) { return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, true, false); } bool ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key) { return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, true); } const ucl_object_t * ucl_object_find_keyl (const ucl_object_t *obj, const char *key, size_t klen) { const ucl_object_t *ret; ucl_object_t srch; if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) { return NULL; } srch.key = key; srch.keylen = klen; ret = ucl_hash_search_obj (obj->value.ov, &srch); return ret; } const ucl_object_t * ucl_object_find_key (const ucl_object_t *obj, const char *key) { - size_t klen; - const ucl_object_t *ret; - ucl_object_t srch; - - if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) { + if (key == NULL) return NULL; - } - - klen = strlen (key); - srch.key = key; - srch.keylen = klen; - ret = ucl_hash_search_obj (obj->value.ov, &srch); - return ret; + return ucl_object_find_keyl (obj, key, strlen(key)); } const ucl_object_t* ucl_iterate_object (const ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_values) { const ucl_object_t *elt; if (obj == NULL || iter == NULL) { return NULL; } if (expand_values) { switch (obj->type) { case UCL_OBJECT: return (const ucl_object_t*)ucl_hash_iterate (obj->value.ov, iter); break; case UCL_ARRAY: elt = *iter; if (elt == NULL) { elt = obj->value.av; if (elt == NULL) { return NULL; } } else if (elt == obj->value.av) { return NULL; } *iter = elt->next ? elt->next : obj->value.av; return elt; default: /* Go to linear iteration */ break; } } /* Treat everything as a linear list */ elt = *iter; if (elt == NULL) { elt = obj; if (elt == NULL) { return NULL; } } else if (elt == obj) { return NULL; } *iter = __DECONST (void *, elt->next ? elt->next : obj); return elt; /* Not reached */ return NULL; } +const ucl_object_t * +ucl_lookup_path (const ucl_object_t *top, const char *path_in) { + const ucl_object_t *o = NULL, *found; + const char *p, *c; + char *err_str; + unsigned index; + + if (path_in == NULL || top == NULL) { + return NULL; + } + + found = NULL; + p = path_in; + + /* Skip leading dots */ + while (*p == '.') { + p ++; + } + + c = p; + while (*p != '\0') { + p ++; + if (*p == '.' || *p == '\0') { + if (p > c) { + switch (top->type) { + case UCL_ARRAY: + /* Key should be an int */ + index = strtoul (c, &err_str, 10); + if (err_str != NULL && (*err_str != '.' && *err_str != '\0')) { + return NULL; + } + o = ucl_array_find_index (top, index); + break; + default: + o = ucl_object_find_keyl (top, c, p - c); + break; + } + if (o == NULL) { + return NULL; + } + top = o; + } + if (*p != '\0') { + c = p + 1; + } + } + } + found = o; + + return found; +} + ucl_object_t * ucl_object_new (void) { ucl_object_t *new; new = malloc (sizeof (ucl_object_t)); if (new != NULL) { memset (new, 0, sizeof (ucl_object_t)); new->ref = 1; new->type = UCL_NULL; } return new; } ucl_object_t * -ucl_object_typed_new (unsigned int type) +ucl_object_typed_new (ucl_type_t type) { ucl_object_t *new; new = malloc (sizeof (ucl_object_t)); if (new != NULL) { memset (new, 0, sizeof (ucl_object_t)); new->ref = 1; new->type = (type <= UCL_NULL ? type : UCL_NULL); } return new; } +ucl_type_t +ucl_object_type (const ucl_object_t *obj) +{ + return obj->type; +} + ucl_object_t* ucl_object_fromstring (const char *str) { return ucl_object_fromstring_common (str, 0, UCL_STRING_ESCAPE); } ucl_object_t * ucl_object_fromlstring (const char *str, size_t len) { return ucl_object_fromstring_common (str, len, UCL_STRING_ESCAPE); } ucl_object_t * ucl_object_fromint (int64_t iv) { ucl_object_t *obj; obj = ucl_object_new (); if (obj != NULL) { obj->type = UCL_INT; obj->value.iv = iv; } return obj; } ucl_object_t * ucl_object_fromdouble (double dv) { ucl_object_t *obj; obj = ucl_object_new (); if (obj != NULL) { obj->type = UCL_FLOAT; obj->value.dv = dv; } return obj; } ucl_object_t* ucl_object_frombool (bool bv) { ucl_object_t *obj; obj = ucl_object_new (); if (obj != NULL) { obj->type = UCL_BOOLEAN; obj->value.iv = bv; } return obj; } bool ucl_array_append (ucl_object_t *top, ucl_object_t *elt) { ucl_object_t *head; if (elt == NULL || top == NULL) { return false; } head = top->value.av; if (head == NULL) { top->value.av = elt; elt->prev = elt; } else { elt->prev = head->prev; head->prev->next = elt; head->prev = elt; } elt->next = NULL; top->len ++; return true; } bool ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt) { ucl_object_t *head; if (elt == NULL || top == NULL) { return false; } head = top->value.av; if (head == NULL) { top->value.av = elt; elt->prev = elt; } else { elt->prev = head->prev; head->prev = elt; } elt->next = head; top->value.av = elt; top->len ++; return true; } ucl_object_t * ucl_array_delete (ucl_object_t *top, ucl_object_t *elt) { ucl_object_t *head; if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) { return NULL; } head = top->value.av; if (elt->prev == elt) { top->value.av = NULL; } else if (elt == head) { elt->next->prev = elt->prev; top->value.av = elt->next; } else { elt->prev->next = elt->next; if (elt->next) { elt->next->prev = elt->prev; } else { head->prev = elt->prev; } } elt->next = NULL; elt->prev = elt; top->len --; return elt; } const ucl_object_t * ucl_array_head (const ucl_object_t *top) { if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) { return NULL; } return top->value.av; } const ucl_object_t * ucl_array_tail (const ucl_object_t *top) { if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) { return NULL; } return top->value.av->prev; } ucl_object_t * ucl_array_pop_last (ucl_object_t *top) { return ucl_array_delete (top, __DECONST(ucl_object_t *, ucl_array_tail (top))); } ucl_object_t * ucl_array_pop_first (ucl_object_t *top) { return ucl_array_delete (top, __DECONST(ucl_object_t *, ucl_array_head (top))); } +const ucl_object_t * +ucl_array_find_index (const ucl_object_t *top, unsigned int index) +{ + ucl_object_iter_t it = NULL; + const ucl_object_t *ret; + + if (top == NULL || top->type != UCL_ARRAY || top->len == 0 || + (index + 1) > top->len) { + return NULL; + } + + while ((ret = ucl_iterate_object (top, &it, true)) != NULL) { + if (index == 0) { + return ret; + } + --index; + } + + return NULL; +} + ucl_object_t * ucl_elt_append (ucl_object_t *head, ucl_object_t *elt) { if (head == NULL) { elt->next = NULL; elt->prev = elt; head = elt; } else { elt->prev = head->prev; head->prev->next = elt; head->prev = elt; elt->next = NULL; } return head; } bool ucl_object_todouble_safe (const ucl_object_t *obj, double *target) { if (obj == NULL || target == NULL) { return false; } switch (obj->type) { case UCL_INT: *target = obj->value.iv; /* Probaly could cause overflow */ break; case UCL_FLOAT: case UCL_TIME: *target = obj->value.dv; break; default: return false; } return true; } double ucl_object_todouble (const ucl_object_t *obj) { double result = 0.; ucl_object_todouble_safe (obj, &result); return result; } bool ucl_object_toint_safe (const ucl_object_t *obj, int64_t *target) { if (obj == NULL || target == NULL) { return false; } switch (obj->type) { case UCL_INT: *target = obj->value.iv; break; case UCL_FLOAT: case UCL_TIME: *target = obj->value.dv; /* Loosing of decimal points */ break; default: return false; } return true; } int64_t ucl_object_toint (const ucl_object_t *obj) { int64_t result = 0; ucl_object_toint_safe (obj, &result); return result; } bool ucl_object_toboolean_safe (const ucl_object_t *obj, bool *target) { if (obj == NULL || target == NULL) { return false; } switch (obj->type) { case UCL_BOOLEAN: *target = (obj->value.iv == true); break; default: return false; } return true; } bool ucl_object_toboolean (const ucl_object_t *obj) { bool result = false; ucl_object_toboolean_safe (obj, &result); return result; } bool ucl_object_tostring_safe (const ucl_object_t *obj, const char **target) { if (obj == NULL || target == NULL) { return false; } switch (obj->type) { case UCL_STRING: *target = ucl_copy_value_trash (obj); break; default: return false; } return true; } const char * ucl_object_tostring (const ucl_object_t *obj) { const char *result = NULL; ucl_object_tostring_safe (obj, &result); return result; } const char * ucl_object_tostring_forced (const ucl_object_t *obj) { return ucl_copy_value_trash (obj); } bool ucl_object_tolstring_safe (const ucl_object_t *obj, const char **target, size_t *tlen) { if (obj == NULL || target == NULL) { return false; } switch (obj->type) { case UCL_STRING: *target = obj->value.sv; if (tlen != NULL) { *tlen = obj->len; } break; default: return false; } return true; } const char * ucl_object_tolstring (const ucl_object_t *obj, size_t *tlen) { const char *result = NULL; ucl_object_tolstring_safe (obj, &result, tlen); return result; } const char * ucl_object_key (const ucl_object_t *obj) { return ucl_copy_key_trash (obj); } const char * ucl_object_keyl (const ucl_object_t *obj, size_t *len) { if (len == NULL || obj == NULL) { return NULL; } *len = obj->keylen; return obj->key; } ucl_object_t * ucl_object_ref (const ucl_object_t *obj) { ucl_object_t *res = NULL; if (obj != NULL) { res = __DECONST (ucl_object_t *, obj); #ifdef HAVE_ATOMIC_BUILTINS (void)__sync_add_and_fetch (&res->ref, 1); #else res->ref ++; #endif } return res; } void ucl_object_unref (ucl_object_t *obj) { if (obj != NULL) { #ifdef HAVE_ATOMIC_BUILTINS unsigned int rc = __sync_sub_and_fetch (&obj->ref, 1); if (rc == 0) { #else if (--obj->ref == 0) { #endif ucl_object_free_internal (obj, true, ucl_object_dtor_unref); } } } int ucl_object_compare (const ucl_object_t *o1, const ucl_object_t *o2) { const ucl_object_t *it1, *it2; ucl_object_iter_t iter = NULL; int ret = 0; if (o1->type != o2->type) { return (o1->type) - (o2->type); } switch (o1->type) { case UCL_STRING: if (o1->len == o2->len) { ret = strcmp (ucl_object_tostring(o1), ucl_object_tostring(o2)); } else { ret = o1->len - o2->len; } break; case UCL_FLOAT: case UCL_INT: case UCL_TIME: ret = ucl_object_todouble (o1) - ucl_object_todouble (o2); break; case UCL_BOOLEAN: ret = ucl_object_toboolean (o1) - ucl_object_toboolean (o2); break; case UCL_ARRAY: if (o1->len == o2->len) { it1 = o1->value.av; it2 = o2->value.av; /* Compare all elements in both arrays */ while (it1 != NULL && it2 != NULL) { ret = ucl_object_compare (it1, it2); if (ret != 0) { break; } it1 = it1->next; it2 = it2->next; } } else { ret = o1->len - o2->len; } break; case UCL_OBJECT: if (o1->len == o2->len) { while ((it1 = ucl_iterate_object (o1, &iter, true)) != NULL) { it2 = ucl_object_find_key (o2, ucl_object_key (it1)); if (it2 == NULL) { ret = 1; break; } ret = ucl_object_compare (it1, it2); if (ret != 0) { break; } } } else { ret = o1->len - o2->len; } break; default: ret = 0; break; } return ret; } void ucl_object_array_sort (ucl_object_t *ar, int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2)) { if (cmp == NULL || ar == NULL || ar->type != UCL_ARRAY) { return; } DL_SORT (ar->value.av, cmp); } diff --git a/tests/test_generate.c b/tests/test_generate.c index 2b1bf8d73a3f..5c130e67499a 100644 --- a/tests/test_generate.c +++ b/tests/test_generate.c @@ -1,132 +1,150 @@ /* Copyright (c) 2013, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 ''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 AUTHOR 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 #include #include "ucl.h" int main (int argc, char **argv) { ucl_object_t *obj, *cur, *ar, *ref; + const ucl_object_t *found; FILE *out; unsigned char *emitted; const char *fname_out = NULL; int ret = 0; switch (argc) { case 2: fname_out = argv[1]; break; } if (fname_out != NULL) { out = fopen (fname_out, "w"); if (out == NULL) { exit (-errno); } } else { out = stdout; } obj = ucl_object_typed_new (UCL_OBJECT); /* Create some strings */ cur = ucl_object_fromstring_common (" test string ", 0, UCL_STRING_TRIM); ucl_object_insert_key (obj, cur, "key1", 0, false); cur = ucl_object_fromstring_common (" test \nstring\n ", 0, UCL_STRING_TRIM | UCL_STRING_ESCAPE); ucl_object_insert_key (obj, cur, "key2", 0, false); cur = ucl_object_fromstring_common (" test string \n", 0, 0); ucl_object_insert_key (obj, cur, "key3", 0, false); /* Array of numbers */ ar = ucl_object_typed_new (UCL_ARRAY); cur = ucl_object_fromint (10); ucl_array_append (ar, cur); cur = ucl_object_fromdouble (10.1); ucl_array_append (ar, cur); cur = ucl_object_fromdouble (9.999); ucl_array_prepend (ar, cur); /* Removing from an array */ cur = ucl_object_fromdouble (1.0); ucl_array_append (ar, cur); cur = ucl_array_delete (ar, cur); assert (ucl_object_todouble (cur) == 1.0); ucl_object_unref (cur); cur = ucl_object_fromdouble (2.0); ucl_array_append (ar, cur); cur = ucl_array_pop_last (ar); assert (ucl_object_todouble (cur) == 2.0); ucl_object_unref (cur); cur = ucl_object_fromdouble (3.0); ucl_array_prepend (ar, cur); cur = ucl_array_pop_first (ar); assert (ucl_object_todouble (cur) == 3.0); ucl_object_unref (cur); ucl_object_insert_key (obj, ar, "key4", 0, false); cur = ucl_object_frombool (true); /* Ref object to test refcounts */ ref = ucl_object_ref (cur); ucl_object_insert_key (obj, cur, "key4", 0, false); /* Empty strings */ cur = ucl_object_fromstring_common (" ", 0, UCL_STRING_TRIM); ucl_object_insert_key (obj, cur, "key5", 0, false); cur = ucl_object_fromstring_common ("", 0, UCL_STRING_ESCAPE); ucl_object_insert_key (obj, cur, "key6", 0, false); cur = ucl_object_fromstring_common (" \n", 0, UCL_STRING_ESCAPE); ucl_object_insert_key (obj, cur, "key7", 0, false); /* Numbers and booleans */ cur = ucl_object_fromstring_common ("1mb", 0, UCL_STRING_ESCAPE | UCL_STRING_PARSE); ucl_object_insert_key (obj, cur, "key8", 0, false); cur = ucl_object_fromstring_common ("3.14", 0, UCL_STRING_PARSE); ucl_object_insert_key (obj, cur, "key9", 0, false); cur = ucl_object_fromstring_common ("true", 0, UCL_STRING_PARSE); ucl_object_insert_key (obj, cur, "key10", 0, false); cur = ucl_object_fromstring_common (" off ", 0, UCL_STRING_PARSE | UCL_STRING_TRIM); ucl_object_insert_key (obj, cur, "key11", 0, false); cur = ucl_object_fromstring_common ("gslin@gslin.org", 0, UCL_STRING_PARSE_INT); ucl_object_insert_key (obj, cur, "key12", 0, false); cur = ucl_object_fromstring_common ("#test", 0, UCL_STRING_PARSE_INT); ucl_object_insert_key (obj, cur, "key13", 0, false); cur = ucl_object_frombool (true); ucl_object_insert_key (obj, cur, "k=3", 0, false); + /* Try to find using path */ + /* Should exist */ + found = ucl_lookup_path (obj, "key4.1"); + assert (found != NULL && ucl_object_toint (found) == 10); + /* . should be ignored */ + found = ucl_lookup_path (obj, ".key4.1"); + assert (found != NULL && ucl_object_toint (found) == 10); + /* moar dots... */ + found = ucl_lookup_path (obj, ".key4........1..."); + assert (found != NULL && ucl_object_toint (found) == 10); + /* No such index */ + found = ucl_lookup_path (obj, ".key4.3"); + assert (found == NULL); + /* No such key */ + found = ucl_lookup_path (obj, "key9..key1"); + assert (found == NULL); + emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG); fprintf (out, "%s\n", emitted); ucl_object_unref (obj); if (emitted != NULL) { free (emitted); } fclose (out); /* Ref should still be accessible */ ref->value.iv = 100500; ucl_object_unref (ref); return ret; }