diff options
| author | Baptiste Daroussin <bapt@FreeBSD.org> | 2014-03-22 17:22:29 +0000 | 
|---|---|---|
| committer | Baptiste Daroussin <bapt@FreeBSD.org> | 2014-03-22 17:22:29 +0000 | 
| commit | bfc6d68337150c52c8ce2dc6a7d19a9d1bd92795 (patch) | |
| tree | 7f1e53bb95f9bc2624280f027e3f0aeb280f9667 /src | |
| parent | 2ae6de7c252d1781a158740c2481220dfdd6cc77 (diff) | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 23 | ||||
| -rw-r--r-- | src/tree.h | 212 | ||||
| -rw-r--r-- | src/ucl_emitter.c | 12 | ||||
| -rw-r--r-- | src/ucl_hash.c | 1 | ||||
| -rw-r--r-- | src/ucl_internal.h | 56 | ||||
| -rw-r--r-- | src/ucl_parser.c | 85 | ||||
| -rw-r--r-- | src/ucl_schema.c | 1008 | ||||
| -rw-r--r-- | src/ucl_util.c | 686 | 
8 files changed, 2014 insertions, 69 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000000000..499d640508a23 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,23 @@ +libucl_common_cflags=	-I$(top_srcdir)/src \ +			-I$(top_srcdir)/include \ +			-I$(top_srcdir)/uthash \ +			-Wall -W -Wno-unused-parameter -Wno-pointer-sign +lib_LTLIBRARIES=	libucl.la +libucl_la_SOURCES=	ucl_emitter.c \ +					ucl_hash.c \ +					ucl_parser.c \ +					ucl_schema.c \ +					ucl_util.c \ +					xxhash.c +libucl_la_CFLAGS=	$(libucl_common_cflags) \ +					@CURL_CFLAGS@ +libucl_la_LDFLAGS = -version-info @SO_VERSION@ +libucl_la_LIBADD=	@LIBFETCH_LIBS@ \ +					@CURL_LIBS@ + +include_HEADERS=	$(top_srcdir)/include/ucl.h +noinst_HEADERS=	ucl_internal.h \ +				xxhash.h \ +				ucl_hash.h \ +				ucl_chartable.h \ +				tree.h diff --git a/src/tree.h b/src/tree.h new file mode 100644 index 0000000000000..cee9373698c2f --- /dev/null +++ b/src/tree.h @@ -0,0 +1,212 @@ +/* tree.h -- AVL trees (in the spirit of BSD's 'queue.h')	-*- C -*-	*/ + +/* Copyright (c) 2005 Ian Piumarta + *  + * All rights reserved. + *  + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED 'AS IS'.  USE ENTIRELY AT YOUR OWN RISK. + */ + +/* This file defines an AVL balanced binary tree [Georgii M. Adelson-Velskii and + * Evgenii M. Landis, 'An algorithm for the organization of information', + * Doklady Akademii Nauk SSSR, 146:263-266, 1962 (Russian).  Also in Myron + * J. Ricci (trans.), Soviet Math, 3:1259-1263, 1962 (English)]. + *  + * An AVL tree is headed by pointers to the root node and to a function defining + * the ordering relation between nodes.  Each node contains an arbitrary payload + * plus three fields per tree entry: the depth of the subtree for which it forms + * the root and two pointers to child nodes (singly-linked for minimum space, at + * the expense of direct access to the parent node given a pointer to one of the + * children).  The tree is rebalanced after every insertion or removal.  The + * tree may be traversed in two directions: forward (in-order left-to-right) and + * reverse (in-order, right-to-left). + *  + * Because of the recursive nature of many of the operations on trees it is + * necessary to define a number of helper functions for each type of tree node. + * The macro TREE_DEFINE(node_tag, entry_name) defines these functions with + * unique names according to the node_tag.  This macro should be invoked, + * thereby defining the necessary functions, once per node tag in the program. + *  + * For details on the use of these macros, see the tree(3) manual page. + */ + +#ifndef __tree_h +#define __tree_h + + +#define TREE_DELTA_MAX	1 + +#define TREE_ENTRY(type)			\ +  struct {					\ +    struct type	*avl_left;			\ +    struct type	*avl_right;			\ +    int		 avl_height;			\ +  } + +#define TREE_HEAD(name, type)				\ +  struct name {						\ +    struct type *th_root;				\ +    int  (*th_cmp)(struct type *lhs, struct type *rhs);	\ +  } + +#define TREE_INITIALIZER(cmp) { 0, cmp } + +#define TREE_DELTA(self, field)								\ +  (( (((self)->field.avl_left)  ? (self)->field.avl_left->field.avl_height  : 0))	\ +   - (((self)->field.avl_right) ? (self)->field.avl_right->field.avl_height : 0)) + +/* Recursion prevents the following from being defined as macros. */ + +#define TREE_DEFINE(node, field)									\ +													\ +  struct node *TREE_BALANCE_##node##_##field(struct node *);						\ +													\ +  struct node *TREE_ROTL_##node##_##field(struct node *self)						\ +  {													\ +    struct node *r= self->field.avl_right;								\ +    self->field.avl_right= r->field.avl_left;								\ +    r->field.avl_left= TREE_BALANCE_##node##_##field(self);						\ +    return TREE_BALANCE_##node##_##field(r);								\ +  }													\ +													\ +  struct node *TREE_ROTR_##node##_##field(struct node *self)						\ +  {													\ +    struct node *l= self->field.avl_left;								\ +    self->field.avl_left= l->field.avl_right;								\ +    l->field.avl_right= TREE_BALANCE_##node##_##field(self);						\ +    return TREE_BALANCE_##node##_##field(l);								\ +  }													\ +													\ +  struct node *TREE_BALANCE_##node##_##field(struct node *self)						\ +  {													\ +    int delta= TREE_DELTA(self, field);									\ +													\ +    if (delta < -TREE_DELTA_MAX)									\ +      {													\ +	if (TREE_DELTA(self->field.avl_right, field) > 0)						\ +	  self->field.avl_right= TREE_ROTR_##node##_##field(self->field.avl_right);			\ +	return TREE_ROTL_##node##_##field(self);							\ +      }													\ +    else if (delta > TREE_DELTA_MAX)									\ +      {													\ +	if (TREE_DELTA(self->field.avl_left, field) < 0)						\ +	  self->field.avl_left= TREE_ROTL_##node##_##field(self->field.avl_left);			\ +	return TREE_ROTR_##node##_##field(self);							\ +      }													\ +    self->field.avl_height= 0;										\ +    if (self->field.avl_left && (self->field.avl_left->field.avl_height > self->field.avl_height))	\ +      self->field.avl_height= self->field.avl_left->field.avl_height;					\ +    if (self->field.avl_right && (self->field.avl_right->field.avl_height > self->field.avl_height))	\ +      self->field.avl_height= self->field.avl_right->field.avl_height;					\ +    self->field.avl_height += 1;									\ +    return self;											\ +  }													\ +													\ +  struct node *TREE_INSERT_##node##_##field								\ +    (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs))		\ +  {													\ +    if (!self)												\ +      return elm;											\ +    if (compare(elm, self) < 0)										\ +      self->field.avl_left= TREE_INSERT_##node##_##field(self->field.avl_left, elm, compare);		\ +    else												\ +      self->field.avl_right= TREE_INSERT_##node##_##field(self->field.avl_right, elm, compare);		\ +    return TREE_BALANCE_##node##_##field(self);								\ +  }													\ +													\ +  struct node *TREE_FIND_##node##_##field								\ +    (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs))		\ +  {													\ +    if (!self)												\ +      return 0;												\ +    if (compare(elm, self) == 0)									\ +      return self;											\ +    if (compare(elm, self) < 0)										\ +      return TREE_FIND_##node##_##field(self->field.avl_left, elm, compare);				\ +    else												\ +      return TREE_FIND_##node##_##field(self->field.avl_right, elm, compare);				\ +  }													\ +													\ +  struct node *TREE_MOVE_RIGHT(struct node *self, struct node *rhs)					\ +  {													\ +    if (!self)												\ +      return rhs;											\ +    self->field.avl_right= TREE_MOVE_RIGHT(self->field.avl_right, rhs);					\ +    return TREE_BALANCE_##node##_##field(self);								\ +  }													\ +													\ +  struct node *TREE_REMOVE_##node##_##field								\ +    (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs))		\ +  {													\ +    if (!self) return 0;										\ +													\ +    if (compare(elm, self) == 0)									\ +      {													\ +	struct node *tmp= TREE_MOVE_RIGHT(self->field.avl_left, self->field.avl_right);			\ +	self->field.avl_left= 0;									\ +	self->field.avl_right= 0;									\ +	return tmp;											\ +      }													\ +    if (compare(elm, self) < 0)										\ +      self->field.avl_left= TREE_REMOVE_##node##_##field(self->field.avl_left, elm, compare);		\ +    else												\ +      self->field.avl_right= TREE_REMOVE_##node##_##field(self->field.avl_right, elm, compare);		\ +    return TREE_BALANCE_##node##_##field(self);								\ +  }													\ +													\ +  void TREE_FORWARD_APPLY_ALL_##node##_##field								\ +    (struct node *self, void (*function)(struct node *node, void *data), void *data)			\ +  {													\ +    if (self)												\ +      {													\ +	TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_left, function, data);			\ +	function(self, data);										\ +	TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_right, function, data);			\ +      }													\ +  }													\ +													\ +  void TREE_REVERSE_APPLY_ALL_##node##_##field								\ +    (struct node *self, void (*function)(struct node *node, void *data), void *data)			\ +  {													\ +    if (self)												\ +      {													\ +	TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_right, function, data);			\ +	function(self, data);										\ +	TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_left, function, data);			\ +      }													\ +  } + +#define TREE_INSERT(head, node, field, elm)						\ +  ((head)->th_root= TREE_INSERT_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_FIND(head, node, field, elm)				\ +  (TREE_FIND_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_REMOVE(head, node, field, elm)						\ +  ((head)->th_root= TREE_REMOVE_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_DEPTH(head, field)			\ +  ((head)->th_root->field.avl_height) + +#define TREE_FORWARD_APPLY(head, node, field, function, data)	\ +  TREE_FORWARD_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_REVERSE_APPLY(head, node, field, function, data)	\ +  TREE_REVERSE_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_INIT(head, cmp) do {		\ +    (head)->th_root= 0;				\ +    (head)->th_cmp= (cmp);			\ +  } while (0) + + +#endif /* __tree_h */ diff --git a/src/ucl_emitter.c b/src/ucl_emitter.c index 51bb09aad7cc3..eb314ac21cd7d 100644 --- a/src/ucl_emitter.c +++ b/src/ucl_emitter.c @@ -21,11 +21,19 @@   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   */ -#include <float.h> -#include <math.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +  #include "ucl.h"  #include "ucl_internal.h"  #include "ucl_chartable.h" +#ifdef HAVE_FLOAT_H +#include <float.h> +#endif +#ifdef HAVE_MATH_H +#include <math.h> +#endif  /**   * @file rcl_emitter.c diff --git a/src/ucl_hash.c b/src/ucl_hash.c index a3711deb85395..0ab962a420097 100644 --- a/src/ucl_hash.c +++ b/src/ucl_hash.c @@ -21,6 +21,7 @@   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   */ +#include "ucl_internal.h"  #include "ucl_hash.h"  #include "utlist.h" diff --git a/src/ucl_internal.h b/src/ucl_internal.h index 49c4aae5d0469..a55d747dbfcce 100644 --- a/src/ucl_internal.h +++ b/src/ucl_internal.h @@ -24,18 +24,67 @@  #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 +#define HAVE_REGEX_H +#endif + +#ifdef HAVE_SYS_TYPES_H  #include <sys/types.h> -#ifndef _WIN32 -#include <sys/mman.h>  #endif + +#ifdef HAVE_SYS_MMAN_H +# ifndef _WIN32 +#  include <sys/mman.h> +# endif +#endif +#ifdef HAVE_SYS_STAT_H  #include <sys/stat.h> +#endif +#ifdef HAVE_SYS_PARAM_H  #include <sys/param.h> +#endif +#ifdef HAVE_LIMITS_H  #include <limits.h> +#endif +#ifdef HAVE_FCNTL_H  #include <fcntl.h> +#endif +#ifdef HAVE_ERRNO_H  #include <errno.h> +#endif +#ifdef HAVE_UNISTD_H  #include <unistd.h> +#endif +#ifdef HAVE_CTYPE_H  #include <ctype.h> +#endif +#ifdef HAVE_STDIO_H +#include <stdio.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif  #include "utlist.h"  #include "utstring.h" @@ -261,7 +310,8 @@ ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t l   * @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); +		const char *start, const char *end, const char **pos, +		bool allow_double, bool number_bytes, bool allow_time);  static inline ucl_object_t * diff --git a/src/ucl_parser.c b/src/ucl_parser.c index 12ebad2741414..a067286df40cc 100644 --- a/src/ucl_parser.c +++ b/src/ucl_parser.c @@ -544,6 +544,10 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra  	}  	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); @@ -554,12 +558,13 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra  int  ucl_maybe_parse_number (ucl_object_t *obj, -		const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes) +		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_date = false, valid_start = false, is_hex = false, +			is_time = false, valid_start = false, is_hex = false,  			is_neg = false;  	double dv = 0;  	int64_t lv = 0; @@ -657,7 +662,8 @@ ucl_maybe_parse_number (ucl_object_t *obj,  	}  	/* Now check endptr */ -	if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0') { +	if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0' || +			ucl_test_character (*endptr, UCL_CHARACTER_WHITESPACE_UNSAFE)) {  		p = endptr;  		goto set_obj;  	} @@ -678,7 +684,7 @@ ucl_maybe_parse_number (ucl_object_t *obj,  						need_double = true;  						dv = lv;  					} -					is_date = true; +					is_time = true;  					if (p[0] == 'm' || p[0] == 'M') {  						dv /= 1000.;  					} @@ -708,7 +714,7 @@ ucl_maybe_parse_number (ucl_object_t *obj,  					p ++;  					goto set_obj;  				} -				else if (end - p >= 3) { +				else if (allow_time && end - p >= 3) {  					if (tolower (p[0]) == 'm' &&  							tolower (p[1]) == 'i' &&  							tolower (p[2]) == 'n') { @@ -717,7 +723,7 @@ ucl_maybe_parse_number (ucl_object_t *obj,  							need_double = true;  							dv = lv;  						} -						is_date = true; +						is_time = true;  						dv *= 60.;  						p += 3;  						goto set_obj; @@ -737,13 +743,14 @@ ucl_maybe_parse_number (ucl_object_t *obj,  			break;  		case 'S':  		case 's': -			if (p == end - 1 || ucl_lex_is_atom_end (p[1])) { +			if (allow_time && +					(p == end - 1 || ucl_lex_is_atom_end (p[1]))) {  				if (!need_double) {  					need_double = true;  					dv = lv;  				}  				p ++; -				is_date = true; +				is_time = true;  				goto set_obj;  			}  			break; @@ -755,12 +762,13 @@ ucl_maybe_parse_number (ucl_object_t *obj,  		case 'W':  		case 'Y':  		case 'y': -			if (p == end - 1 || ucl_lex_is_atom_end (p[1])) { +			if (allow_time && +					(p == end - 1 || ucl_lex_is_atom_end (p[1]))) {  				if (!need_double) {  					need_double = true;  					dv = lv;  				} -				is_date = true; +				is_time = true;  				dv *= ucl_lex_time_multiplier (*p);  				p ++;  				goto set_obj; @@ -773,8 +781,8 @@ ucl_maybe_parse_number (ucl_object_t *obj,  	return EINVAL;  	set_obj: -	if (allow_double && (need_double || is_date)) { -		if (!is_date) { +	if (allow_double && (need_double || is_time)) { +		if (!is_time) {  			obj->type = UCL_FLOAT;  		}  		else { @@ -803,7 +811,8 @@ ucl_lex_number (struct ucl_parser *parser,  	const unsigned char *pos;  	int ret; -	ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos, true, false); +	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; @@ -1308,6 +1317,9 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)  			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; @@ -1316,6 +1328,9 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)  			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; @@ -1608,6 +1623,9 @@ ucl_state_machine (struct ucl_parser *parser)  		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; @@ -1673,7 +1691,11 @@ ucl_state_machine (struct ucl_parser *parser)  			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); +					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; @@ -1787,6 +1809,9 @@ 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); @@ -1808,7 +1833,13 @@ ucl_parser_register_macro (struct ucl_parser *parser, const char *macro,  {  	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); @@ -1851,6 +1882,9 @@ ucl_parser_register_variable (struct ucl_parser *parser, const char *var,  	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); @@ -1873,8 +1907,16 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,  {  	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; @@ -1895,3 +1937,18 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,  	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_schema.c b/src/ucl_schema.c new file mode 100644 index 0000000000000..a74ed0f527a17 --- /dev/null +++ b/src/ucl_schema.c @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2014, 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 BY AUTHOR ''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 "tree.h" +#include "utlist.h" +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif +#ifdef HAVE_STDIO_H +#include <stdio.h> +#endif +#ifdef HAVE_REGEX_H +#include <regex.h> +#endif +#ifdef HAVE_MATH_H +#include <math.h> +#endif + +static bool ucl_schema_validate (ucl_object_t *schema, +		ucl_object_t *obj, bool try_array, +		struct ucl_schema_error *err, +		ucl_object_t *root); + +static bool +ucl_string_to_type (const char *input, ucl_type_t *res) +{ +	if (strcasecmp (input, "object") == 0) { +		*res = UCL_OBJECT; +	} +	else if (strcasecmp (input, "array") == 0) { +		*res = UCL_ARRAY; +	} +	else if (strcasecmp (input, "integer") == 0) { +		*res = UCL_INT; +	} +	else if (strcasecmp (input, "number") == 0) { +		*res = UCL_FLOAT; +	} +	else if (strcasecmp (input, "string") == 0) { +		*res = UCL_STRING; +	} +	else if (strcasecmp (input, "boolean") == 0) { +		*res = UCL_BOOLEAN; +	} +	else if (strcasecmp (input, "null") == 0) { +		*res = UCL_NULL; +	} +	else { +		return false; +	} + +	return true; +} + +static const char * +ucl_object_type_to_string (ucl_type_t type) +{ +	const char *res = "unknown"; + +	switch (type) { +	case UCL_OBJECT: +		res = "object"; +		break; +	case UCL_ARRAY: +		res = "array"; +		break; +	case UCL_INT: +		res = "integer"; +		break; +	case UCL_FLOAT: +	case UCL_TIME: +		res = "number"; +		break; +	case UCL_STRING: +		res = "string"; +		break; +	case UCL_BOOLEAN: +		res = "boolean"; +		break; +	case UCL_NULL: +	case UCL_USERDATA: +		res = "null"; +		break; +	} + +	return res; +} + +/* + * Create validation error + */ +static void +ucl_schema_create_error (struct ucl_schema_error *err, +		enum ucl_schema_error_code code, ucl_object_t *obj, +		const char *fmt, ...) +{ +	va_list va; + +	if (err != NULL) { +		err->code = code; +		err->obj = obj; +		va_start (va, fmt); +		vsnprintf (err->msg, sizeof (err->msg), fmt, va); +		va_end (va); +	} +} + +/* + * Check whether we have a pattern specified + */ +static ucl_object_t * +ucl_schema_test_pattern (ucl_object_t *obj, const char *pattern) +{ +	regex_t reg; +	ucl_object_t *res = NULL, *elt; +	ucl_object_iter_t iter = NULL; + +	if (regcomp (®, pattern, REG_EXTENDED | REG_NOSUB) == 0) { +		while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { +			if (regexec (®, ucl_object_key (elt), 0, NULL, 0) == 0) { +				res = elt; +				break; +			} +		} +		regfree (®); +	} + +	return res; +} + +/* + * Check dependencies for an object + */ +static bool +ucl_schema_validate_dependencies (ucl_object_t *deps, +		ucl_object_t *obj, struct ucl_schema_error *err, +		ucl_object_t *root) +{ +	ucl_object_t *elt, *cur, *cur_dep; +	ucl_object_iter_t iter = NULL, piter; +	bool ret = true; + +	while (ret && (cur = ucl_iterate_object (deps, &iter, true)) != NULL) { +		elt = ucl_object_find_key (obj, ucl_object_key (cur)); +		if (elt != NULL) { +			/* Need to check dependencies */ +			if (cur->type == UCL_ARRAY) { +				piter = NULL; +				while (ret && (cur_dep = ucl_iterate_object (cur, &piter, true)) != NULL) { +					if (ucl_object_find_key (obj, ucl_object_tostring (cur_dep)) == NULL) { +						ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt, +								"dependency %s is missing for key %s", +								ucl_object_tostring (cur_dep), ucl_object_key (cur)); +						ret = false; +						break; +					} +				} +			} +			else if (cur->type == UCL_OBJECT) { +				ret = ucl_schema_validate (cur, obj, true, err, root); +			} +		} +	} + +	return ret; +} + +/* + * Validate object + */ +static bool +ucl_schema_validate_object (ucl_object_t *schema, +		ucl_object_t *obj, struct ucl_schema_error *err, +		ucl_object_t *root) +{ +	ucl_object_t *elt, *prop, *found, *additional_schema = NULL, +			*required = NULL, *pat, *pelt; +	ucl_object_iter_t iter = NULL, piter = NULL; +	bool ret = true, allow_additional = true; +	int64_t minmax; + +	while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { +		if (elt->type == UCL_OBJECT && +				strcmp (ucl_object_key (elt), "properties") == 0) { +			piter = NULL; +			while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) { +				found = ucl_object_find_key (obj, ucl_object_key (prop)); +				if (found) { +					ret = ucl_schema_validate (prop, found, true, err, root); +				} +			} +		} +		else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) { +			if (elt->type == UCL_BOOLEAN) { +				if (!ucl_object_toboolean (elt)) { +					/* Deny additional fields completely */ +					allow_additional = false; +				} +			} +			else if (elt->type == UCL_OBJECT) { +				/* Define validator for additional fields */ +				additional_schema = elt; +			} +			else { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"additionalProperties attribute is invalid in schema"); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "required") == 0) { +			if (elt->type == UCL_ARRAY) { +				required = elt; +			} +			else { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"required attribute is invalid in schema"); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "minProperties") == 0 +				&& ucl_object_toint_safe (elt, &minmax)) { +			if (obj->len < minmax) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"object has not enough properties: %u, minimum is: %u", +						obj->len, (unsigned)minmax); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "maxProperties") == 0 +				&& ucl_object_toint_safe (elt, &minmax)) { +			if (obj->len > minmax) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"object has too many properties: %u, maximum is: %u", +						obj->len, (unsigned)minmax); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "patternProperties") == 0) { +			piter = NULL; +			while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) { +				found = ucl_schema_test_pattern (obj, ucl_object_key (prop)); +				if (found) { +					ret = ucl_schema_validate (prop, found, true, err, root); +				} +			} +		} +		else if (elt->type == UCL_OBJECT && +				strcmp (ucl_object_key (elt), "dependencies") == 0) { +			ret = ucl_schema_validate_dependencies (elt, obj, err, root); +		} +	} + +	if (ret) { +		/* Additional properties */ +		if (!allow_additional || additional_schema != NULL) { +			/* Check if we have exactly the same properties in schema and object */ +			iter = NULL; +			prop = ucl_object_find_key (schema, "properties"); +			while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { +				found = ucl_object_find_key (prop, ucl_object_key (elt)); +				if (found == NULL) { +					/* Try patternProperties */ +					piter = NULL; +					pat = ucl_object_find_key (schema, "patternProperties"); +					while ((pelt = ucl_iterate_object (pat, &piter, true)) != NULL) { +						found = ucl_schema_test_pattern (obj, ucl_object_key (pelt)); +						if (found != NULL) { +							break; +						} +					} +				} +				if (found == NULL) { +					if (!allow_additional) { +						ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +								"object has non-allowed property %s", +								ucl_object_key (elt)); +						ret = false; +						break; +					} +					else if (additional_schema != NULL) { +						if (!ucl_schema_validate (additional_schema, elt, true, err, root)) { +							ret = false; +							break; +						} +					} +				} +			} +		} +		/* Required properties */ +		if (required != NULL) { +			iter = NULL; +			while ((elt = ucl_iterate_object (required, &iter, true)) != NULL) { +				if (ucl_object_find_key (obj, ucl_object_tostring (elt)) == NULL) { +					ucl_schema_create_error (err, UCL_SCHEMA_MISSING_PROPERTY, obj, +							"object has missing property %s", +							ucl_object_tostring (elt)); +					ret = false; +					break; +				} +			} +		} +	} + + +	return ret; +} + +static bool +ucl_schema_validate_number (ucl_object_t *schema, +		ucl_object_t *obj, struct ucl_schema_error *err) +{ +	ucl_object_t *elt, *test; +	ucl_object_iter_t iter = NULL; +	bool ret = true, exclusive = false; +	double constraint, val; +	const double alpha = 1e-16; + +	while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { +		if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && +				strcmp (ucl_object_key (elt), "multipleOf") == 0) { +			constraint = ucl_object_todouble (elt); +			if (constraint <= 0) { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"multipleOf must be greater than zero"); +				ret = false; +				break; +			} +			val = ucl_object_todouble (obj); +			if (fabs (remainder (val, constraint)) > alpha) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"number %.4f is not multiple of %.4f, remainder is %.7f", +						val, constraint); +				ret = false; +				break; +			} +		} +		else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && +			strcmp (ucl_object_key (elt), "maximum") == 0) { +			constraint = ucl_object_todouble (elt); +			test = ucl_object_find_key (schema, "exclusiveMaximum"); +			if (test && test->type == UCL_BOOLEAN) { +				exclusive = ucl_object_toboolean (test); +			} +			val = ucl_object_todouble (obj); +			if (val > constraint || (exclusive && val >= constraint)) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"number is too big: %.3f, maximum is: %.3f", +						val, constraint); +				ret = false; +				break; +			} +		} +		else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && +				strcmp (ucl_object_key (elt), "minimum") == 0) { +			constraint = ucl_object_todouble (elt); +			test = ucl_object_find_key (schema, "exclusiveMinimum"); +			if (test && test->type == UCL_BOOLEAN) { +				exclusive = ucl_object_toboolean (test); +			} +			val = ucl_object_todouble (obj); +			if (val < constraint || (exclusive && val <= constraint)) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"number is too small: %.3f, minimum is: %.3f", +						val, constraint); +				ret = false; +				break; +			} +		} +	} + +	return ret; +} + +static bool +ucl_schema_validate_string (ucl_object_t *schema, +		ucl_object_t *obj, struct ucl_schema_error *err) +{ +	ucl_object_t *elt; +	ucl_object_iter_t iter = NULL; +	bool ret = true; +	int64_t constraint; +	regex_t re; + +	while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { +		if (elt->type == UCL_INT && +			strcmp (ucl_object_key (elt), "maxLength") == 0) { +			constraint = ucl_object_toint (elt); +			if (obj->len > constraint) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"string is too big: %.3f, maximum is: %.3f", +						obj->len, constraint); +				ret = false; +				break; +			} +		} +		else if (elt->type == UCL_INT && +				strcmp (ucl_object_key (elt), "minLength") == 0) { +			constraint = ucl_object_toint (elt); +			if (obj->len < constraint) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"string is too short: %.3f, minimum is: %.3f", +						obj->len, constraint); +				ret = false; +				break; +			} +		} +		else if (elt->type == UCL_STRING && +				strcmp (ucl_object_key (elt), "pattern") == 0) { +			if (regcomp (&re, ucl_object_tostring (elt), +					REG_EXTENDED | REG_NOSUB) != 0) { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"cannot compile pattern %s", ucl_object_tostring (elt)); +				ret = false; +				break; +			} +			if (regexec (&re, ucl_object_tostring (obj), 0, NULL, 0) != 0) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"string doesn't match regexp %s", +						ucl_object_tostring (elt)); +				ret = false; +			} +			regfree (&re); +		} +	} + +	return ret; +} + +struct ucl_compare_node { +	ucl_object_t *obj; +	TREE_ENTRY(ucl_compare_node) link; +	struct ucl_compare_node *next; +}; + +typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t; + +TREE_DEFINE(ucl_compare_node, link) + +static int +ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2) +{ +	ucl_object_t *o1 = n1->obj, *o2 = n2->obj; + +	return ucl_object_compare (o1, o2); +} + +static bool +ucl_schema_array_is_unique (ucl_object_t *obj, struct ucl_schema_error *err) +{ +	ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare); +	ucl_object_iter_t iter = NULL; +	ucl_object_t *elt; +	struct ucl_compare_node *node, test, *nodes = NULL, *tmp; +	bool ret = true; + +	while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { +		test.obj = elt; +		node = TREE_FIND (&tree, ucl_compare_node, link, &test); +		if (node != NULL) { +			ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt, +					"duplicate values detected while uniqueItems is true"); +			ret = false; +			break; +		} +		node = calloc (1, sizeof (*node)); +		if (node == NULL) { +			ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt, +					"cannot allocate tree node"); +			ret = false; +			break; +		} +		node->obj = elt; +		TREE_INSERT (&tree, ucl_compare_node, link, node); +		LL_PREPEND (nodes, node); +	} + +	LL_FOREACH_SAFE (nodes, node, tmp) { +		free (node); +	} + +	return ret; +} + +static bool +ucl_schema_validate_array (ucl_object_t *schema, +		ucl_object_t *obj, struct ucl_schema_error *err, +		ucl_object_t *root) +{ +	ucl_object_t *elt, *it, *found, *additional_schema = NULL, +			*first_unvalidated = NULL; +	ucl_object_iter_t iter = NULL, piter = NULL; +	bool ret = true, allow_additional = true, need_unique = false; +	int64_t minmax; + +	while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { +		if (strcmp (ucl_object_key (elt), "items") == 0) { +			if (elt->type == UCL_ARRAY) { +				found = obj->value.av; +				while (ret && (it = ucl_iterate_object (elt, &piter, true)) != NULL) { +					if (found) { +						ret = ucl_schema_validate (it, found, false, err, root); +						found = found->next; +					} +				} +				if (found != NULL) { +					/* The first element that is not validated */ +					first_unvalidated = found; +				} +			} +			else if (elt->type == UCL_OBJECT) { +				/* Validate all items using the specified schema */ +				while (ret && (it = ucl_iterate_object (obj, &piter, true)) != NULL) { +					ret = ucl_schema_validate (elt, it, false, err, root); +				} +			} +			else { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"items attribute is invalid in schema"); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) { +			if (elt->type == UCL_BOOLEAN) { +				if (!ucl_object_toboolean (elt)) { +					/* Deny additional fields completely */ +					allow_additional = false; +				} +			} +			else if (elt->type == UCL_OBJECT) { +				/* Define validator for additional fields */ +				additional_schema = elt; +			} +			else { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, +						"additionalItems attribute is invalid in schema"); +				ret = false; +				break; +			} +		} +		else if (elt->type == UCL_BOOLEAN && +				strcmp (ucl_object_key (elt), "uniqueItems") == 0) { +			need_unique = ucl_object_toboolean (elt); +		} +		else if (strcmp (ucl_object_key (elt), "minItems") == 0 +				&& ucl_object_toint_safe (elt, &minmax)) { +			if (obj->len < minmax) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"array has not enough items: %u, minimum is: %u", +						obj->len, (unsigned)minmax); +				ret = false; +				break; +			} +		} +		else if (strcmp (ucl_object_key (elt), "maxItems") == 0 +				&& ucl_object_toint_safe (elt, &minmax)) { +			if (obj->len > minmax) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +						"array has too many items: %u, maximum is: %u", +						obj->len, (unsigned)minmax); +				ret = false; +				break; +			} +		} +	} + +	if (ret) { +		/* Additional properties */ +		if (!allow_additional || additional_schema != NULL) { +			if (first_unvalidated != NULL) { +				if (!allow_additional) { +					ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +							"array has undefined item"); +					ret = false; +				} +				else if (additional_schema != NULL) { +					elt = first_unvalidated; +					while (elt) { +						if (!ucl_schema_validate (additional_schema, elt, false, +								err, root)) { +							ret = false; +							break; +						} +						elt = elt->next; +					} +				} +			} +		} +		/* Required properties */ +		if (ret && need_unique) { +			ret = ucl_schema_array_is_unique (obj, err); +		} +	} + +	return ret; +} + +/* + * Returns whether this object is allowed for this type + */ +static bool +ucl_schema_type_is_allowed (ucl_object_t *type, ucl_object_t *obj, +		struct ucl_schema_error *err) +{ +	ucl_object_iter_t iter = NULL; +	ucl_object_t *elt; +	const char *type_str; +	ucl_type_t t; + +	if (type == NULL) { +		/* Any type is allowed */ +		return true; +	} + +	if (type->type == UCL_ARRAY) { +		/* One of allowed types */ +		while ((elt = ucl_iterate_object (type, &iter, true)) != NULL) { +			if (ucl_schema_type_is_allowed (elt, obj, err)) { +				return true; +			} +		} +	} +	else if (type->type == UCL_STRING) { +		type_str = ucl_object_tostring (type); +		if (!ucl_string_to_type (type_str, &t)) { +			ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, type, +					"Type attribute is invalid in schema"); +			return false; +		} +		if (obj->type != t) { +			/* Some types are actually compatible */ +			if (obj->type == UCL_TIME && t == UCL_FLOAT) { +				return true; +			} +			else if (obj->type == UCL_INT && t == UCL_FLOAT) { +				return true; +			} +			else { +				ucl_schema_create_error (err, UCL_SCHEMA_TYPE_MISMATCH, obj, +						"Invalid type of %s, expected %s", +						ucl_object_type_to_string (obj->type), +						ucl_object_type_to_string (t)); +			} +		} +		else { +			/* Types are equal */ +			return true; +		} +	} + +	return false; +} + +/* + * Check if object is equal to one of elements of enum + */ +static bool +ucl_schema_validate_enum (ucl_object_t *en, ucl_object_t *obj, +		struct ucl_schema_error *err) +{ +	ucl_object_iter_t iter = NULL; +	ucl_object_t *elt; +	bool ret = false; + +	while ((elt = ucl_iterate_object (en, &iter, true)) != NULL) { +		if (ucl_object_compare (elt, obj) == 0) { +			ret = true; +			break; +		} +	} + +	if (!ret) { +		ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +				"object is not one of enumerated patterns"); +	} + +	return ret; +} + + +/* + * Check a single ref component + */ +static ucl_object_t * +ucl_schema_resolve_ref_component (ucl_object_t *cur, +		const char *refc, int len, +		struct ucl_schema_error *err) +{ +	ucl_object_t *res = NULL; +	char *err_str; +	int num, i; + +	if (cur->type == UCL_OBJECT) { +		/* Find a key inside an object */ +		res = ucl_object_find_keyl (cur, refc, len); +		if (res == NULL) { +			ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, +					"reference %s is invalid, missing path component", refc); +			return NULL; +		} +	} +	else if (cur->type == UCL_ARRAY) { +		/* We must figure out a number inside array */ +		num = strtoul (refc, &err_str, 10); +		if (err_str != NULL && *err_str != '/' && *err_str != '\0') { +			ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, +					"reference %s is invalid, invalid item number", refc); +			return NULL; +		} +		res = cur->value.av; +		i = 0; +		while (res != NULL) { +			if (i == num) { +				break; +			} +			res = res->next; +		} +		if (res == NULL) { +			ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, +					"reference %s is invalid, item number %d does not exist", +					refc, num); +			return NULL; +		} +	} +	else { +		ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, +				"reference %s is invalid, contains primitive object in the path", +				refc); +		return NULL; +	} + +	return res; +} +/* + * Find reference schema + */ +static ucl_object_t * +ucl_schema_resolve_ref (ucl_object_t *root, const char *ref, +		struct ucl_schema_error *err) +{ +	const char *p, *c; +	ucl_object_t *res = NULL; + + +	if (ref[0] != '#') { +		ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root, +				"reference %s is invalid, not started with #", ref); +		return NULL; +	} +	if (ref[1] == '/') { +		p = &ref[2]; +	} +	else if (ref[1] == '\0') { +		return root; +	} +	else { +		ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root, +				"reference %s is invalid, not started with #/", ref); +		return NULL; +	} + +	c = p; +	res = root; + +	while (*p != '\0') { +		if (*p == '/') { +			if (p - c == 0) { +				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, +						"reference %s is invalid, empty path component", ref); +				return NULL; +			} +			/* Now we have some url part, so we need to figure out where we are */ +			res = ucl_schema_resolve_ref_component (res, c, p - c, err); +			if (res == NULL) { +				return NULL; +			} +			c = p + 1; +		} +		p ++; +	} + +	if (p - c != 0) { +		res = ucl_schema_resolve_ref_component (res, c, p - c, err); +	} + +	if (res == NULL || res->type != UCL_OBJECT) { +		ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, +				"reference %s is invalid, cannot find specified object", +				ref); +		return NULL; +	} + +	return res; +} + +static bool +ucl_schema_validate_values (ucl_object_t *schema, ucl_object_t *obj, +		struct ucl_schema_error *err) +{ +	ucl_object_t *elt, *cur; +	int64_t constraint, i; + +	elt = ucl_object_find_key (schema, "maxValues"); +	if (elt != NULL && elt->type == UCL_INT) { +		constraint = ucl_object_toint (elt); +		cur = obj; +		i = 0; +		while (cur) { +			if (i > constraint) { +				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +					"object has more values than defined: %ld", +					(long int)constraint); +				return false; +			} +			i ++; +			cur = cur->next; +		} +	} +	elt = ucl_object_find_key (schema, "minValues"); +	if (elt != NULL && elt->type == UCL_INT) { +		constraint = ucl_object_toint (elt); +		cur = obj; +		i = 0; +		while (cur) { +			if (i >= constraint) { +				break; +			} +			i ++; +			cur = cur->next; +		} +		if (i < constraint) { +			ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, +					"object has less values than defined: %ld", +					(long int)constraint); +			return false; +		} +	} + +	return true; +} + +static bool +ucl_schema_validate (ucl_object_t *schema, +		ucl_object_t *obj, bool try_array, +		struct ucl_schema_error *err, +		ucl_object_t *root) +{ +	ucl_object_t *elt, *cur; +	ucl_object_iter_t iter = NULL; +	bool ret; + +	if (schema->type != UCL_OBJECT) { +		ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, schema, +				"schema is %s instead of object", ucl_object_type_to_string (schema->type)); +		return false; +	} + +	if (try_array) { +		/* +		 * Special case for multiple values +		 */ +		if (!ucl_schema_validate_values (schema, obj, err)) { +			return false; +		} +		LL_FOREACH (obj, cur) { +			if (!ucl_schema_validate (schema, cur, false, err, root)) { +				return false; +			} +		} +		return true; +	} + +	elt = ucl_object_find_key (schema, "enum"); +	if (elt != NULL && elt->type == UCL_ARRAY) { +		if (!ucl_schema_validate_enum (elt, obj, err)) { +			return false; +		} +	} + +	elt = ucl_object_find_key (schema, "allOf"); +	if (elt != NULL && elt->type == UCL_ARRAY) { +		iter = NULL; +		while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { +			ret = ucl_schema_validate (cur, obj, true, err, root); +			if (!ret) { +				return false; +			} +		} +	} + +	elt = ucl_object_find_key (schema, "anyOf"); +	if (elt != NULL && elt->type == UCL_ARRAY) { +		iter = NULL; +		while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { +			ret = ucl_schema_validate (cur, obj, true, err, root); +			if (ret) { +				break; +			} +		} +		if (!ret) { +			return false; +		} +		else { +			/* Reset error */ +			err->code = UCL_SCHEMA_OK; +		} +	} + +	elt = ucl_object_find_key (schema, "oneOf"); +	if (elt != NULL && elt->type == UCL_ARRAY) { +		iter = NULL; +		ret = false; +		while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { +			if (!ret) { +				ret = ucl_schema_validate (cur, obj, true, err, root); +			} +			else if (ucl_schema_validate (cur, obj, true, err, root)) { +				ret = false; +				break; +			} +		} +		if (!ret) { +			return false; +		} +	} + +	elt = ucl_object_find_key (schema, "not"); +	if (elt != NULL && elt->type == UCL_OBJECT) { +		if (ucl_schema_validate (elt, obj, true, err, root)) { +			return false; +		} +		else { +			/* Reset error */ +			err->code = UCL_SCHEMA_OK; +		} +	} + +	elt = ucl_object_find_key (schema, "$ref"); +	if (elt != NULL) { +		cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt), err); +		if (cur == NULL) { +			return false; +		} +		if (!ucl_schema_validate (cur, obj, try_array, err, root)) { +			return false; +		} +	} + +	elt = ucl_object_find_key (schema, "type"); +	if (!ucl_schema_type_is_allowed (elt, obj, err)) { +		return false; +	} + +	switch (obj->type) { +	case UCL_OBJECT: +		return ucl_schema_validate_object (schema, obj, err, root); +		break; +	case UCL_ARRAY: +		return ucl_schema_validate_array (schema, obj, err, root); +		break; +	case UCL_INT: +	case UCL_FLOAT: +		return ucl_schema_validate_number (schema, obj, err); +		break; +	case UCL_STRING: +		return ucl_schema_validate_string (schema, obj, err); +		break; +	default: +		break; +	} + +	return true; +} + +bool +ucl_object_validate (ucl_object_t *schema, +		ucl_object_t *obj, struct ucl_schema_error *err) +{ +	return ucl_schema_validate (schema, obj, true, err, schema); +} diff --git a/src/ucl_util.c b/src/ucl_util.c index 34080d4414925..f96c23cd6129b 100644 --- a/src/ucl_util.c +++ b/src/ucl_util.c @@ -25,7 +25,9 @@  #include "ucl_internal.h"  #include "ucl_chartable.h" +#ifdef HAVE_LIBGEN_H  #include <libgen.h> /* For dirname */ +#endif  #ifdef HAVE_OPENSSL  #include <openssl/err.h> @@ -35,17 +37,36 @@  #include <openssl/evp.h>  #endif +#ifdef CURL_FOUND +#include <curl/curl.h> +#endif +#ifdef HAVE_FETCH_H +#include <fetch.h> +#endif +  #ifdef _WIN32  #include <windows.h> +#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 *mmap(char *addr, size_t length, int prot, int access, int fd, off_t offset) +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; @@ -83,7 +104,7 @@ static void *mmap(char *addr, size_t length, int prot, int access, int fd, off_t  	return (void *) ((char *) map + offset);  } -static int munmap(void *map,size_t length) +static int ucl_munmap(void *map,size_t length)  {  	if (!UnmapViewOfFile(map)) {  		return(-1); @@ -91,7 +112,7 @@ static int munmap(void *map,size_t length)  	return(0);  } -static char* realpath(const char *path, char *resolved_path) { +static char* ucl_realpath(const char *path, char *resolved_path) {      char *p;      char tmp[MAX_PATH + 1];      strncpy(tmp, path, sizeof(tmp)-1); @@ -102,6 +123,10 @@ static char* realpath(const char *path, char *resolved_path) {      }      return _fullpath(resolved_path, tmp, MAX_PATH);  } +#else +#define ucl_mmap mmap +#define ucl_munmap munmap +#define ucl_realpath realpath  #endif  /** @@ -158,6 +183,9 @@ 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) { @@ -188,45 +216,53 @@ ucl_unescape_json_string (char *str, size_t len)  			case 'u':  				/* Unicode escape */  				uval = 0; -				for (i = 0; i < 4; i++) { -					uval <<= 4; -					if (isdigit (h[i])) { -						uval += h[i] - '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; +						}  					} -					else if (h[i] >= 'a' && h[i] <= 'f') { -						uval += h[i] - 'a' + 10; +					h += 3; +					len -= 3; +					/* Encode */ +					if(uval < 0x80) { +						t[0] = (char)uval; +						t ++;  					} -					else if (h[i] >= 'A' && h[i] <= 'F') { -						uval += h[i] - 'A' + 10; +					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++ = '?';  					} -				} -				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++ = '?'; +					*t++ = 'u';  				}  				break;  			default: @@ -249,6 +285,9 @@ ucl_unescape_json_string (char *str, size_t len)  UCL_EXTERN char *  ucl_copy_key_trash (ucl_object_t *obj)  { +	if (obj == NULL) { +		return NULL; +	}  	if (obj->trash_stack[UCL_TRASH_KEY] == NULL && obj->key != NULL) {  		obj->trash_stack[UCL_TRASH_KEY] = malloc (obj->keylen + 1);  		if (obj->trash_stack[UCL_TRASH_KEY] != NULL) { @@ -265,6 +304,9 @@ ucl_copy_key_trash (ucl_object_t *obj)  UCL_EXTERN char *  ucl_copy_value_trash (ucl_object_t *obj)  { +	if (obj == NULL) { +		return NULL; +	}  	if (obj->trash_stack[UCL_TRASH_VALUE] == NULL) {  		if (obj->type == UCL_STRING) {  			/* Special case for strings */ @@ -304,6 +346,10 @@ ucl_parser_free (struct ucl_parser *parser)  	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);  	} @@ -338,6 +384,10 @@ ucl_parser_free (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; @@ -360,6 +410,10 @@ ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len)  	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) { @@ -527,7 +581,7 @@ ucl_fetch_file (const unsigned char *filename, unsigned char **buf, size_t *bufl  					filename, strerror (errno));  			return false;  		} -		if ((*buf = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { +		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)); @@ -629,12 +683,12 @@ ucl_include_url (const unsigned char *data, size_t len,  							urlbuf,  							ERR_error_string (ERR_get_error (), NULL));  			if (siglen > 0) { -				munmap (sigbuf, siglen); +				ucl_munmap (sigbuf, siglen);  			}  			return false;  		}  		if (siglen > 0) { -			munmap (sigbuf, siglen); +			ucl_munmap (sigbuf, siglen);  		}  #endif  	} @@ -678,7 +732,7 @@ ucl_include_file (const unsigned char *data, size_t len,  	int prev_state;  	snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data); -	if (realpath (filebuf, realbuf) == NULL) { +	if (ucl_realpath (filebuf, realbuf) == NULL) {  		if (!must_exist) {  			return true;  		} @@ -706,12 +760,12 @@ ucl_include_file (const unsigned char *data, size_t len,  							filebuf,  							ERR_error_string (ERR_get_error (), NULL));  			if (siglen > 0) { -				munmap (sigbuf, siglen); +				ucl_munmap (sigbuf, siglen);  			}  			return false;  		}  		if (siglen > 0) { -			munmap (sigbuf, siglen); +			ucl_munmap (sigbuf, siglen);  		}  #endif  	} @@ -734,7 +788,7 @@ ucl_include_file (const unsigned char *data, size_t len,  	parser->state = prev_state;  	if (buflen > 0) { -		munmap (buf, buflen); +		ucl_munmap (buf, buflen);  	}  	return res; @@ -803,7 +857,7 @@ ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool n  	if (filename != NULL) {  		if (need_expand) { -			if (realpath (filename, realbuf) == NULL) { +			if (ucl_realpath (filename, realbuf) == NULL) {  				return false;  			}  		} @@ -834,7 +888,7 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)  	bool ret;  	char realbuf[PATH_MAX]; -	if (realpath (filename, realbuf) == NULL) { +	if (ucl_realpath (filename, realbuf) == NULL) {  		ucl_create_err (&parser->err, "cannot open file %s: %s",  				filename,  				strerror (errno)); @@ -849,7 +903,7 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)  	ret = ucl_parser_add_chunk (parser, buf, len);  	if (len > 0) { -		munmap (buf, len); +		ucl_munmap (buf, len);  	}  	return ret; @@ -1014,13 +1068,15 @@ ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags  				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_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_BYTES, +						flags & UCL_STRING_PARSE_TIME);  			}  		}  	} @@ -1083,6 +1139,7 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,  	if (!found) {  		top->value.ov = ucl_hash_insert_object (top->value.ov, elt);  		DL_APPEND (found, elt); +		top->len ++;  	}  	else {  		if (replace) { @@ -1129,10 +1186,15 @@ 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 = ucl_object_find_keyl(top, key, keylen); -	if (found == NULL) +	if (found == NULL) {  		return false; +	}  	ucl_hash_delete(top->value.ov, found);  	ucl_object_unref (found); @@ -1147,6 +1209,31 @@ ucl_object_delete_key(ucl_object_t *top, const char *key)  	return ucl_object_delete_keyl(top, key, 0);  } +ucl_object_t* +ucl_object_pop_keyl (ucl_object_t *top, const char *key, size_t keylen) +{ +	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 found; +} + +ucl_object_t* +ucl_object_pop_key (ucl_object_t *top, const char *key) +{ +	return ucl_object_pop_keyl (top, key, 0); +} +  ucl_object_t *  ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,  		const char *key, size_t keylen, bool copy_key) @@ -1207,6 +1294,10 @@ ucl_iterate_object (ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_valu  {  	ucl_object_t *elt; +	if (obj == NULL || iter == NULL) { +		return NULL; +	} +  	if (expand_values) {  		switch (obj->type) {  		case UCL_OBJECT: @@ -1247,3 +1338,498 @@ ucl_iterate_object (ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_valu  	/* Not reached */  	return NULL;  } + + +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_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_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; +} + +ucl_object_t * +ucl_array_append (ucl_object_t *top, ucl_object_t *elt) +{ +	ucl_object_t *head; + +	if (elt == NULL) { +		return NULL; +	} + +	if (top == NULL) { +		top = ucl_object_typed_new (UCL_ARRAY); +		top->value.av = elt; +		elt->next = NULL; +		elt->prev = elt; +		top->len = 1; +	} +	else { +		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 top; +} + +ucl_object_t * +ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt) +{ +	ucl_object_t *head; + +	if (elt == NULL) { +		return NULL; +	} + +	if (top == NULL) { +		top = ucl_object_typed_new (UCL_ARRAY); +		top->value.av = elt; +		elt->next = NULL; +		elt->prev = elt; +		top->len = 1; +	} +	else { +		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 top; +} + +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; +} + +ucl_object_t * +ucl_array_head (ucl_object_t *top) +{ +	if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) { +		return NULL; +	} +	return top->value.av; +} + +ucl_object_t * +ucl_array_tail (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, ucl_array_tail (top)); +} + +ucl_object_t * +ucl_array_pop_first (ucl_object_t *top) +{ +	return ucl_array_delete (top, ucl_array_head (top)); +} + +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 (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 (ucl_object_t *obj) +{ +	double result = 0.; + +	ucl_object_todouble_safe (obj, &result); +	return result; +} + +bool +ucl_object_toint_safe (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 (ucl_object_t *obj) +{ +	int64_t result = 0; + +	ucl_object_toint_safe (obj, &result); +	return result; +} + +bool +ucl_object_toboolean_safe (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 (ucl_object_t *obj) +{ +	bool result = false; + +	ucl_object_toboolean_safe (obj, &result); +	return result; +} + +bool +ucl_object_tostring_safe (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 (ucl_object_t *obj) +{ +	const char *result = NULL; + +	ucl_object_tostring_safe (obj, &result); +	return result; +} + +const char * +ucl_object_tostring_forced (ucl_object_t *obj) +{ +	return ucl_copy_value_trash (obj); +} + +bool +ucl_object_tolstring_safe (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 (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 (ucl_object_t *obj) +{ +	return ucl_copy_key_trash (obj); +} + +const char * +ucl_object_keyl (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 (ucl_object_t *obj) +{ +	if (obj != NULL) { +		obj->ref ++; +	} +	return obj; +} + +void +ucl_object_unref (ucl_object_t *obj) +{ +	if (obj != NULL && --obj->ref <= 0) { +		ucl_object_free (obj); +	} +} + +int +ucl_object_compare (ucl_object_t *o1, ucl_object_t *o2) +{ +	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)(ucl_object_t *o1, ucl_object_t *o2)) +{ +	if (cmp == NULL || ar == NULL || ar->type != UCL_ARRAY) { +		return; +	} + +	DL_SORT (ar->value.av, cmp); +} | 
