/* 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 RCL_H_ #define RCL_H_ #include #include #include #include #include #include #include #include "config.h" #include "uthash.h" #include "utlist.h" /** * @file rcl.h * RCL is an rspamd configuration language, which is a form of * JSON with less strict rules that make it more comfortable for * using as a configuration language */ /** * 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_object_todouble_safe ucl_obj_todouble_safe #define ucl_object_todouble ucl_obj_todouble #define ucl_object_tostring ucl_obj_tostring #define ucl_object_tostring_safe ucl_obj_tostring_safe #define ucl_object_tolstring ucl_obj_tolstring #define ucl_object_tolstring_safe ucl_obj_tolstring_safe #define ucl_object_toint ucl_obj_toint #define ucl_object_toint_safe ucl_obj_toint_safe #define ucl_object_toboolean ucl_obj_toboolean #define ucl_object_toboolean_safe ucl_obj_toboolean_safe #define ucl_object_find_key ucl_obj_get_key #define ucl_object_find_keyl ucl_obj_get_keyl #define ucl_object_unref ucl_obj_unref #define ucl_object_ref ucl_obj_ref #define ucl_object_free ucl_obj_free /** * 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 enum ucl_error { UCL_EOK = 0, //!< UCL_EOK UCL_ESYNTAX, //!< UCL_ESYNTAX UCL_EIO, //!< UCL_EIO UCL_ESTATE, //!< UCL_ESTATE UCL_ENESTED, //!< UCL_ENESTED UCL_EMACRO, //!< UCL_EMACRO UCL_ERECURSION,//!< UCL_ERECURSION UCL_EINTERNAL, //!< UCL_EINTERNAL UCL_ESSL //!< UCL_ESSL }; /** * Object types */ enum ucl_type { UCL_OBJECT = 0,//!< UCL_OBJECT UCL_ARRAY, //!< UCL_ARRAY UCL_INT, //!< UCL_INT UCL_FLOAT, //!< UCL_FLOAT UCL_STRING, //!< UCL_STRING UCL_BOOLEAN, //!< UCL_BOOLEAN UCL_TIME, //!< UCL_TIME UCL_USERDATA //!< UCL_USERDATA }; /** * Emitting types */ enum ucl_emitter { UCL_EMIT_JSON = 0, //!< UCL_EMIT_JSON UCL_EMIT_JSON_COMPACT,//!< UCL_EMIT_JSON_COMPACT UCL_EMIT_CONFIG, //!< UCL_EMIT_CONFIG UCL_EMIT_YAML //!< UCL_EMIT_YAML }; /** * Parsing flags */ enum ucl_parser_flags { UCL_PARSER_KEY_LOWERCASE = 0x1,//!< UCL_FLAG_KEY_LOWERCASE UCL_PARSER_ZEROCOPY = 0x2 //!< UCL_FLAG_ZEROCOPY }; /** * String conversion flags */ enum ucl_string_flags { UCL_STRING_ESCAPE = 0x1, /**< UCL_STRING_ESCAPE perform JSON escape */ UCL_STRING_TRIM = 0x2, /**< UCL_STRING_TRIM trim leading and trailing whitespaces */ UCL_STRING_PARSE_BOOLEAN = 0x4, /**< UCL_STRING_PARSE_BOOLEAN parse passed string and detect boolean */ UCL_STRING_PARSE_INT = 0x8, /**< UCL_STRING_PARSE_INT parse passed string and detect integer number */ UCL_STRING_PARSE_DOUBLE = 0x10, /**< UCL_STRING_PARSE_DOUBLE parse passed string and detect integer or float number */ UCL_STRING_PARSE_NUMBER = UCL_STRING_PARSE_INT|UCL_STRING_PARSE_DOUBLE , /**< UCL_STRING_PARSE_NUMBER parse passed string and detect number */ UCL_STRING_PARSE = UCL_STRING_PARSE_BOOLEAN|UCL_STRING_PARSE_NUMBER /**< UCL_STRING_PARSE parse passed string (and detect booleans and numbers) */ }; /** * Basic flags for an object */ enum ucl_object_flags { UCL_OBJECT_ALLOCATED_KEY = 1, //!< UCL_OBJECT_ALLOCATED_KEY UCL_OBJECT_ALLOCATED_VALUE = 2, //!< UCL_OBJECT_ALLOCATED_VALUE UCL_OBJECT_NEED_KEY_ESCAPE = 4 //!< UCL_OBJECT_NEED_KEY_ESCAPE }; /** * UCL object */ typedef struct ucl_object_s { 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 *ov; /**< array or hash */ void* ud; /**< opaque user data */ } value; enum ucl_type type; /**< real type */ short int ref; /**< reference count */ short int flags; /**< object flags */ size_t len; /**< size 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 */ UT_hash_handle hh; /**< hash handle */ } ucl_object_t; /** * Copy and return a key of an object, returned key is zero-terminated * @param obj CL object * @return zero terminated key */ char* ucl_copy_key_trash (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 */ char* ucl_copy_value_trash (ucl_object_t *obj); /** * Creates a new object * @return new object */ static inline 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; } return new; } /** * 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_object_t * ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags flags); /** * Create a UCL object from the specified string * @param str NULL terminated string, will be json escaped * @return new object */ static inline ucl_object_t * ucl_object_fromstring (const char *str) { return ucl_object_fromstring_common (str, 0, UCL_STRING_ESCAPE); } /** * 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 */ static inline ucl_object_t * ucl_object_fromlstring (const char *str, size_t len) { return ucl_object_fromstring_common (str, len, UCL_STRING_ESCAPE); } /** * Create an object from an integer number * @param iv number * @return new object */ static inline 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; } /** * Create an object from a float number * @param dv number * @return new object */ static inline 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; } /** * Create an object from a boolean * @param bv bool value * @return new object */ static inline 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; } /** * 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 new value of top object */ ucl_object_t* ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt, const char *key, size_t keylen, bool copy_key); /** * Append an element to the array object * @param top destination object (will be created automatically if top is NULL) * @param eltelement to append (must NOT be NULL) * @return new value of top object */ static inline ucl_object_t * ucl_array_append (ucl_object_t *top, ucl_object_t *elt) { if (elt == NULL) { return NULL; } if (top == NULL) { top = ucl_object_new (); top->type = UCL_ARRAY; } DL_APPEND (top->value.ov, elt); return top; } /** * Append a element to another element forming an implicit array * @param head head to append (may be NULL) * @param elt new element * @return new head if applicable */ static inline ucl_object_t * ucl_elt_append (ucl_object_t *head, ucl_object_t *elt) { DL_APPEND (head, elt); return head; } /** * Converts an object to double value * @param obj CL object * @param target target double variable * @return true if conversion was successful */ static inline bool ucl_obj_todouble_safe (ucl_object_t *obj, double *target) { if (obj == 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; } /** * Unsafe version of \ref ucl_obj_todouble_safe * @param obj CL object * @return double value */ static inline double ucl_obj_todouble (ucl_object_t *obj) { double result = 0.; ucl_object_todouble_safe (obj, &result); return result; } /** * Converts an object to integer value * @param obj CL object * @param target target integer variable * @return true if conversion was successful */ static inline bool ucl_obj_toint_safe (ucl_object_t *obj, int64_t *target) { if (obj == 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; } /** * Unsafe version of \ref ucl_obj_toint_safe * @param obj CL object * @return int value */ static inline int64_t ucl_obj_toint (ucl_object_t *obj) { int64_t result = 0; ucl_object_toint_safe (obj, &result); return result; } /** * Converts an object to boolean value * @param obj CL object * @param target target boolean variable * @return true if conversion was successful */ static inline bool ucl_obj_toboolean_safe (ucl_object_t *obj, bool *target) { if (obj == NULL) { return false; } switch (obj->type) { case UCL_BOOLEAN: *target = (obj->value.iv == true); break; default: return false; } return true; } /** * Unsafe version of \ref ucl_obj_toboolean_safe * @param obj CL object * @return boolean value */ static inline bool ucl_obj_toboolean (ucl_object_t *obj) { bool result = false; ucl_object_toboolean_safe (obj, &result); return result; } /** * 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 */ static inline bool ucl_obj_tostring_safe (ucl_object_t *obj, const char **target) { if (obj == NULL) { return false; } switch (obj->type) { case UCL_STRING: *target = ucl_copy_value_trash (obj); break; default: return false; } return true; } /** * Unsafe version of \ref ucl_obj_tostring_safe * @param obj CL object * @return string value */ static inline const char * ucl_obj_tostring (ucl_object_t *obj) { const char *result = NULL; ucl_object_tostring_safe (obj, &result); return result; } /** * Convert any object to a string in JSON notation if needed * @param obj CL object * @return string value */ static inline const char * ucl_obj_tostring_forced (ucl_object_t *obj) { return ucl_copy_value_trash (obj); } /** * Return string as char * and len, string may be not zero terminated, more efficient that tostring as it * allows zero-copy * @param obj CL object * @param target target string variable, no need to free value * @param tlen target length * @return true if conversion was successful */ static inline bool ucl_obj_tolstring_safe (ucl_object_t *obj, const char **target, size_t *tlen) { if (obj == NULL) { return false; } switch (obj->type) { case UCL_STRING: *target = obj->value.sv; *tlen = obj->len; break; default: return false; } return true; } /** * Unsafe version of \ref ucl_obj_tolstring_safe * @param obj CL object * @return string value */ static inline const char * ucl_obj_tolstring (ucl_object_t *obj, size_t *tlen) { const char *result = NULL; ucl_object_tolstring_safe (obj, &result, tlen); return result; } /** * 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 */ static inline ucl_object_t * ucl_obj_get_key (ucl_object_t *obj, const char *key) { size_t keylen; ucl_object_t *ret; if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) { return NULL; } keylen = strlen (key); HASH_FIND (hh, obj->value.ov, key, keylen, ret); return ret; } /** * 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 */ static inline ucl_object_t * ucl_obj_get_keyl (ucl_object_t *obj, const char *key, size_t klen) { ucl_object_t *ret; if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) { return NULL; } HASH_FIND (hh, obj->value.ov, key, klen, ret); return ret; } /** * Returns a key of an object as a NULL terminated string * @param obj CL object * @return key or NULL if there is no key */ static inline const char * ucl_object_key (ucl_object_t *obj) { return ucl_copy_key_trash (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 */ static inline const char * ucl_object_keyl (ucl_object_t *obj, size_t *len) { *len = obj->hh.keylen; return obj->hh.key; } /** * 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 */ 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 */ void ucl_parser_register_macro (struct ucl_parser *parser, const char *macro, ucl_macro_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 */ bool ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned 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 */ bool ucl_parser_add_file (struct ucl_parser *parser, const char *filename); /** * Get a top object for a parser * @param parser parser structure * @param err if *err is NULL it is set to parser error * @return top parser object or NULL */ ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser); /** * Get the error string if failing * @param parser parser object */ const char *ucl_parser_get_error(struct ucl_parser *parser); /** * Free cl parser object * @param parser parser object */ void ucl_parser_free (struct ucl_parser *parser); /** * Free cl object * @param obj cl object to free */ void ucl_obj_free (ucl_object_t *obj); /** * Icrease reference count for an object * @param obj object to ref */ static inline ucl_object_t * ucl_obj_ref (ucl_object_t *obj) { obj->ref ++; return obj; } /** * Decrease reference count for an object * @param obj object to unref */ static inline void ucl_obj_unref (ucl_object_t *obj) { if (--obj->ref <= 0) { ucl_obj_free (obj); } } /** * 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 */ unsigned char *ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type); /** * 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 */ bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len); #endif /* RCL_H_ */