diff options
48 files changed, 5577 insertions, 3530 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 52f42203d..7bac51678 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1032,7 +1032,10 @@ ENDIF(HG) ################################ SOURCES SECTION ########################### -INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/src" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/contrib/uthash") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/src" + "${CMAKE_BINARY_DIR}/src" + "${CMAKE_SOURCE_DIR}/src/ucl/include" + "${CMAKE_SOURCE_DIR}/contrib/uthash") SET(RSPAMDSRC src/modules.c src/controller.c @@ -1073,7 +1076,7 @@ ENDIF(NOT HIREDIS_FOUND) ADD_SUBDIRECTORY(src/lua) ADD_SUBDIRECTORY(src/json) ADD_SUBDIRECTORY(src/cdb) -ADD_SUBDIRECTORY(src/rcl) +ADD_SUBDIRECTORY(src/ucl) ADD_SUBDIRECTORY(lib) ADD_SUBDIRECTORY(src/client) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 561058e1a..9f4636dd7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -76,7 +76,7 @@ ENDIF(CMAKE_COMPILER_IS_GNUCC) TARGET_LINK_LIBRARIES(rspamd-util ${CMAKE_REQUIRED_LIBRARIES}) TARGET_LINK_LIBRARIES(rspamd-util pcre) -TARGET_LINK_LIBRARIES(rspamd-util rspamd-rcl) +TARGET_LINK_LIBRARIES(rspamd-util rspamd-ucl) TARGET_LINK_LIBRARIES(rspamd-util ${GLIB2_LIBRARIES}) TARGET_LINK_LIBRARIES(rspamd-util event) diff --git a/src/cfg_file.h b/src/cfg_file.h index e7e122977..875ce3373 100644 --- a/src/cfg_file.h +++ b/src/cfg_file.h @@ -13,7 +13,7 @@ #include "symbols_cache.h" #include "cfg_rcl.h" #include "utlist.h" -#include "rcl/rcl.h" +#include "ucl.h" #define DEFAULT_BIND_PORT 768 #define DEFAULT_CONTROL_PORT 7608 @@ -283,7 +283,7 @@ struct worker_conf { GQueue *active_workers; /**< linked list of spawned workers */ gboolean has_socket; /**< whether we should make listening socket in main process */ gpointer *ctx; /**< worker's context */ - rspamd_cl_object_t *options; /**< other worker's options */ + ucl_object_t *options; /**< other worker's options */ }; /** @@ -348,7 +348,7 @@ struct config_file { GList *workers; /**< linked list of all workers params */ struct rspamd_worker_cfg_parser *wrk_parsers; /**< hash for worker config parsers, indexed by worker quarks */ gchar *filters_str; /**< string of filters */ - rspamd_cl_object_t *rcl_obj; /**< rcl object */ + ucl_object_t *rcl_obj; /**< rcl object */ GHashTable* variables; /**< hash of $variables defined in config, indexed by variable name */ GHashTable* metrics; /**< hash of metrics indexed by metric name */ GList* symbols_groups; /**< groups of symbols */ @@ -448,7 +448,7 @@ void free_config (struct config_file *cfg); * @param opt_name name of option to get * @return module value or NULL if option does not defined */ -rspamd_cl_object_t* get_module_opt (struct config_file *cfg, const gchar *module_name, +ucl_object_t* get_module_opt (struct config_file *cfg, const gchar *module_name, const gchar *opt_name); /** diff --git a/src/cfg_rcl.c b/src/cfg_rcl.c index 2c4bb2845..5320e17ba 100644 --- a/src/cfg_rcl.c +++ b/src/cfg_rcl.c @@ -29,23 +29,23 @@ * Common section handlers */ static gboolean -rspamd_rcl_logging_handler (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_logging_handler (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { - rspamd_cl_object_t *val; + ucl_object_t *val; gchar *filepath; const gchar *facility, *log_type, *log_level; - val = rspamd_cl_obj_get_key (obj, "type"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &log_type)) { + val = ucl_obj_get_key (obj, "type"); + if (val != NULL && ucl_obj_tostring_safe (val, &log_type)) { if (g_ascii_strcasecmp (log_type, "file") == 0) { /* Need to get filename */ - val = rspamd_cl_obj_get_key (obj, "filename"); - if (val == NULL || val->type != RSPAMD_CL_STRING) { + val = ucl_obj_get_key (obj, "filename"); + if (val == NULL || val->type != UCL_STRING) { g_set_error (err, CFG_RCL_ERROR, ENOENT, "filename attribute must be specified for file logging type"); return FALSE; } - if ((filepath = realpath (rspamd_cl_obj_tostring (val), NULL)) == NULL || + if ((filepath = realpath (ucl_obj_tostring (val), NULL)) == NULL || access (filepath, W_OK) == -1) { g_set_error (err, CFG_RCL_ERROR, errno, "log file is inaccessible"); return FALSE; @@ -57,8 +57,8 @@ rspamd_rcl_logging_handler (struct config_file *cfg, rspamd_cl_object_t *obj, /* Need to get facility */ cfg->log_facility = LOG_DAEMON; cfg->log_type = RSPAMD_LOG_SYSLOG; - val = rspamd_cl_obj_get_key (obj, "facility"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &facility)) { + val = ucl_obj_get_key (obj, "facility"); + if (val != NULL && ucl_obj_tostring_safe (val, &facility)) { if (g_ascii_strcasecmp (facility, "LOG_AUTH") == 0 || g_ascii_strcasecmp (facility, "auth") == 0 ) { cfg->log_facility = LOG_AUTH; @@ -131,8 +131,8 @@ rspamd_rcl_logging_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } /* Handle log level */ - val = rspamd_cl_obj_get_key (obj, "level"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &log_level)) { + val = ucl_obj_get_key (obj, "level"); + if (val != NULL && ucl_obj_tostring_safe (val, &log_level)) { if (g_ascii_strcasecmp (log_level, "error") == 0) { cfg->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; } @@ -155,15 +155,15 @@ rspamd_rcl_logging_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } static gboolean -rspamd_rcl_options_handler (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_options_handler (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { - rspamd_cl_object_t *val; + ucl_object_t *val; const gchar *user_settings, *domain_settings; /* Handle user and domain settings */ - val = rspamd_cl_obj_get_key (obj, "user_settings"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &user_settings)) { + val = ucl_obj_get_key (obj, "user_settings"); + if (val != NULL && ucl_obj_tostring_safe (val, &user_settings)) { if (!read_settings (user_settings, "Users' settings", cfg, cfg->user_settings)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot read settings: %s", user_settings); return FALSE; @@ -171,8 +171,8 @@ rspamd_rcl_options_handler (struct config_file *cfg, rspamd_cl_object_t *obj, cfg->user_settings_str = memory_pool_strdup (cfg->cfg_pool, user_settings); } - val = rspamd_cl_obj_get_key (obj, "domain_settings"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &domain_settings)) { + val = ucl_obj_get_key (obj, "domain_settings"); + if (val != NULL && ucl_obj_tostring_safe (val, &domain_settings)) { if (!read_settings (domain_settings, "Domains settings", cfg, cfg->domain_settings)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot read settings: %s", domain_settings); return FALSE; @@ -202,11 +202,11 @@ rspamd_symbols_group_find_func (gconstpointer a, gconstpointer b) */ static gboolean rspamd_rcl_insert_symbol (struct config_file *cfg, struct metric *metric, - rspamd_cl_object_t *obj, GError **err) + ucl_object_t *obj, GError **err) { const gchar *group = "ungrouped", *description = NULL; gdouble symbol_score, *score_ptr; - rspamd_cl_object_t *val; + ucl_object_t *val; struct symbols_group *sym_group; struct symbol_def *sym_def; GList *metric_list, *group_list; @@ -221,26 +221,26 @@ rspamd_rcl_insert_symbol (struct config_file *cfg, struct metric *metric, * group = ...; * } */ - if (rspamd_cl_obj_todouble_safe (obj, &symbol_score)) { + if (ucl_obj_todouble_safe (obj, &symbol_score)) { description = NULL; } - else if (obj->type == RSPAMD_CL_OBJECT) { - val = rspamd_cl_obj_get_key (obj, "score"); - if (val == NULL || !rspamd_cl_obj_todouble_safe (val, &symbol_score)) { - g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid symbol score: %s", obj->key); + else if (obj->type == UCL_OBJECT) { + val = ucl_obj_get_key (obj, "score"); + if (val == NULL || !ucl_obj_todouble_safe (val, &symbol_score)) { + g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid symbol score: %s", ucl_object_key (obj)); return FALSE; } - val = rspamd_cl_obj_get_key (obj, "description"); + val = ucl_obj_get_key (obj, "description"); if (val != NULL) { - description = rspamd_cl_obj_tostring (val); + description = ucl_obj_tostring (val); } - val = rspamd_cl_obj_get_key (obj, "group"); + val = ucl_obj_get_key (obj, "group"); if (val != NULL) { - rspamd_cl_obj_tostring_safe (val, &group); + ucl_obj_tostring_safe (val, &group); } } else { - g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid symbol type: %s", obj->key); + g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid symbol type: %s", ucl_object_key (obj)); return FALSE; } @@ -249,7 +249,7 @@ rspamd_rcl_insert_symbol (struct config_file *cfg, struct metric *metric, *score_ptr = symbol_score; sym_def->weight_ptr = score_ptr; - sym_def->name = memory_pool_strdup (cfg->cfg_pool, obj->key); + sym_def->name = memory_pool_strdup (cfg->cfg_pool, ucl_object_key (obj)); sym_def->description = (gchar *)description; g_hash_table_insert (metric->symbols, sym_def->name, score_ptr); @@ -285,10 +285,10 @@ rspamd_rcl_insert_symbol (struct config_file *cfg, struct metric *metric, } static gboolean -rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_metric_handler (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { - rspamd_cl_object_t *val, *cur, *tmp; + ucl_object_t *val, *cur, *tmp; const gchar *metric_name, *subject_name; struct metric *metric; struct metric_action *action; @@ -296,8 +296,8 @@ rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, gint action_value; gboolean new = TRUE; - val = rspamd_cl_obj_get_key (obj, "name"); - if (val == NULL || !rspamd_cl_obj_tostring_safe (val, &metric_name)) { + val = ucl_obj_get_key (obj, "name"); + if (val == NULL || !ucl_obj_tostring_safe (val, &metric_name)) { metric_name = DEFAULT_METRIC; } @@ -310,16 +310,16 @@ rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } /* Handle actions */ - val = rspamd_cl_obj_get_key (obj, "actions"); + val = ucl_obj_get_key (obj, "actions"); if (val != NULL) { - if (val->type != RSPAMD_CL_OBJECT) { + if (val->type != UCL_OBJECT) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "actions must be an object"); return FALSE; } HASH_ITER (hh, val, cur, tmp) { - if (!check_action_str (cur->key, &action_value) || - !rspamd_cl_obj_todouble_safe (cur, &action_score)) { - g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid action definition: %s", cur->key); + if (!check_action_str (ucl_object_key (cur), &action_value) || + !ucl_obj_todouble_safe (cur, &action_score)) { + g_set_error (err, CFG_RCL_ERROR, EINVAL, "invalid action definition: %s", ucl_object_key (cur)); return FALSE; } action = &metric->actions[action_value]; @@ -333,12 +333,12 @@ rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } /* Handle symbols */ - val = rspamd_cl_obj_get_key (obj, "symbols"); + val = ucl_obj_get_key (obj, "symbols"); if (val != NULL) { - if (val->type == RSPAMD_CL_ARRAY) { + if (val->type == UCL_ARRAY) { val = val->value.ov; } - if (val->type != RSPAMD_CL_OBJECT) { + if (val->type != UCL_OBJECT) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "symbols must be an object"); return FALSE; } @@ -349,13 +349,13 @@ rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } } - val = rspamd_cl_obj_get_key (obj, "grow_factor"); - if (val && rspamd_cl_obj_todouble_safe (val, &grow_factor)) { + val = ucl_obj_get_key (obj, "grow_factor"); + if (val && ucl_obj_todouble_safe (val, &grow_factor)) { metric->grow_factor = grow_factor; } - val = rspamd_cl_obj_get_key (obj, "subject"); - if (val && rspamd_cl_obj_tostring_safe (val, &subject_name)) { + val = ucl_obj_get_key (obj, "subject"); + if (val && ucl_obj_tostring_safe (val, &subject_name)) { metric->subject = (gchar *)subject_name; } @@ -367,10 +367,10 @@ rspamd_rcl_metric_handler (struct config_file *cfg, rspamd_cl_object_t *obj, } static gboolean -rspamd_rcl_worker_handler (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_worker_handler (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { - rspamd_cl_object_t *val, *cur, *tmp; + ucl_object_t *val, *cur, *tmp; const gchar *worker_type, *worker_bind; GQuark qtype; struct worker_conf *wrk; @@ -378,8 +378,8 @@ rspamd_rcl_worker_handler (struct config_file *cfg, rspamd_cl_object_t *obj, struct rspamd_worker_cfg_parser *wparser; struct rspamd_worker_param_parser *whandler; - val = rspamd_cl_obj_get_key (obj, "type"); - if (val != NULL && rspamd_cl_obj_tostring_safe (val, &worker_type)) { + val = ucl_obj_get_key (obj, "type"); + if (val != NULL && ucl_obj_tostring_safe (val, &worker_type)) { qtype = g_quark_try_string (worker_type); if (qtype != 0) { wrk = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct worker_conf)); @@ -403,13 +403,13 @@ rspamd_rcl_worker_handler (struct config_file *cfg, rspamd_cl_object_t *obj, return FALSE; } - val = rspamd_cl_obj_get_key (obj, "bind_socket"); + val = ucl_obj_get_key (obj, "bind_socket"); if (val != NULL) { - if (val->type == RSPAMD_CL_ARRAY) { + if (val->type == UCL_ARRAY) { val = val->value.ov; } LL_FOREACH (val, cur) { - if (!rspamd_cl_obj_tostring_safe (cur, &worker_bind)) { + if (!ucl_obj_tostring_safe (cur, &worker_bind)) { continue; } bcf = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_worker_bind_conf)); @@ -430,9 +430,9 @@ rspamd_rcl_worker_handler (struct config_file *cfg, rspamd_cl_object_t *obj, /* Parse other attributes */ HASH_FIND_INT (cfg->wrk_parsers, (gint *)&qtype, wparser); - if (wparser != NULL && obj->type == RSPAMD_CL_OBJECT) { + if (wparser != NULL && obj->type == UCL_OBJECT) { HASH_ITER (hh, obj->value.ov, cur, tmp) { - HASH_FIND_STR (wparser->parsers, cur->key, whandler); + HASH_FIND_STR (wparser->parsers, ucl_object_key (cur), whandler); if (whandler != NULL) { if (!whandler->handler (cfg, cur, &whandler->parser, section, err)) { return FALSE; @@ -451,7 +451,7 @@ rspamd_rcl_worker_handler (struct config_file *cfg, rspamd_cl_object_t *obj, * for default handlers */ static gboolean -rspamd_rcl_empty_handler (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_empty_handler (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { return rspamd_rcl_section_parse_defaults (section, cfg, obj, cfg, err); @@ -470,7 +470,7 @@ rspamd_rcl_empty_handler (struct config_file *cfg, rspamd_cl_object_t *obj, static inline struct rspamd_rcl_section* rspamd_rcl_add_section (struct rspamd_rcl_section *top, const gchar *name, rspamd_rcl_handler_t handler, - enum rspamd_cl_type type, gboolean required, gboolean strict_type) + enum ucl_type type, gboolean required, gboolean strict_type) { struct rspamd_rcl_section *new; @@ -520,7 +520,7 @@ rspamd_rcl_config_init (void) /** * Logging section */ - sub = rspamd_rcl_add_section (new, "logging", rspamd_rcl_logging_handler, RSPAMD_CL_OBJECT, + sub = rspamd_rcl_add_section (new, "logging", rspamd_rcl_logging_handler, UCL_OBJECT, FALSE, TRUE); /* Default handlers */ rspamd_rcl_add_default_handler (sub, "log_buffer", rspamd_rcl_parse_struct_integer, @@ -536,7 +536,7 @@ rspamd_rcl_config_init (void) /** * Options section */ - sub = rspamd_rcl_add_section (new, "options", rspamd_rcl_options_handler, RSPAMD_CL_OBJECT, + sub = rspamd_rcl_add_section (new, "options", rspamd_rcl_options_handler, UCL_OBJECT, FALSE, TRUE); rspamd_rcl_add_default_handler (sub, "statfile_pool_size", rspamd_rcl_parse_struct_integer, G_STRUCT_OFFSET (struct config_file, max_statfile_size), RSPAMD_CL_FLAG_INT_SIZE); @@ -580,13 +580,13 @@ rspamd_rcl_config_init (void) /** * Metric section */ - sub = rspamd_rcl_add_section (new, "metric", rspamd_rcl_metric_handler, RSPAMD_CL_OBJECT, + sub = rspamd_rcl_add_section (new, "metric", rspamd_rcl_metric_handler, UCL_OBJECT, FALSE, TRUE); /** * Worker section */ - sub = rspamd_rcl_add_section (new, "worker", rspamd_rcl_worker_handler, RSPAMD_CL_OBJECT, + sub = rspamd_rcl_add_section (new, "worker", rspamd_rcl_worker_handler, UCL_OBJECT, FALSE, TRUE); rspamd_rcl_add_default_handler (sub, "count", rspamd_rcl_parse_struct_integer, G_STRUCT_OFFSET (struct worker_conf, count), RSPAMD_CL_FLAG_INT_16); @@ -634,19 +634,19 @@ rspamd_rcl_config_get_section (struct rspamd_rcl_section *top, gboolean rspamd_read_rcl_config (struct rspamd_rcl_section *top, - struct config_file *cfg, rspamd_cl_object_t *obj, GError **err) + struct config_file *cfg, ucl_object_t *obj, GError **err) { - rspamd_cl_object_t *found; + ucl_object_t *found; struct rspamd_rcl_section *cur, *tmp; - if (obj->type != RSPAMD_CL_OBJECT) { + if (obj->type != UCL_OBJECT) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "top configuration must be an object"); return FALSE; } /* Iterate over known sections and ignore unknown ones */ HASH_ITER (hh, top, cur, tmp) { - found = rspamd_cl_obj_get_key (obj, cur->name); + found = ucl_obj_get_key (obj, cur->name); if (found == NULL) { if (cur->required) { g_set_error (err, CFG_RCL_ERROR, ENOENT, "required section %s is missing", cur->name); @@ -673,19 +673,19 @@ rspamd_read_rcl_config (struct rspamd_rcl_section *top, } gboolean rspamd_rcl_section_parse_defaults (struct rspamd_rcl_section *section, - struct config_file *cfg, rspamd_cl_object_t *obj, gpointer ptr, + struct config_file *cfg, ucl_object_t *obj, gpointer ptr, GError **err) { - rspamd_cl_object_t *found; + ucl_object_t *found; struct rspamd_rcl_default_handler_data *cur, *tmp; - if (obj->type != RSPAMD_CL_OBJECT) { + if (obj->type != UCL_OBJECT) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "default configuration must be an object"); return FALSE; } HASH_ITER (hh, section->default_parser, cur, tmp) { - found = rspamd_cl_obj_get_key (obj, cur->key); + found = ucl_obj_get_key (obj, cur->key); if (found != NULL) { cur->pd.user_struct = ptr; if (!cur->handler (cfg, found, &cur->pd, section, err)) { @@ -698,7 +698,7 @@ gboolean rspamd_rcl_section_parse_defaults (struct rspamd_rcl_section *section, } gboolean -rspamd_rcl_parse_struct_string (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_string (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; @@ -707,19 +707,19 @@ rspamd_rcl_parse_struct_string (struct config_file *cfg, rspamd_cl_object_t *obj target = (gchar **)(((gchar *)pd->user_struct) + pd->offset); switch (obj->type) { - case RSPAMD_CL_STRING: + case UCL_STRING: /* Direct assigning is safe, as object is likely linked to the cfg mem_pool */ - *target = obj->value.sv; + *target = ucl_copy_value_trash (obj); break; - case RSPAMD_CL_INT: + case UCL_INT: *target = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (*target, num_str_len, "%L", obj->value.iv); break; - case RSPAMD_CL_FLOAT: + case UCL_FLOAT: *target = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (*target, num_str_len, "%f", obj->value.dv); break; - case RSPAMD_CL_BOOLEAN: + case UCL_BOOLEAN: *target = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (*target, num_str_len, "%b", (gboolean)obj->value.iv); break; @@ -732,7 +732,7 @@ rspamd_rcl_parse_struct_string (struct config_file *cfg, rspamd_cl_object_t *obj } gboolean -rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_integer (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; @@ -747,7 +747,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob if (pd->flags == RSPAMD_CL_FLAG_INT_32) { target.i32p = (gint32 *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_toint_safe (obj, &val)) { + if (!ucl_obj_toint_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to integer"); return FALSE; } @@ -755,7 +755,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob } else if (pd->flags == RSPAMD_CL_FLAG_INT_64) { target.i64p = (gint64 *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_toint_safe (obj, &val)) { + if (!ucl_obj_toint_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to integer"); return FALSE; } @@ -763,7 +763,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob } else if (pd->flags == RSPAMD_CL_FLAG_INT_SIZE) { target.sp = (gsize *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_toint_safe (obj, &val)) { + if (!ucl_obj_toint_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to integer"); return FALSE; } @@ -771,7 +771,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob } else if (pd->flags == RSPAMD_CL_FLAG_INT_16) { target.i16p = (gint16 *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_toint_safe (obj, &val)) { + if (!ucl_obj_toint_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to integer"); return FALSE; } @@ -779,7 +779,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob } else { target.ip = (gint *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_toint_safe (obj, &val)) { + if (!ucl_obj_toint_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to integer"); return FALSE; } @@ -790,7 +790,7 @@ rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *ob } gboolean -rspamd_rcl_parse_struct_double (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_double (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; @@ -798,7 +798,7 @@ rspamd_rcl_parse_struct_double (struct config_file *cfg, rspamd_cl_object_t *obj target = (gdouble *)(((gchar *)pd->user_struct) + pd->offset); - if (!rspamd_cl_obj_todouble_safe (obj, target)) { + if (!ucl_obj_todouble_safe (obj, target)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to double"); return FALSE; } @@ -807,7 +807,7 @@ rspamd_rcl_parse_struct_double (struct config_file *cfg, rspamd_cl_object_t *obj } gboolean -rspamd_rcl_parse_struct_time (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_time (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; @@ -820,7 +820,7 @@ rspamd_rcl_parse_struct_time (struct config_file *cfg, rspamd_cl_object_t *obj, } target; gdouble val; - if (!rspamd_cl_obj_todouble_safe (obj, &val)) { + if (!ucl_obj_todouble_safe (obj, &val)) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "cannot convert param to double"); return FALSE; } @@ -856,37 +856,37 @@ rspamd_rcl_parse_struct_time (struct config_file *cfg, rspamd_cl_object_t *obj, } gboolean -rspamd_rcl_parse_struct_string_list (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_string_list (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; GList **target; gchar *val; - rspamd_cl_object_t *cur; + ucl_object_t *cur; const gsize num_str_len = 32; target = (GList **)(((gchar *)pd->user_struct) + pd->offset); - if (obj->type != RSPAMD_CL_ARRAY) { + if (obj->type != UCL_ARRAY) { g_set_error (err, CFG_RCL_ERROR, EINVAL, "an array of strings is expected"); return FALSE; } for (cur = obj; cur != NULL; cur = cur->next) { switch (cur->type) { - case RSPAMD_CL_STRING: + case UCL_STRING: /* Direct assigning is safe, as curect is likely linked to the cfg mem_pool */ - val = cur->value.sv; + val = ucl_copy_value_trash (obj); break; - case RSPAMD_CL_INT: + case UCL_INT: val = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (val, num_str_len, "%L", cur->value.iv); break; - case RSPAMD_CL_FLOAT: + case UCL_FLOAT: val = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (val, num_str_len, "%f", cur->value.dv); break; - case RSPAMD_CL_BOOLEAN: + case UCL_BOOLEAN: val = memory_pool_alloc (cfg->cfg_pool, num_str_len); rspamd_snprintf (val, num_str_len, "%b", (gboolean)cur->value.iv); break; @@ -904,7 +904,7 @@ rspamd_rcl_parse_struct_string_list (struct config_file *cfg, rspamd_cl_object_t } gboolean -rspamd_rcl_parse_struct_boolean (struct config_file *cfg, rspamd_cl_object_t *obj, +rspamd_rcl_parse_struct_boolean (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err) { struct rspamd_rcl_struct_parser *pd = ud; @@ -912,10 +912,10 @@ rspamd_rcl_parse_struct_boolean (struct config_file *cfg, rspamd_cl_object_t *ob target = (gboolean *)(((gchar *)pd->user_struct) + pd->offset); - if (obj->type == RSPAMD_CL_BOOLEAN) { + if (obj->type == UCL_BOOLEAN) { *target = obj->value.iv; } - else if (obj->type == RSPAMD_CL_INT) { + else if (obj->type == UCL_INT) { *target = obj->value.iv; } else { diff --git a/src/cfg_rcl.h b/src/cfg_rcl.h index fb0fea7e3..2c1441fbb 100644 --- a/src/cfg_rcl.h +++ b/src/cfg_rcl.h @@ -25,7 +25,7 @@ #define CFG_RCL_H_ #include "config.h" -#include "rcl/rcl.h" +#include "ucl/include/ucl.h" #define CFG_RCL_ERROR cfg_rcl_error_quark () static inline GQuark @@ -61,7 +61,7 @@ struct rspamd_rcl_struct_parser { * @param err error object * @return TRUE if a section has been parsed */ -typedef gboolean (*rspamd_rcl_handler_t) (struct config_file *cfg, rspamd_cl_object_t *obj, +typedef gboolean (*rspamd_rcl_handler_t) (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); struct rspamd_rcl_default_handler_data { @@ -74,7 +74,7 @@ struct rspamd_rcl_default_handler_data { struct rspamd_rcl_section { const gchar *name; /**< name of section */ rspamd_rcl_handler_t handler; /**< handler of section attributes */ - enum rspamd_cl_type type; /**< type of attribute */ + enum ucl_type type; /**< type of attribute */ gboolean required; /**< whether this param is required */ gboolean strict_type; /**< whether we need strict type */ UT_hash_handle hh; /** hash handle */ @@ -105,7 +105,7 @@ struct rspamd_rcl_section *rspamd_rcl_config_get_section (struct rspamd_rcl_sect * @return TRUE if an object can be parsed */ gboolean rspamd_read_rcl_config (struct rspamd_rcl_section *top, - struct config_file *cfg, rspamd_cl_object_t *obj, GError **err); + struct config_file *cfg, ucl_object_t *obj, GError **err); /** @@ -118,7 +118,7 @@ gboolean rspamd_read_rcl_config (struct rspamd_rcl_section *top, * @return TRUE if the object has been parsed */ gboolean rspamd_rcl_section_parse_defaults (struct rspamd_rcl_section *section, - struct config_file *cfg, rspamd_cl_object_t *obj, gpointer ptr, + struct config_file *cfg, ucl_object_t *obj, gpointer ptr, GError **err); /** * Here is a section of common handlers that accepts rcl_struct_parser @@ -135,7 +135,7 @@ gboolean rspamd_rcl_section_parse_defaults (struct rspamd_rcl_section *section, * @param err error pointer * @return TRUE if a string value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_string (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_string (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); /** @@ -147,7 +147,7 @@ gboolean rspamd_rcl_parse_struct_string (struct config_file *cfg, rspamd_cl_obje * @param err error pointer * @return TRUE if a value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_integer (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); @@ -160,7 +160,7 @@ gboolean rspamd_rcl_parse_struct_integer (struct config_file *cfg, rspamd_cl_obj * @param err error pointer * @return TRUE if a value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_double (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_double (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); /** @@ -172,7 +172,7 @@ gboolean rspamd_rcl_parse_struct_double (struct config_file *cfg, rspamd_cl_obje * @param err error pointer * @return TRUE if a value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_time (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_time (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); /** @@ -184,7 +184,7 @@ gboolean rspamd_rcl_parse_struct_time (struct config_file *cfg, rspamd_cl_object * @param err error pointer * @return TRUE if a value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_string_list (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_string_list (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); /** @@ -196,7 +196,7 @@ gboolean rspamd_rcl_parse_struct_string_list (struct config_file *cfg, rspamd_cl * @param err error pointer * @return TRUE if a value has been successfully parsed */ -gboolean rspamd_rcl_parse_struct_boolean (struct config_file *cfg, rspamd_cl_object_t *obj, +gboolean rspamd_rcl_parse_struct_boolean (struct config_file *cfg, ucl_object_t *obj, gpointer ud, struct rspamd_rcl_section *section, GError **err); /** diff --git a/src/cfg_utils.c b/src/cfg_utils.c index 906f4406f..8a49237ac 100644 --- a/src/cfg_utils.c +++ b/src/cfg_utils.c @@ -265,7 +265,7 @@ free_config (struct config_file *cfg) struct symbols_group *gr; remove_all_maps (cfg); - rspamd_cl_obj_unref (cfg->rcl_obj); + ucl_obj_unref (cfg->rcl_obj); g_hash_table_remove_all (cfg->variables); g_hash_table_unref (cfg->variables); g_hash_table_remove_all (cfg->metrics); @@ -299,14 +299,14 @@ free_config (struct config_file *cfg) memory_pool_delete (cfg->cfg_pool); } -rspamd_cl_object_t * +ucl_object_t * get_module_opt (struct config_file *cfg, const gchar *module_name, const gchar *opt_name) { - rspamd_cl_object_t *res = NULL, *sec; + ucl_object_t *res = NULL, *sec; - sec = rspamd_cl_obj_get_key (cfg->rcl_obj, module_name); + sec = ucl_obj_get_key (cfg->rcl_obj, module_name); if (sec != NULL) { - res = rspamd_cl_obj_get_key (cfg->rcl_obj, opt_name); + res = ucl_obj_get_key (cfg->rcl_obj, opt_name); } return res; diff --git a/src/lua/lua_cfg_file.c b/src/lua/lua_cfg_file.c index 4f7a3bca5..0e22ec8f2 100644 --- a/src/lua/lua_cfg_file.c +++ b/src/lua/lua_cfg_file.c @@ -125,7 +125,7 @@ lua_post_load_config (struct config_file *cfg) const gchar *name, *val; gchar *sym; struct expression *expr, *old_expr; - rspamd_cl_object_t *obj; + ucl_object_t *obj; gsize keylen; /* First check all module options that may be overriden in 'config' global */ @@ -140,10 +140,7 @@ lua_post_load_config (struct config_file *cfg) if (name != NULL && lua_istable (L, -1)) { obj = lua_rcl_obj_get (L, -1); if (obj != NULL) { - obj->key = memory_pool_alloc (cfg->cfg_pool, keylen + 1); - memcpy (obj->key, name, keylen); - obj->key[keylen] = '\0'; - HASH_ADD_KEYPTR (hh, cfg->rcl_obj->value.ov, obj->key, keylen, obj); + cfg->rcl_obj = ucl_object_insert_key (cfg->rcl_obj, obj, name, keylen, true); } } } diff --git a/src/lua/lua_common.h b/src/lua/lua_common.h index c6ba06160..c878a50f3 100644 --- a/src/lua/lua_common.h +++ b/src/lua/lua_common.h @@ -6,7 +6,7 @@ #include "main.h" #include "cfg_file.h" -#include "rcl/rcl.h" +#include "ucl.h" #include <lua.h> #include <lauxlib.h> #include <lualib.h> @@ -107,14 +107,14 @@ void free_lua_locked (struct lua_locked_state *st); * @param L lua state * @param obj object to push */ -gint lua_rcl_obj_push (lua_State *L, rspamd_cl_object_t *obj); +gint lua_rcl_obj_push (lua_State *L, ucl_object_t *obj); /** * Extract rcl object from lua object * @param L * @return */ -rspamd_cl_object_t * lua_rcl_obj_get (lua_State *L, gint idx); +ucl_object_t * lua_rcl_obj_get (lua_State *L, gint idx); /** * Open libraries functions diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c index d3b7c6af9..df8526d0f 100644 --- a/src/lua/lua_config.c +++ b/src/lua/lua_config.c @@ -154,7 +154,7 @@ lua_config_get_module_opt (lua_State * L) { struct config_file *cfg = lua_check_config (L); const gchar *mname, *optname; - rspamd_cl_object_t *obj; + ucl_object_t *obj; if (cfg) { mname = luaL_checkstring (L, 2); @@ -190,13 +190,13 @@ lua_config_get_all_opt (lua_State * L) { struct config_file *cfg = lua_check_config (L); const gchar *mname; - rspamd_cl_object_t *obj; + ucl_object_t *obj; if (cfg) { mname = luaL_checkstring (L, 2); if (mname) { - obj = rspamd_cl_obj_get_key (cfg->rcl_obj, mname); + obj = ucl_obj_get_key (cfg->rcl_obj, mname); if (obj != NULL) { return lua_rcl_obj_push (L, obj); } diff --git a/src/lua/lua_rcl.c b/src/lua/lua_rcl.c index 91f1fff79..b695f4f11 100644 --- a/src/lua/lua_rcl.c +++ b/src/lua/lua_rcl.c @@ -22,16 +22,15 @@ */ #include "lua_common.h" -#include "rcl/rcl.h" /** * @file lua rcl bindings */ -static gint lua_rcl_obj_push_array (lua_State *L, rspamd_cl_object_t *obj); -static gint lua_rcl_obj_push_simple (lua_State *L, rspamd_cl_object_t *obj); -static void lua_rcl_table_get (lua_State *L, rspamd_cl_object_t *top, gint idx); -static void lua_rcl_elt_get (lua_State *L, rspamd_cl_object_t *top, gint idx); +static gint lua_rcl_obj_push_array (lua_State *L, ucl_object_t *obj); +static gint lua_rcl_obj_push_simple (lua_State *L, ucl_object_t *obj); +static void lua_rcl_table_get (lua_State *L, ucl_object_t *top, gint idx); +static void lua_rcl_elt_get (lua_State *L, ucl_object_t *top, gint idx); /** * Push a single element of an object to lua @@ -40,7 +39,7 @@ static void lua_rcl_elt_get (lua_State *L, rspamd_cl_object_t *top, gint idx); * @param obj */ static void -lua_rcl_obj_push_elt (lua_State *L, const char *key, rspamd_cl_object_t *obj) +lua_rcl_obj_push_elt (lua_State *L, const char *key, ucl_object_t *obj) { lua_pushstring (L, key); lua_rcl_obj_push (L, obj); @@ -54,9 +53,9 @@ lua_rcl_obj_push_elt (lua_State *L, const char *key, rspamd_cl_object_t *obj) * @return */ static gint -lua_rcl_obj_push_obj (lua_State *L, rspamd_cl_object_t *obj) +lua_rcl_obj_push_obj (lua_State *L, ucl_object_t *obj) { - rspamd_cl_object_t *cur, *tmp; + ucl_object_t *cur, *tmp; if (obj->next != NULL) { /* Actually we need to push this as an array */ @@ -65,7 +64,7 @@ lua_rcl_obj_push_obj (lua_State *L, rspamd_cl_object_t *obj) lua_newtable (L); HASH_ITER (hh, obj, cur, tmp) { - lua_rcl_obj_push_elt (L, obj->key, obj); + lua_rcl_obj_push_elt (L, ucl_object_key (obj), obj); } return 1; @@ -78,9 +77,9 @@ lua_rcl_obj_push_obj (lua_State *L, rspamd_cl_object_t *obj) * @return */ static gint -lua_rcl_obj_push_array (lua_State *L, rspamd_cl_object_t *obj) +lua_rcl_obj_push_array (lua_State *L, ucl_object_t *obj) { - rspamd_cl_object_t *cur; + ucl_object_t *cur; gint i = 1; lua_newtable (L); @@ -98,7 +97,7 @@ lua_rcl_obj_push_array (lua_State *L, rspamd_cl_object_t *obj) * Push a simple object to lua depending on its actual type */ static gint -lua_rcl_obj_push_simple (lua_State *L, rspamd_cl_object_t *obj) +lua_rcl_obj_push_simple (lua_State *L, ucl_object_t *obj) { if (obj->next != NULL) { /* Actually we need to push this as an array */ @@ -106,22 +105,22 @@ lua_rcl_obj_push_simple (lua_State *L, rspamd_cl_object_t *obj) } switch (obj->type) { - case RSPAMD_CL_BOOLEAN: - lua_pushboolean (L, rspamd_cl_obj_toboolean (obj)); + case UCL_BOOLEAN: + lua_pushboolean (L, ucl_obj_toboolean (obj)); break; - case RSPAMD_CL_STRING: - lua_pushstring (L, rspamd_cl_obj_tostring (obj)); + case UCL_STRING: + lua_pushstring (L, ucl_obj_tostring (obj)); break; - case RSPAMD_CL_INT: + case UCL_INT: #if LUA_VERSION_NUM >= 501 - lua_pushinteger (L, rspamd_cl_obj_toint (obj)); + lua_pushinteger (L, ucl_obj_toint (obj)); #else - lua_pushnumber (L, rspamd_cl_obj_toint (obj)); + lua_pushnumber (L, ucl_obj_toint (obj)); #endif break; - case RSPAMD_CL_FLOAT: - case RSPAMD_CL_TIME: - lua_pushnumber (L, rspamd_cl_obj_todouble (obj)); + case UCL_FLOAT: + case UCL_TIME: + lua_pushnumber (L, ucl_obj_todouble (obj)); break; default: lua_pushnil (L); @@ -137,12 +136,12 @@ lua_rcl_obj_push_simple (lua_State *L, rspamd_cl_object_t *obj) * @param obj object to push */ gint -lua_rcl_obj_push (lua_State *L, rspamd_cl_object_t *obj) +lua_rcl_obj_push (lua_State *L, ucl_object_t *obj) { switch (obj->type) { - case RSPAMD_CL_OBJECT: + case UCL_OBJECT: return lua_rcl_obj_push_obj (L, obj->value.ov); - case RSPAMD_CL_ARRAY: + case UCL_ARRAY: return lua_rcl_obj_push_array (L, obj->value.ov); default: return lua_rcl_obj_push_simple (L, obj); @@ -156,9 +155,9 @@ lua_rcl_obj_push (lua_State *L, rspamd_cl_object_t *obj) * @param idx */ static void -lua_rcl_table_get (lua_State *L, rspamd_cl_object_t *top, gint idx) +lua_rcl_table_get (lua_State *L, ucl_object_t *top, gint idx) { - rspamd_cl_object_t *obj; + ucl_object_t *obj; gsize keylen; const gchar *k; @@ -168,13 +167,10 @@ lua_rcl_table_get (lua_State *L, rspamd_cl_object_t *top, gint idx) while (lua_next (L, -2) != 0) { /* copy key to avoid modifications */ lua_pushvalue (L, -2); - obj = rspamd_cl_object_new (); + obj = ucl_object_new (); if (obj != NULL) { k = lua_tolstring (L, -1, &keylen); - obj->key = g_malloc (keylen + 1); - memcpy (obj->key, k, keylen); - obj->key[keylen] = '\0'; - HASH_ADD_KEYPTR (hh, top->value.ov, obj->key, keylen, obj); + ucl_object_insert_key (top, obj, k, keylen, true); lua_rcl_elt_get (L, obj, -2); } @@ -190,7 +186,7 @@ lua_rcl_table_get (lua_State *L, rspamd_cl_object_t *top, gint idx) * @param idx */ static void -lua_rcl_elt_get (lua_State *L, rspamd_cl_object_t *obj, gint idx) +lua_rcl_elt_get (lua_State *L, ucl_object_t *obj, gint idx) { gint type; @@ -199,23 +195,23 @@ lua_rcl_elt_get (lua_State *L, rspamd_cl_object_t *obj, gint idx) switch (type) { case LUA_TFUNCTION: lua_pushvalue (L, idx); - obj->type = RSPAMD_CL_USERDATA; + obj->type = UCL_USERDATA; obj->value.ud = GINT_TO_POINTER (luaL_ref (L, LUA_REGISTRYINDEX)); break; case LUA_TSTRING: - obj->type = RSPAMD_CL_STRING; + obj->type = UCL_STRING; obj->value.sv = g_strdup (lua_tostring (L, idx)); break; case LUA_TNUMBER: - obj->type = RSPAMD_CL_FLOAT; + obj->type = UCL_FLOAT; obj->value.dv = lua_tonumber (L, idx); break; case LUA_TBOOLEAN: - obj->type = RSPAMD_CL_BOOLEAN; + obj->type = UCL_BOOLEAN; obj->value.iv = lua_toboolean (L, idx); break; case LUA_TTABLE: - obj->type = RSPAMD_CL_OBJECT; + obj->type = UCL_OBJECT; lua_rcl_table_get (L, obj, idx); break; } @@ -226,13 +222,13 @@ lua_rcl_elt_get (lua_State *L, rspamd_cl_object_t *obj, gint idx) * @param L * @return */ -rspamd_cl_object_t * +ucl_object_t * lua_rcl_obj_get (lua_State *L, gint idx) { - rspamd_cl_object_t *obj; + ucl_object_t *obj; gint t; - obj = rspamd_cl_object_new (); + obj = ucl_object_new (); if (obj != NULL) { t = lua_type (L, idx); diff --git a/src/plugins/chartable.c b/src/plugins/chartable.c index a140c5893..69197dfd6 100644 --- a/src/plugins/chartable.c +++ b/src/plugins/chartable.c @@ -86,17 +86,17 @@ chartable_module_init (struct config_file *cfg, struct module_ctx **ctx) gint chartable_module_config (struct config_file *cfg) { - rspamd_cl_object_t *value; + ucl_object_t *value; gint res = TRUE; if ((value = get_module_opt (cfg, "chartable", "symbol")) != NULL) { - chartable_module_ctx->symbol = rspamd_cl_obj_tostring (value); + chartable_module_ctx->symbol = ucl_obj_tostring (value); } else { chartable_module_ctx->symbol = DEFAULT_SYMBOL; } if ((value = get_module_opt (cfg, "chartable", "threshold")) != NULL) { - if (!rspamd_cl_obj_todouble_safe (value, &chartable_module_ctx->threshold)) { + if (!ucl_obj_todouble_safe (value, &chartable_module_ctx->threshold)) { msg_warn ("invalid numeric value"); chartable_module_ctx->threshold = DEFAULT_THRESHOLD; } diff --git a/src/plugins/dkim_check.c b/src/plugins/dkim_check.c index 7ae11631c..8e3fa4c23 100644 --- a/src/plugins/dkim_check.c +++ b/src/plugins/dkim_check.c @@ -114,7 +114,7 @@ dkim_module_init (struct config_file *cfg, struct module_ctx **ctx) gint dkim_module_config (struct config_file *cfg) { - rspamd_cl_object_t *value; + ucl_object_t *value; gint res = TRUE; guint cache_size, cache_expire; gboolean got_trusted = FALSE; @@ -122,72 +122,72 @@ dkim_module_config (struct config_file *cfg) dkim_module_ctx->whitelist_ip = radix_tree_create (); if ((value = get_module_opt (cfg, "dkim", "symbol_reject")) != NULL) { - dkim_module_ctx->symbol_reject = rspamd_cl_obj_tostring (value); + dkim_module_ctx->symbol_reject = ucl_obj_tostring (value); } else { dkim_module_ctx->symbol_reject = DEFAULT_SYMBOL_REJECT; } if ((value = get_module_opt (cfg, "dkim", "symbol_tempfail")) != NULL) { - dkim_module_ctx->symbol_tempfail = rspamd_cl_obj_tostring (value); + dkim_module_ctx->symbol_tempfail = ucl_obj_tostring (value); } else { dkim_module_ctx->symbol_tempfail = DEFAULT_SYMBOL_TEMPFAIL; } if ((value = get_module_opt (cfg, "dkim", "symbol_allow")) != NULL) { - dkim_module_ctx->symbol_allow = rspamd_cl_obj_tostring (value); + dkim_module_ctx->symbol_allow = ucl_obj_tostring (value); } else { dkim_module_ctx->symbol_allow = DEFAULT_SYMBOL_ALLOW; } if ((value = get_module_opt (cfg, "dkim", "dkim_cache_size")) != NULL) { - cache_size = rspamd_cl_obj_toint (value); + cache_size = ucl_obj_toint (value); } else { cache_size = DEFAULT_CACHE_SIZE; } if ((value = get_module_opt (cfg, "dkim", "dkim_cache_expire")) != NULL) { - cache_expire = rspamd_cl_obj_todouble (value); + cache_expire = ucl_obj_todouble (value); } else { cache_expire = DEFAULT_CACHE_MAXAGE; } if ((value = get_module_opt (cfg, "dkim", "time_jitter")) != NULL) { - dkim_module_ctx->time_jitter = rspamd_cl_obj_todouble (value); + dkim_module_ctx->time_jitter = ucl_obj_todouble (value); } else { dkim_module_ctx->time_jitter = DEFAULT_TIME_JITTER; } if ((value = get_module_opt (cfg, "dkim", "whitelist")) != NULL) { - if (! add_map (cfg, rspamd_cl_obj_tostring (value), + if (! add_map (cfg, ucl_obj_tostring (value), "DKIM whitelist", read_radix_list, fin_radix_list, (void **)&dkim_module_ctx->whitelist_ip)) { - msg_warn ("cannot load whitelist from %s", rspamd_cl_obj_tostring (value)); + msg_warn ("cannot load whitelist from %s", ucl_obj_tostring (value)); } } if ((value = get_module_opt (cfg, "dkim", "domains")) != NULL) { - if (! add_map (cfg, rspamd_cl_obj_tostring (value), + if (! add_map (cfg, ucl_obj_tostring (value), "DKIM domains", read_kv_list, fin_kv_list, (void **)&dkim_module_ctx->dkim_domains)) { - msg_warn ("cannot load dkim domains list from %s", rspamd_cl_obj_tostring (value)); + msg_warn ("cannot load dkim domains list from %s", ucl_obj_tostring (value)); } else { got_trusted = TRUE; } } if ((value = get_module_opt (cfg, "dkim", "strict_multiplier")) != NULL) { - dkim_module_ctx->strict_multiplier = rspamd_cl_obj_toint (value); + dkim_module_ctx->strict_multiplier = ucl_obj_toint (value); } else { dkim_module_ctx->strict_multiplier = 1; } if ((value = get_module_opt (cfg, "dkim", "trusted_only")) != NULL) { - dkim_module_ctx->trusted_only = rspamd_cl_obj_toboolean (value); + dkim_module_ctx->trusted_only = ucl_obj_toboolean (value); } else { dkim_module_ctx->trusted_only = FALSE; } if ((value = get_module_opt (cfg, "dkim", "skip_multi")) != NULL) { - dkim_module_ctx->skip_multi = rspamd_cl_obj_toboolean (value); + dkim_module_ctx->skip_multi = ucl_obj_toboolean (value); } else { dkim_module_ctx->skip_multi = FALSE; diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c index 4a900d4db..60549b849 100644 --- a/src/plugins/fuzzy_check.c +++ b/src/plugins/fuzzy_check.c @@ -186,24 +186,24 @@ parse_flags_string_old (struct config_file *cfg, const gchar *str) } static void -parse_flags_string (struct config_file *cfg, rspamd_cl_object_t *val) +parse_flags_string (struct config_file *cfg, ucl_object_t *val) { - rspamd_cl_object_t *elt; + ucl_object_t *elt; struct fuzzy_mapping *map; - if (val->type == RSPAMD_CL_STRING) { - parse_flags_string_old (cfg, rspamd_cl_obj_tostring (val)); + if (val->type == UCL_STRING) { + parse_flags_string_old (cfg, ucl_obj_tostring (val)); } - else if (val->type == RSPAMD_CL_OBJECT) { - elt = rspamd_cl_obj_get_key (val, "symbol"); + else if (val->type == UCL_OBJECT) { + elt = ucl_obj_get_key (val, "symbol"); if (elt != NULL) { map = memory_pool_alloc (fuzzy_module_ctx->fuzzy_pool, sizeof (struct fuzzy_mapping)); - map->symbol = rspamd_cl_obj_tostring (elt); - elt = rspamd_cl_obj_get_key (val, "flag"); - if (elt != NULL && rspamd_cl_obj_toint_safe (elt, &map->fuzzy_flag)) { - elt = rspamd_cl_obj_get_key (val, "weight"); + map->symbol = ucl_obj_tostring (elt); + elt = ucl_obj_get_key (val, "flag"); + if (elt != NULL && ucl_obj_toint_safe (elt, &map->fuzzy_flag)) { + elt = ucl_obj_get_key (val, "weight"); if (elt != NULL) { - map->weight = rspamd_cl_obj_todouble (elt); + map->weight = ucl_obj_todouble (elt); } else { map->weight = fuzzy_module_ctx->max_score; @@ -377,64 +377,64 @@ fuzzy_check_module_init (struct config_file *cfg, struct module_ctx **ctx) gint fuzzy_check_module_config (struct config_file *cfg) { - rspamd_cl_object_t *value, *cur; + ucl_object_t *value, *cur; gint res = TRUE; if ((value = get_module_opt (cfg, "fuzzy_check", "symbol")) != NULL) { - fuzzy_module_ctx->symbol = rspamd_cl_obj_tostring (value); + fuzzy_module_ctx->symbol = ucl_obj_tostring (value); } else { fuzzy_module_ctx->symbol = DEFAULT_SYMBOL; } if ((value = get_module_opt (cfg, "fuzzy_check", "max_score")) != NULL) { - fuzzy_module_ctx->max_score = rspamd_cl_obj_todouble (value); + fuzzy_module_ctx->max_score = ucl_obj_todouble (value); } else { fuzzy_module_ctx->max_score = 0.; } if ((value = get_module_opt (cfg, "fuzzy_check", "min_length")) != NULL) { - fuzzy_module_ctx->min_hash_len = rspamd_cl_obj_toint (value); + fuzzy_module_ctx->min_hash_len = ucl_obj_toint (value); } else { fuzzy_module_ctx->min_hash_len = 0; } if ((value = get_module_opt (cfg, "fuzzy_check", "min_bytes")) != NULL) { - fuzzy_module_ctx->min_bytes = rspamd_cl_obj_toint (value); + fuzzy_module_ctx->min_bytes = ucl_obj_toint (value); } else { fuzzy_module_ctx->min_bytes = 0; } if ((value = get_module_opt (cfg, "fuzzy_check", "min_height")) != NULL) { - fuzzy_module_ctx->min_height = rspamd_cl_obj_toint (value); + fuzzy_module_ctx->min_height = ucl_obj_toint (value); } else { fuzzy_module_ctx->min_height = 0; } if ((value = get_module_opt (cfg, "fuzzy_check", "min_width")) != NULL) { - fuzzy_module_ctx->min_width = rspamd_cl_obj_toint (value); + fuzzy_module_ctx->min_width = ucl_obj_toint (value); } else { fuzzy_module_ctx->min_width = 0; } if ((value = get_module_opt (cfg, "fuzzy_check", "timeout")) != NULL) { - fuzzy_module_ctx->io_timeout = rspamd_cl_obj_todouble (value) * 1000; + fuzzy_module_ctx->io_timeout = ucl_obj_todouble (value) * 1000; } else { fuzzy_module_ctx->io_timeout = DEFAULT_IO_TIMEOUT; } if ((value = get_module_opt (cfg, "fuzzy_check", "mime_types")) != NULL) { LL_FOREACH (value, cur) { - fuzzy_module_ctx->mime_types = parse_mime_types (rspamd_cl_obj_tostring (cur)); + fuzzy_module_ctx->mime_types = parse_mime_types (ucl_obj_tostring (cur)); } } if ((value = get_module_opt (cfg, "fuzzy_check", "whitelist")) != NULL) { fuzzy_module_ctx->whitelist = radix_tree_create (); - if (!add_map (cfg, rspamd_cl_obj_tostring (value), + if (!add_map (cfg, ucl_obj_tostring (value), "Fuzzy whitelist", read_radix_list, fin_radix_list, (void **)&fuzzy_module_ctx->whitelist)) { - msg_err ("cannot add whitelist '%s'", rspamd_cl_obj_tostring (value)); + msg_err ("cannot add whitelist '%s'", ucl_obj_tostring (value)); } } else { @@ -443,7 +443,7 @@ fuzzy_check_module_config (struct config_file *cfg) if ((value = get_module_opt (cfg, "fuzzy_check", "servers")) != NULL) { LL_FOREACH (value, cur) { - parse_servers_string (rspamd_cl_obj_tostring (cur)); + parse_servers_string (ucl_obj_tostring (cur)); } } if ((value = get_module_opt (cfg, "fuzzy_check", "fuzzy_map")) != NULL) { diff --git a/src/plugins/regexp.c b/src/plugins/regexp.c index f3eadfa45..a1c4fa049 100644 --- a/src/plugins/regexp.c +++ b/src/plugins/regexp.c @@ -44,7 +44,7 @@ struct regexp_module_item { struct expression *expr; - gchar *symbol; + const gchar *symbol; guint32 avg_time; gpointer lua_function; }; @@ -574,12 +574,12 @@ gint regexp_module_config (struct config_file *cfg) { struct regexp_module_item *cur_item; - rspamd_cl_object_t *sec, *value, *tmp; + ucl_object_t *sec, *value, *tmp; gint res = TRUE; struct regexp_json_buf *jb, **pjb; - HASH_FIND_STR (cfg->rcl_obj, "regexp", sec); + sec = ucl_object_find_key (cfg->rcl_obj, "regexp"); if (sec == NULL) { msg_err ("regexp module enabled, but no rules are defined"); return TRUE; @@ -590,44 +590,44 @@ regexp_module_config (struct config_file *cfg) regexp_module_ctx->workers = NULL; HASH_ITER (hh, sec, value, tmp) { - if (g_ascii_strncasecmp (value->key, "autolearn", sizeof ("autolearn") - 1) == 0) { - parse_autolearn_param (value->key, rspamd_cl_obj_tostring (value), cfg); + if (g_ascii_strncasecmp (ucl_object_key (value), "autolearn", sizeof ("autolearn") - 1) == 0) { + parse_autolearn_param (ucl_object_key (value), ucl_obj_tostring (value), cfg); } - else if (g_ascii_strncasecmp (value->key, "dynamic_rules", sizeof ("dynamic_rules") - 1) == 0) { + else if (g_ascii_strncasecmp (ucl_object_key (value), "dynamic_rules", sizeof ("dynamic_rules") - 1) == 0) { jb = g_malloc (sizeof (struct regexp_json_buf)); pjb = g_malloc (sizeof (struct regexp_json_buf *)); jb->buf = NULL; jb->cfg = cfg; *pjb = jb; - if (!add_map (cfg, rspamd_cl_obj_tostring (value), + if (!add_map (cfg, ucl_obj_tostring (value), "Dynamic regexp rules", json_regexp_read_cb, json_regexp_fin_cb, (void **)pjb)) { - msg_err ("cannot add map %s", rspamd_cl_obj_tostring (value)); + msg_err ("cannot add map %s", ucl_obj_tostring (value)); } } - else if (g_ascii_strncasecmp (value->key, "max_size", sizeof ("max_size") - 1) == 0) { - regexp_module_ctx->max_size = rspamd_cl_obj_toint (value); + else if (g_ascii_strncasecmp (ucl_object_key (value), "max_size", sizeof ("max_size") - 1) == 0) { + regexp_module_ctx->max_size = ucl_obj_toint (value); } - else if (g_ascii_strncasecmp (value->key, "max_threads", sizeof ("max_threads") - 1) == 0) { - regexp_module_ctx->max_threads = rspamd_cl_obj_toint (value); + else if (g_ascii_strncasecmp (ucl_object_key (value), "max_threads", sizeof ("max_threads") - 1) == 0) { + regexp_module_ctx->max_threads = ucl_obj_toint (value); } - else if (value->type == RSPAMD_CL_STRING) { + else if (value->type == UCL_STRING) { cur_item = memory_pool_alloc0 (regexp_module_ctx->regexp_pool, sizeof (struct regexp_module_item)); - cur_item->symbol = value->key; - if (!read_regexp_expression (regexp_module_ctx->regexp_pool, cur_item, value->key, - rspamd_cl_obj_tostring (value), cfg->raw_mode)) { + cur_item->symbol = ucl_object_key (value); + if (!read_regexp_expression (regexp_module_ctx->regexp_pool, cur_item, ucl_object_key (value), + ucl_obj_tostring (value), cfg->raw_mode)) { res = FALSE; } register_symbol (&cfg->cache, cur_item->symbol, 1, process_regexp_item, cur_item); } - else if (value->type == RSPAMD_CL_USERDATA) { + else if (value->type == UCL_USERDATA) { cur_item = memory_pool_alloc0 (regexp_module_ctx->regexp_pool, sizeof (struct regexp_module_item)); - cur_item->symbol = value->key; + cur_item->symbol = ucl_object_key (value); cur_item->lua_function = value->value.ud; register_symbol (&cfg->cache, cur_item->symbol, 1, process_regexp_item, cur_item); } else { - msg_warn ("unknown type of attribute %s for regexp module", value->key); + msg_warn ("unknown type of attribute %s for regexp module", ucl_object_key (value)); } } @@ -1112,7 +1112,7 @@ optimize_regexp_expression (struct expression **e, GQueue * stack, gboolean res) } static gboolean -process_regexp_expression (struct expression *expr, gchar *symbol, struct worker_task *task, +process_regexp_expression (struct expression *expr, const gchar *symbol, struct worker_task *task, const gchar *additional, struct lua_locked_state *nL) { GQueue *stack; diff --git a/src/plugins/spf.c b/src/plugins/spf.c index 254190961..cf1fbb180 100644 --- a/src/plugins/spf.c +++ b/src/plugins/spf.c @@ -101,44 +101,44 @@ spf_module_init (struct config_file *cfg, struct module_ctx **ctx) gint spf_module_config (struct config_file *cfg) { - rspamd_cl_object_t *value; + ucl_object_t *value; gint res = TRUE; guint cache_size, cache_expire; spf_module_ctx->whitelist_ip = radix_tree_create (); if ((value = get_module_opt (cfg, "spf", "symbol_fail")) != NULL) { - spf_module_ctx->symbol_fail = rspamd_cl_obj_tostring (value); + spf_module_ctx->symbol_fail = ucl_obj_tostring (value); } else { spf_module_ctx->symbol_fail = DEFAULT_SYMBOL_FAIL; } if ((value = get_module_opt (cfg, "spf", "symbol_softfail")) != NULL) { - spf_module_ctx->symbol_softfail = rspamd_cl_obj_tostring (value); + spf_module_ctx->symbol_softfail = ucl_obj_tostring (value); } else { spf_module_ctx->symbol_softfail = DEFAULT_SYMBOL_SOFTFAIL; } if ((value = get_module_opt (cfg, "spf", "symbol_allow")) != NULL) { - spf_module_ctx->symbol_allow = rspamd_cl_obj_tostring (value); + spf_module_ctx->symbol_allow = ucl_obj_tostring (value); } else { spf_module_ctx->symbol_allow = DEFAULT_SYMBOL_ALLOW; } if ((value = get_module_opt (cfg, "spf", "spf_cache_size")) != NULL) { - cache_size = rspamd_cl_obj_toint (value); + cache_size = ucl_obj_toint (value); } else { cache_size = DEFAULT_CACHE_SIZE; } if ((value = get_module_opt (cfg, "spf", "spf_cache_expire")) != NULL) { - cache_expire = rspamd_cl_obj_toint (value); + cache_expire = ucl_obj_toint (value); } else { cache_expire = DEFAULT_CACHE_MAXAGE; } if ((value = get_module_opt (cfg, "spf", "whitelist")) != NULL) { - if (! add_map (cfg, rspamd_cl_obj_tostring (value), + if (! add_map (cfg, ucl_obj_tostring (value), "SPF whitelist", read_radix_list, fin_radix_list, (void **)&spf_module_ctx->whitelist_ip)) { msg_warn ("cannot load whitelist from %s", value); diff --git a/src/plugins/surbl.c b/src/plugins/surbl.c index 8b5121aa8..5d49eccec 100644 --- a/src/plugins/surbl.c +++ b/src/plugins/surbl.c @@ -303,7 +303,7 @@ surbl_module_config (struct config_file *cfg) struct suffix_item *new_suffix, *cur_suffix = NULL; struct surbl_bit_item *new_bit; - rspamd_cl_object_t *value, *cur, *cur_rule, *tmp, *cur_bit; + ucl_object_t *value, *cur, *cur_rule, *tmp, *cur_bit; const gchar *redir_val; guint32 bit; gint i, idx; @@ -318,7 +318,7 @@ surbl_module_config (struct config_file *cfg) i * sizeof (struct redirector_upstream)); idx = 0; LL_FOREACH (value, cur) { - redir_val = rspamd_cl_obj_tostring (cur); + redir_val = ucl_obj_tostring (cur); surbl_module_ctx->redirectors[idx].up.priority = 100; if (! parse_host_port_priority (surbl_module_ctx->surbl_pool, redir_val, &surbl_module_ctx->redirectors[idx].addr, @@ -340,100 +340,100 @@ surbl_module_config (struct config_file *cfg) surbl_module_ctx->use_redirector = (surbl_module_ctx->redirectors_number != 0); } if ((value = get_module_opt (cfg, "surbl", "redirector_symbol")) != NULL) { - surbl_module_ctx->redirector_symbol = rspamd_cl_obj_tostring (value); + surbl_module_ctx->redirector_symbol = ucl_obj_tostring (value); register_virtual_symbol (&cfg->cache, surbl_module_ctx->redirector_symbol, 1.0); } else { surbl_module_ctx->redirector_symbol = NULL; } if ((value = get_module_opt (cfg, "surbl", "weight")) != NULL) { - surbl_module_ctx->weight = rspamd_cl_obj_toint (value); + surbl_module_ctx->weight = ucl_obj_toint (value); } else { surbl_module_ctx->weight = DEFAULT_SURBL_WEIGHT; } if ((value = get_module_opt (cfg, "surbl", "url_expire")) != NULL) { - surbl_module_ctx->url_expire = rspamd_cl_obj_todouble (value); + surbl_module_ctx->url_expire = ucl_obj_todouble (value); } else { surbl_module_ctx->url_expire = DEFAULT_SURBL_URL_EXPIRE; } if ((value = get_module_opt (cfg, "surbl", "redirector_connect_timeout")) != NULL) { - surbl_module_ctx->connect_timeout = rspamd_cl_obj_todouble (value); + surbl_module_ctx->connect_timeout = ucl_obj_todouble (value); } else { surbl_module_ctx->connect_timeout = DEFAULT_REDIRECTOR_CONNECT_TIMEOUT; } if ((value = get_module_opt (cfg, "surbl", "redirector_read_timeout")) != NULL) { - surbl_module_ctx->read_timeout = rspamd_cl_obj_todouble (value); + surbl_module_ctx->read_timeout = ucl_obj_todouble (value); } else { surbl_module_ctx->read_timeout = DEFAULT_REDIRECTOR_READ_TIMEOUT; } if ((value = get_module_opt (cfg, "surbl", "redirector_hosts_map")) != NULL) { - add_map (cfg, rspamd_cl_obj_tostring (value), + add_map (cfg, ucl_obj_tostring (value), "SURBL redirectors list", read_redirectors_list, fin_redirectors_list, (void **)&surbl_module_ctx->redirector_hosts); } if ((value = get_module_opt (cfg, "surbl", "max_urls")) != NULL) { - surbl_module_ctx->max_urls = rspamd_cl_obj_toint (value); + surbl_module_ctx->max_urls = ucl_obj_toint (value); } else { surbl_module_ctx->max_urls = DEFAULT_SURBL_MAX_URLS; } if ((value = get_module_opt (cfg, "surbl", "exceptions")) != NULL) { - if (add_map (cfg, rspamd_cl_obj_tostring (value), + if (add_map (cfg, ucl_obj_tostring (value), "SURBL exceptions list", read_exceptions_list, fin_exceptions_list, (void **)&surbl_module_ctx->exceptions)) { surbl_module_ctx->tld2_file = memory_pool_strdup (surbl_module_ctx->surbl_pool, - rspamd_cl_obj_tostring (value) + sizeof ("file://") - 1); + ucl_obj_tostring (value) + sizeof ("file://") - 1); } } if ((value = get_module_opt (cfg, "surbl", "whitelist")) != NULL) { - if (add_map (cfg, rspamd_cl_obj_tostring (value), + if (add_map (cfg, ucl_obj_tostring (value), "SURBL whitelist", read_host_list, fin_host_list, (void **)&surbl_module_ctx->whitelist)) { surbl_module_ctx->whitelist_file = memory_pool_strdup (surbl_module_ctx->surbl_pool, - rspamd_cl_obj_tostring (value) + sizeof ("file://") - 1); + ucl_obj_tostring (value) + sizeof ("file://") - 1); } } value = get_module_opt (cfg, "surbl", "rule"); - if (value != NULL && value->type == RSPAMD_CL_OBJECT) { + if (value != NULL && value->type == UCL_OBJECT) { LL_FOREACH (value, cur_rule) { - cur = rspamd_cl_obj_get_key (cur_rule, "suffix"); + cur = ucl_obj_get_key (cur_rule, "suffix"); if (cur == NULL) { msg_err ("surbl rule must have explicit symbol definition"); continue; } new_suffix = memory_pool_alloc (surbl_module_ctx->surbl_pool, sizeof (struct suffix_item)); new_suffix->suffix = memory_pool_strdup (surbl_module_ctx->surbl_pool, - rspamd_cl_obj_tostring (cur)); + ucl_obj_tostring (cur)); new_suffix->options = 0; new_suffix->bits = NULL; - cur = rspamd_cl_obj_get_key (cur_rule, "symbol"); + cur = ucl_obj_get_key (cur_rule, "symbol"); if (cur == NULL) { msg_warn ("surbl rule for suffix %s lacks symbol, using %s as symbol", new_suffix->suffix, DEFAULT_SURBL_SYMBOL); new_suffix->suffix = memory_pool_strdup (surbl_module_ctx->surbl_pool, DEFAULT_SURBL_SYMBOL); } - cur = rspamd_cl_obj_get_key (cur_rule, "options"); - if (cur != NULL && cur->type == RSPAMD_CL_STRING) { - if (strstr (rspamd_cl_obj_tostring (cur), "noip") != NULL) { + cur = ucl_obj_get_key (cur_rule, "options"); + if (cur != NULL && cur->type == UCL_STRING) { + if (strstr (ucl_obj_tostring (cur), "noip") != NULL) { new_suffix->options |= SURBL_OPTION_NOIP; } } - cur = rspamd_cl_obj_get_key (cur_rule, "bits"); - if (cur != NULL && cur->type == RSPAMD_CL_OBJECT) { + cur = ucl_obj_get_key (cur_rule, "bits"); + if (cur != NULL && cur->type == UCL_OBJECT) { HASH_ITER (hh, cur->value.ov, cur_bit, tmp) { - if (cur_bit->key != NULL && cur_bit->type == RSPAMD_CL_INT) { - bit = rspamd_cl_obj_toint (cur_bit); + if (ucl_object_key (cur_bit) != NULL && cur_bit->type == UCL_INT) { + bit = ucl_obj_toint (cur_bit); new_bit = memory_pool_alloc (surbl_module_ctx->surbl_pool, sizeof (struct surbl_bit_item)); new_bit->bit = bit; - new_bit->symbol = memory_pool_strdup (surbl_module_ctx->surbl_pool, cur->key); + new_bit->symbol = memory_pool_strdup (surbl_module_ctx->surbl_pool, ucl_object_key (cur_bit)); msg_debug ("add new bit suffix: %d with symbol: %s", (gint)new_bit->bit, new_bit->symbol); new_suffix->bits = g_list_prepend (new_suffix->bits, new_bit); } diff --git a/src/rcl/CMakeLists.txt b/src/rcl/CMakeLists.txt deleted file mode 100644 index 134db7269..000000000 --- a/src/rcl/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Lua support makefile -SET(RCLSRC rcl_util.c - rcl_parser.c - rcl_emitter.c) - -ADD_LIBRARY(rspamd-rcl ${LINK_TYPE} ${RCLSRC}) -SET_TARGET_PROPERTIES(rspamd-rcl PROPERTIES VERSION ${RSPAMD_VERSION}) -SET_TARGET_PROPERTIES(rspamd-rcl PROPERTIES COMPILE_FLAGS "-DRSPAMD_LIB") -IF(HAVE_FETCH_H) - TARGET_LINK_LIBRARIES(rspamd-rcl fetch) -ELSE(HAVE_FETCH_H) - IF(CURL_FOUND) - TARGET_LINK_LIBRARIES(rspamd-rcl ${CURL_LIBRARIES}) - ENDIF(CURL_FOUND) -ENDIF(HAVE_FETCH_H) -IF(NO_SHARED MATCHES "OFF") - INSTALL(TARGETS rspamd-rcl - LIBRARY DESTINATION ${LIBDIR} - PUBLIC_HEADER DESTINATION ${INCLUDEDIR}) -ENDIF(NO_SHARED MATCHES "OFF")
\ No newline at end of file diff --git a/src/rcl/rcl.h b/src/rcl/rcl.h deleted file mode 100644 index 48f055414..000000000 --- a/src/rcl/rcl.h +++ /dev/null @@ -1,391 +0,0 @@ -/* 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 "config.h" -#include "uthash.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 - */ - -enum rspamd_cl_error { - RSPAMD_CL_EOK = 0, - RSPAMD_CL_ESYNTAX, - RSPAMD_CL_EIO, - RSPAMD_CL_ESTATE, - RSPAMD_CL_ENESTED, - RSPAMD_CL_EMACRO, - RSPAMD_CL_ERECURSION, - RSPAMD_CL_EINTERNAL, - RSPAMD_CL_ESSL -}; - -enum rspamd_cl_type { - RSPAMD_CL_OBJECT = 0, - RSPAMD_CL_ARRAY, - RSPAMD_CL_INT, - RSPAMD_CL_FLOAT, - RSPAMD_CL_STRING, - RSPAMD_CL_BOOLEAN, - RSPAMD_CL_TIME, - RSPAMD_CL_USERDATA -}; - -enum rspamd_cl_emitter { - RSPAMD_CL_EMIT_JSON = 0, - RSPAMD_CL_EMIT_JSON_COMPACT, - RSPAMD_CL_EMIT_CONFIG -}; - -enum rspamd_cl_flags { - RSPAMD_CL_FLAG_KEY_LOWERCASE = 0x1 -}; - -typedef struct rspamd_cl_object_s { - gchar *key; /**< the key of an object */ - union { - gint64 iv; /**< int value of an object */ - gchar *sv; /**< string value of an object */ - gdouble dv; /**< double value of an object */ - struct rspamd_cl_object_s *ov; /**< array or hash */ - gpointer ud; /**< opaque user data */ - } value; - enum rspamd_cl_type type; /**< real type */ - gint ref; /**< reference count */ - struct rspamd_cl_object_s *next; /**< array handle */ - UT_hash_handle hh; /**< hash handle */ -} rspamd_cl_object_t; - - -/** - * Creates a new object - * @return new object - */ -static inline rspamd_cl_object_t * -rspamd_cl_object_new (void) -{ - rspamd_cl_object_t *new; - new = g_slice_alloc0 (sizeof (rspamd_cl_object_t)); - if (new != NULL) { - new->ref = 1; - } - return new; -} - - -/** - * Converts an object to double value - * @param obj CL object - * @param target target double variable - * @return TRUE if conversion was successful - */ -static inline gboolean -rspamd_cl_obj_todouble_safe (rspamd_cl_object_t *obj, gdouble *target) -{ - if (obj == NULL) { - return FALSE; - } - switch (obj->type) { - case RSPAMD_CL_INT: - *target = obj->value.iv; /* Probaly could cause overflow */ - break; - case RSPAMD_CL_FLOAT: - case RSPAMD_CL_TIME: - *target = obj->value.dv; - break; - default: - return FALSE; - } - - return TRUE; -} - -/** - * Unsafe version of \ref rspamd_cl_obj_todouble_safe - * @param obj CL object - * @return double value - */ -static inline gdouble -rspamd_cl_obj_todouble (rspamd_cl_object_t *obj) -{ - gdouble result = 0.; - - rspamd_cl_obj_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 gboolean -rspamd_cl_obj_toint_safe (rspamd_cl_object_t *obj, gint64 *target) -{ - if (obj == NULL) { - return FALSE; - } - switch (obj->type) { - case RSPAMD_CL_INT: - *target = obj->value.iv; - break; - case RSPAMD_CL_FLOAT: - case RSPAMD_CL_TIME: - *target = obj->value.dv; /* Loosing of decimal points */ - break; - default: - return FALSE; - } - - return TRUE; -} - -/** - * Unsafe version of \ref rspamd_cl_obj_toint_safe - * @param obj CL object - * @return int value - */ -static inline gint64 -rspamd_cl_obj_toint (rspamd_cl_object_t *obj) -{ - gint64 result = 0; - - rspamd_cl_obj_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 gboolean -rspamd_cl_obj_toboolean_safe (rspamd_cl_object_t *obj, gboolean *target) -{ - if (obj == NULL) { - return FALSE; - } - switch (obj->type) { - case RSPAMD_CL_BOOLEAN: - *target = (obj->value.iv == TRUE); - break; - default: - return FALSE; - } - - return TRUE; -} - -/** - * Unsafe version of \ref rspamd_cl_obj_toboolean_safe - * @param obj CL object - * @return boolean value - */ -static inline gboolean -rspamd_cl_obj_toboolean (rspamd_cl_object_t *obj) -{ - gboolean result = FALSE; - - rspamd_cl_obj_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 gboolean -rspamd_cl_obj_tostring_safe (rspamd_cl_object_t *obj, const gchar **target) -{ - if (obj == NULL) { - return FALSE; - } - switch (obj->type) { - case RSPAMD_CL_STRING: - *target = obj->value.sv; - break; - default: - return FALSE; - } - - return TRUE; -} - -/** - * Unsafe version of \ref rspamd_cl_obj_tostring_safe - * @param obj CL object - * @return string value - */ -static inline const gchar * -rspamd_cl_obj_tostring (rspamd_cl_object_t *obj) -{ - const gchar *result = NULL; - - rspamd_cl_obj_tostring_safe (obj, &result); - return result; -} - -/** - * Return object identified by a key in the specified object - * @param obj object to get a key from (must be of type RSPAMD_CL_OBJECT) - * @param key key to search - * @return object matched the specified key or NULL if key is not found - */ -static inline rspamd_cl_object_t * -rspamd_cl_obj_get_key (rspamd_cl_object_t *obj, const gchar *key) -{ - gsize keylen; - rspamd_cl_object_t *ret; - - if (obj == NULL || obj->type != RSPAMD_CL_OBJECT || key == NULL) { - return NULL; - } - - keylen = strlen (key); - HASH_FIND(hh, obj->value.ov, key, keylen, ret); - - return ret; -} - -/** - * 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 gboolean (*rspamd_cl_macro_handler) (const guchar *data, gsize len, gpointer ud, GError **err); - -/* Opaque parser */ -struct rspamd_cl_parser; - -/** - * Creates new parser object - * @param pool pool to allocate memory from - * @return new parser object - */ -struct rspamd_cl_parser* rspamd_cl_parser_new (gint 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 rspamd_cl_parser_register_macro (struct rspamd_cl_parser *parser, const gchar *macro, - rspamd_cl_macro_handler handler, gpointer 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 - */ -gboolean rspamd_cl_parser_add_chunk (struct rspamd_cl_parser *parser, const guchar *data, - gsize len, GError **err); - -/** - * 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 - */ -gboolean rspamd_cl_parser_add_file (struct rspamd_cl_parser *parser, const gchar *filename, - GError **err); - -/** - * 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 - */ -rspamd_cl_object_t* rspamd_cl_parser_get_object (struct rspamd_cl_parser *parser, GError **err); - -/** - * Free cl parser object - * @param parser parser object - */ -void rspamd_cl_parser_free (struct rspamd_cl_parser *parser); - -/** - * Free cl object - * @param obj cl object to free - */ -void rspamd_cl_obj_free (rspamd_cl_object_t *obj); - -/** - * Icrease reference count for an object - * @param obj object to ref - */ -static inline rspamd_cl_object_t * -rspamd_cl_obj_ref (rspamd_cl_object_t *obj) { - obj->ref ++; - return obj; -} - -/** - * Decrease reference count for an object - * @param obj object to unref - */ -static inline void -rspamd_cl_obj_unref (rspamd_cl_object_t *obj) { - if (--obj->ref <= 0) { - rspamd_cl_obj_free (obj); - } -} - -/** - * Emit object to a string - * @param obj object - * @param emit_type if type is RSPAMD_CL_EMIT_JSON then emit json, if type is - * RSPAMD_CL_EMIT_CONFIG then emit config like object - * @return dump of an object (must be freed after using) or NULL in case of error - */ -guchar *rspamd_cl_object_emit (rspamd_cl_object_t *obj, enum rspamd_cl_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 - */ -gboolean rspamd_cl_pubkey_add (struct rspamd_cl_parser *parser, const guchar *key, gsize len, GError **err); - -#endif /* RCL_H_ */ diff --git a/src/rcl/rcl_chartable.h b/src/rcl/rcl_chartable.h deleted file mode 100644 index 2c02c9546..000000000 --- a/src/rcl/rcl_chartable.h +++ /dev/null @@ -1,256 +0,0 @@ -/* 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_CHARTABLE_H_ -#define RCL_CHARTABLE_H_ - -#include "rcl_internal.h" - -static const unsigned char rcl_chartable[255] = { -RCL_CHARACTER_VALUE_END, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_WHITESPACE|RCL_CHARACTER_WHITESPACE_UNSAFE, -RCL_CHARACTER_WHITESPACE_UNSAFE|RCL_CHARACTER_VALUE_END, -RCL_CHARACTER_WHITESPACE_UNSAFE, RCL_CHARACTER_WHITESPACE_UNSAFE, -RCL_CHARACTER_WHITESPACE_UNSAFE|RCL_CHARACTER_VALUE_END, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_DENIED, RCL_CHARACTER_DENIED, -RCL_CHARACTER_WHITESPACE|RCL_CHARACTER_WHITESPACE_UNSAFE|RCL_CHARACTER_VALUE_STR /* */, -RCL_CHARACTER_VALUE_STR /* ! */, RCL_CHARACTER_VALUE_STR /* " */, -RCL_CHARACTER_VALUE_END /* # */, RCL_CHARACTER_VALUE_STR /* $ */, -RCL_CHARACTER_VALUE_STR /* % */, RCL_CHARACTER_VALUE_STR /* & */, -RCL_CHARACTER_VALUE_STR /* ' */, RCL_CHARACTER_VALUE_STR /* ( */, -RCL_CHARACTER_VALUE_STR /* ) */, RCL_CHARACTER_VALUE_STR /* * */, -RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* + */, -RCL_CHARACTER_VALUE_END /* , */, -RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* - */, -RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* . */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR /* / */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 0 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 1 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 2 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 3 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 4 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 5 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 6 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 7 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 8 */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT_START|RCL_CHARACTER_VALUE_DIGIT /* 9 */, -RCL_CHARACTER_VALUE_STR /* : */, RCL_CHARACTER_VALUE_END /* ; */, -RCL_CHARACTER_VALUE_STR /* < */, RCL_CHARACTER_VALUE_STR /* = */, -RCL_CHARACTER_VALUE_STR /* > */, RCL_CHARACTER_VALUE_STR /* ? */, -RCL_CHARACTER_VALUE_STR /* @ */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* A */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* B */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* C */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* D */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* E */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* F */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* G */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* H */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* I */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* J */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* K */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* L */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* M */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* N */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* O */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* P */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* Q */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* R */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* S */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* T */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* U */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* V */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* W */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* X */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* Y */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* Z */, -RCL_CHARACTER_VALUE_STR /* [ */, RCL_CHARACTER_VALUE_STR /* \ */, -RCL_CHARACTER_VALUE_END /* ] */, RCL_CHARACTER_VALUE_STR /* ^ */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR /* _ */, -RCL_CHARACTER_VALUE_STR /* ` */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* a */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* b */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* c */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* d */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* e */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* f */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* g */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* h */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* i */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* j */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* k */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* l */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* m */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* n */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* o */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* p */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* q */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* r */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* s */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* t */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* u */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* v */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* w */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* x */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* y */, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR|RCL_CHARACTER_VALUE_DIGIT /* z */, -RCL_CHARACTER_VALUE_STR /* { */, RCL_CHARACTER_VALUE_STR /* | */, -RCL_CHARACTER_VALUE_END /* } */, RCL_CHARACTER_VALUE_STR /* ~ */, -RCL_CHARACTER_DENIED, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR, -RCL_CHARACTER_KEY_START|RCL_CHARACTER_KEY|RCL_CHARACTER_VALUE_STR -}; - -#endif /* RCL_CHARTABLE_H_ */ diff --git a/src/rcl/rcl_emitter.c b/src/rcl/rcl_emitter.c deleted file mode 100644 index bf394ee1d..000000000 --- a/src/rcl/rcl_emitter.c +++ /dev/null @@ -1,451 +0,0 @@ -/* 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 "config.h" -#include "rcl.h" -#include "rcl_internal.h" - -/** - * @file rcl_emitter.c - * Serialise RCL object to the RCL format - */ - - -static void rspamd_cl_elt_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, - gboolean start_tabs, gboolean compact); -static void rspamd_cl_obj_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, - gboolean start_tabs, gboolean compact); -static void rspamd_cl_elt_write_rcl (rspamd_cl_object_t *obj, GString *buf, guint tabs, - gboolean start_tabs, gboolean is_top, gboolean expand_array); - -/** - * Add tabulation to the output buffer - * @param buf target buffer - * @param tabs number of tabs to add - */ -static inline void -rspamd_cl_add_tabs (GString *buf, guint tabs, gboolean compact) -{ - while (!compact && tabs--) { - g_string_append_len (buf, " ", 4); - } -} - -/** - * Serialise string - * @param str string to emit - * @param buf target buffer - */ -static void -rspamd_cl_elt_string_write_json (const gchar *str, GString *buf) -{ - const gchar *p = str; - - g_string_append_c (buf, '"'); - while (*p != '\0') { - switch (*p) { - case '\n': - g_string_append_len (buf, "\\n", 2); - break; - case '\r': - g_string_append_len (buf, "\\r", 2); - break; - case '\b': - g_string_append_len (buf, "\\b", 2); - break; - case '\t': - g_string_append_len (buf, "\\t", 2); - break; - case '\f': - g_string_append_len (buf, "\\f", 2); - break; - case '\\': - g_string_append_len (buf, "\\\\", 2); - break; - case '"': - g_string_append_len (buf, "\\\"", 2); - break; - default: - g_string_append_c (buf, *p); - break; - } - p ++; - } - g_string_append_c (buf, '"'); -} - -/** - * Write a single object to the buffer - * @param obj object to write - * @param buf target buffer - */ -static void -rspamd_cl_elt_obj_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean compact) -{ - rspamd_cl_object_t *cur, *tmp; - - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - if (compact) { - g_string_append_c (buf, '{'); - } - else { - g_string_append_len (buf, "{\n", 2); - } - HASH_ITER (hh, obj, cur, tmp) { - rspamd_cl_add_tabs (buf, tabs + 1, compact); - rspamd_cl_elt_string_write_json (cur->key, buf); - if (compact) { - g_string_append_c (buf, ':'); - } - else { - g_string_append_len (buf, ": ", 2); - } - rspamd_cl_obj_write_json (cur, buf, tabs + 1, FALSE, compact); - if (cur->hh.next != NULL) { - if (compact) { - g_string_append_c (buf, ','); - } - else { - g_string_append_len (buf, ",\n", 2); - } - } - else if (!compact) { - g_string_append_c (buf, '\n'); - } - } - rspamd_cl_add_tabs (buf, tabs, compact); - g_string_append_c (buf, '}'); -} - -/** - * Write a single array to the buffer - * @param obj array to write - * @param buf target buffer - */ -static void -rspamd_cl_elt_array_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean compact) -{ - rspamd_cl_object_t *cur = obj; - - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - if (compact) { - g_string_append_c (buf, '['); - } - else { - g_string_append_len (buf, "[\n", 2); - } - while (cur) { - rspamd_cl_elt_write_json (cur, buf, tabs + 1, TRUE, compact); - if (cur->next != NULL) { - if (compact) { - g_string_append_c (buf, ','); - } - else { - g_string_append_len (buf, ",\n", 2); - } - } - else if (!compact) { - g_string_append_c (buf, '\n'); - } - cur = cur->next; - } - rspamd_cl_add_tabs (buf, tabs, compact); - g_string_append_c (buf, ']'); -} - -/** - * Emit a single element - * @param obj object - * @param buf buffer - */ -static void -rspamd_cl_elt_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean compact) -{ - switch (obj->type) { - case RSPAMD_CL_INT: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - g_string_append_printf (buf, "%ld", (long int)rspamd_cl_obj_toint (obj)); - break; - case RSPAMD_CL_FLOAT: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - g_string_append_printf (buf, "%lf", rspamd_cl_obj_todouble (obj)); - break; - case RSPAMD_CL_TIME: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - g_string_append_printf (buf, "%lf", rspamd_cl_obj_todouble (obj)); - break; - case RSPAMD_CL_BOOLEAN: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - g_string_append_printf (buf, "%s", rspamd_cl_obj_toboolean (obj) ? "true" : "false"); - break; - case RSPAMD_CL_STRING: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - rspamd_cl_elt_string_write_json (rspamd_cl_obj_tostring (obj), buf); - break; - case RSPAMD_CL_OBJECT: - rspamd_cl_elt_obj_write_json (obj->value.ov, buf, tabs, start_tabs, compact); - break; - case RSPAMD_CL_ARRAY: - rspamd_cl_elt_array_write_json (obj->value.ov, buf, tabs, start_tabs, compact); - break; - case RSPAMD_CL_USERDATA: - break; - } -} - -/** - * Write a single object to the buffer - * @param obj object - * @param buf target buffer - */ -static void -rspamd_cl_obj_write_json (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean compact) -{ - rspamd_cl_object_t *cur; - gboolean is_array = (obj->next != NULL); - - if (is_array) { - /* This is an array actually */ - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, compact); - } - - if (compact) { - g_string_append_c (buf, '['); - } - else { - g_string_append_len (buf, "[\n", 2); - } - cur = obj; - while (cur != NULL) { - rspamd_cl_elt_write_json (cur, buf, tabs + 1, TRUE, compact); - if (cur->next) { - g_string_append_c (buf, ','); - } - if (!compact) { - g_string_append_c (buf, '\n'); - } - cur = cur->next; - } - rspamd_cl_add_tabs (buf, tabs, compact); - g_string_append_c (buf, ']'); - } - else { - rspamd_cl_elt_write_json (obj, buf, tabs, start_tabs, compact); - } - -} - -/** - * Emit an object to json - * @param obj object - * @return json output (should be freed after using) - */ -static guchar * -rspamd_cl_object_emit_json (rspamd_cl_object_t *obj, gboolean compact) -{ - GString *buf; - guchar *res; - - /* Allocate large enough buffer */ - buf = g_string_sized_new (BUFSIZ); - - rspamd_cl_obj_write_json (obj, buf, 0, FALSE, compact); - - res = buf->str; - g_string_free (buf, FALSE); - - return res; -} - -/** - * Write a single object to the buffer - * @param obj object to write - * @param buf target buffer - */ -static void -rspamd_cl_elt_obj_write_rcl (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean is_top) -{ - rspamd_cl_object_t *cur, *tmp; - - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, is_top); - } - if (!is_top) { - g_string_append_len (buf, "{\n", 2); - } - - HASH_ITER (hh, obj, cur, tmp) { - rspamd_cl_add_tabs (buf, tabs + 1, is_top); - g_string_append (buf, cur->key); - if (cur->type != RSPAMD_CL_OBJECT && cur->type != RSPAMD_CL_ARRAY) { - g_string_append_len (buf, " = ", 3); - } - else { - g_string_append_c (buf, ' '); - } - rspamd_cl_elt_write_rcl (cur, buf, is_top ? tabs : tabs + 1, FALSE, FALSE, TRUE); - if (cur->type != RSPAMD_CL_OBJECT && cur->type != RSPAMD_CL_ARRAY) { - g_string_append_len (buf, ";\n", 2); - } - else { - g_string_append_c (buf, '\n'); - } - } - rspamd_cl_add_tabs (buf, tabs, is_top); - if (!is_top) { - g_string_append_c (buf, '}'); - } -} - -/** - * Write a single array to the buffer - * @param obj array to write - * @param buf target buffer - */ -static void -rspamd_cl_elt_array_write_rcl (rspamd_cl_object_t *obj, GString *buf, guint tabs, gboolean start_tabs, gboolean is_top) -{ - rspamd_cl_object_t *cur = obj; - - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - - g_string_append_len (buf, "[\n", 2); - while (cur) { - rspamd_cl_elt_write_rcl (cur, buf, tabs + 1, TRUE, FALSE, FALSE); - g_string_append_len (buf, ",\n", 2); - cur = cur->next; - } - rspamd_cl_add_tabs (buf, tabs, FALSE); - g_string_append_c (buf, ']'); -} - -/** - * Emit a single element - * @param obj object - * @param buf buffer - */ -static void -rspamd_cl_elt_write_rcl (rspamd_cl_object_t *obj, GString *buf, guint tabs, - gboolean start_tabs, gboolean is_top, gboolean expand_array) -{ - if (expand_array && obj->next) { - rspamd_cl_elt_array_write_rcl (obj, buf, tabs, start_tabs, is_top); - } - else { - switch (obj->type) { - case RSPAMD_CL_INT: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - g_string_append_printf (buf, "%ld", (long int)rspamd_cl_obj_toint (obj)); - break; - case RSPAMD_CL_FLOAT: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - g_string_append_printf (buf, "%.4lf", rspamd_cl_obj_todouble (obj)); - break; - case RSPAMD_CL_TIME: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - g_string_append_printf (buf, "%.4lf", rspamd_cl_obj_todouble (obj)); - break; - case RSPAMD_CL_BOOLEAN: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - g_string_append_printf (buf, "%s", rspamd_cl_obj_toboolean (obj) ? "true" : "false"); - break; - case RSPAMD_CL_STRING: - if (start_tabs) { - rspamd_cl_add_tabs (buf, tabs, FALSE); - } - rspamd_cl_elt_string_write_json (rspamd_cl_obj_tostring (obj), buf); - break; - case RSPAMD_CL_OBJECT: - rspamd_cl_elt_obj_write_rcl (obj->value.ov, buf, tabs, start_tabs, is_top); - break; - case RSPAMD_CL_ARRAY: - rspamd_cl_elt_array_write_rcl (obj->value.ov, buf, tabs, start_tabs, is_top); - break; - case RSPAMD_CL_USERDATA: - break; - } - } -} - -/** - * Emit an object to rcl - * @param obj object - * @return rcl output (should be freed after using) - */ -static guchar * -rspamd_cl_object_emit_rcl (rspamd_cl_object_t *obj) -{ - GString *buf; - guchar *res; - - /* Allocate large enough buffer */ - buf = g_string_sized_new (BUFSIZ); - - rspamd_cl_elt_write_rcl (obj, buf, 0, FALSE, TRUE, TRUE); - - res = buf->str; - g_string_free (buf, FALSE); - - return res; -} - -guchar * -rspamd_cl_object_emit (rspamd_cl_object_t *obj, enum rspamd_cl_emitter emit_type) -{ - if (emit_type == RSPAMD_CL_EMIT_JSON) { - return rspamd_cl_object_emit_json (obj, FALSE); - } - else if (emit_type == RSPAMD_CL_EMIT_JSON_COMPACT) { - return rspamd_cl_object_emit_json (obj, TRUE); - } - else { - return rspamd_cl_object_emit_rcl (obj); - } - - return NULL; -} diff --git a/src/rcl/rcl_internal.h b/src/rcl/rcl_internal.h deleted file mode 100644 index c2928da9d..000000000 --- a/src/rcl/rcl_internal.h +++ /dev/null @@ -1,146 +0,0 @@ -/* 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_INTERNAL_H_ -#define RCL_INTERNAL_H_ - -#include "rcl.h" -#include "utlist.h" -#ifdef HAVE_OPENSSL -#include <openssl/evp.h> -#endif - -/** - * @file rcl_internal.h - * Internal structures and functions of RCL library - */ - -#define RCL_ERROR rcl_error_quark () -static inline GQuark -rcl_error_quark (void) -{ - return g_quark_from_static_string ("rcl-error-quark"); -} - -#define RCL_MAX_RECURSION 16 - -enum rcl_character_type { - RCL_CHARACTER_DENIED = 0, - RCL_CHARACTER_KEY = 1, - RCL_CHARACTER_KEY_START = 1 << 1, - RCL_CHARACTER_WHITESPACE = 1 << 2, - RCL_CHARACTER_WHITESPACE_UNSAFE = 1 << 3, - RCL_CHARACTER_VALUE_END = 1 << 4, - RCL_CHARACTER_VALUE_STR = 1 << 5, - RCL_CHARACTER_VALUE_DIGIT = 1 << 6, - RCL_CHARACTER_VALUE_DIGIT_START = 1 << 7 -}; - -enum rspamd_cl_parser_state { - RSPAMD_RCL_STATE_INIT = 0, - RSPAMD_RCL_STATE_OBJECT, - RSPAMD_RCL_STATE_ARRAY, - RSPAMD_RCL_STATE_KEY, - RSPAMD_RCL_STATE_VALUE, - RSPAMD_RCL_STATE_AFTER_VALUE, - RSPAMD_RCL_STATE_ARRAY_VALUE, - RSPAMD_RCL_STATE_SCOMMENT, - RSPAMD_RCL_STATE_MCOMMENT, - RSPAMD_RCL_STATE_MACRO_NAME, - RSPAMD_RCL_STATE_MACRO, - RSPAMD_RCL_STATE_ERROR -}; - -struct rspamd_cl_macro { - gchar *name; - rspamd_cl_macro_handler handler; - gpointer ud; - UT_hash_handle hh; -}; - -struct rspamd_cl_stack { - rspamd_cl_object_t *obj; - struct rspamd_cl_stack *next; -}; - -struct rspamd_cl_chunk { - const guchar *begin; - const guchar *end; - const guchar *pos; - gsize remain; - guint line; - guint column; - struct rspamd_cl_chunk *next; -}; - -#ifdef HAVE_OPENSSL -struct rspamd_cl_pubkey { - EVP_PKEY *key; - struct rspamd_cl_pubkey *next; -}; -#else -struct rspamd_cl_pubkey { - struct rspamd_cl_pubkey *next; -}; -#endif - -struct rspamd_cl_parser { - enum rspamd_cl_parser_state state; - enum rspamd_cl_parser_state prev_state; - guint recursion; - gint flags; - rspamd_cl_object_t *top_obj; - rspamd_cl_object_t *cur_obj; - struct rspamd_cl_macro *macroes; - struct rspamd_cl_stack *stack; - struct rspamd_cl_chunk *chunks; - struct rspamd_cl_pubkey *keys; -}; - -/** - * Unescape json string inplace - * @param str - */ -void rspamd_cl_unescape_json_string (gchar *str); - -/** - * Handle include macro - * @param data include data - * @param len length of data - * @param ud user data - * @param err error ptr - * @return - */ -gboolean rspamd_cl_include_handler (const guchar *data, gsize len, gpointer ud, GError **err); - -/** - * Handle includes macro - * @param data include data - * @param len length of data - * @param ud user data - * @param err error ptr - * @return - */ -gboolean rspamd_cl_includes_handler (const guchar *data, gsize len, gpointer ud, GError **err); - -#endif /* RCL_INTERNAL_H_ */ diff --git a/src/rcl/rcl_parser.c b/src/rcl/rcl_parser.c deleted file mode 100644 index b601df310..000000000 --- a/src/rcl/rcl_parser.c +++ /dev/null @@ -1,1380 +0,0 @@ -/* 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 "config.h" -#include "rcl.h" -#include "rcl_internal.h" -#include "rcl_chartable.h" -#include "util.h" - -/** - * @file rcl_parser.c - * The implementation of rcl parser - */ - -struct rspamd_cl_parser_saved_state { - guint line; - guint column; - gsize remain; - const guchar *pos; -}; - -/** - * Move up to len characters - * @param parser - * @param begin - * @param len - * @return new position in chunk - */ -static inline void -rspamd_cl_chunk_skipc (struct rspamd_cl_chunk *chunk, guchar c) -{ - if (c == '\n') { - chunk->line ++; - chunk->column = 0; - } - else { - chunk->column ++; - } - - chunk->pos ++; - chunk->remain --; -} - -/** - * Save parser state - * @param chunk - * @param s - */ -static inline void -rspamd_cl_chunk_save_state (struct rspamd_cl_chunk *chunk, struct rspamd_cl_parser_saved_state *s) -{ - s->column = chunk->column; - s->pos = chunk->pos; - s->line = chunk->line; - s->remain = chunk->remain; -} - -/** - * Restore parser state - * @param chunk - * @param s - */ -static inline void -rspamd_cl_chunk_restore_state (struct rspamd_cl_chunk *chunk, struct rspamd_cl_parser_saved_state *s) -{ - chunk->column = s->column; - chunk->pos = s->pos; - chunk->line = s->line; - chunk->remain = s->remain; -} - -static inline gboolean -rcl_test_character (guchar c, gint type_flags) -{ - return (rcl_chartable[c] & type_flags) != 0; -} - -static inline void -rspamd_cl_set_err (struct rspamd_cl_chunk *chunk, gint code, const char *str, GError **err) -{ - g_set_error (err, RCL_ERROR, code, "error on line %d at column %d: '%s', character: '%c'", - chunk->line, chunk->column, str, *chunk->pos); -} - -static gboolean -rspamd_cl_skip_comments (struct rspamd_cl_parser *parser, GError **err) -{ - struct rspamd_cl_chunk *chunk = parser->chunks; - const guchar *p; - gint comments_nested = 0; - - p = chunk->pos; - -start: - if (*p == '#') { - if (parser->state != RSPAMD_RCL_STATE_SCOMMENT && - parser->state != RSPAMD_RCL_STATE_MCOMMENT) { - while (p < chunk->end) { - if (*p == '\n') { - rspamd_cl_chunk_skipc (chunk, *++p); - /* Check comments again */ - goto start; - } - rspamd_cl_chunk_skipc (chunk, *++p); - } - } - } - else if (*p == '/' && chunk->remain >= 2) { - if (p[1] == '*') { - rspamd_cl_chunk_skipc (chunk, *++p); - comments_nested ++; - rspamd_cl_chunk_skipc (chunk, *++p); - - while (p < chunk->end) { - if (*p == '*') { - rspamd_cl_chunk_skipc (chunk, *++p); - if (*p == '/') { - comments_nested --; - if (comments_nested == 0) { - rspamd_cl_chunk_skipc (chunk, *++p); - goto start; - } - } - rspamd_cl_chunk_skipc (chunk, *++p); - } - else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') { - comments_nested ++; - rspamd_cl_chunk_skipc (chunk, *++p); - rspamd_cl_chunk_skipc (chunk, *++p); - continue; - } - rspamd_cl_chunk_skipc (chunk, *++p); - } - if (comments_nested != 0) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ENESTED, "comments nesting is invalid", 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 gulong -rspamd_cl_lex_num_multiplier (const guchar c, gboolean is_bytes) { - const struct { - char c; - glong mult_normal; - glong mult_bytes; - } multipliers[] = { - {'m', 1000 * 1000, 1024 * 1024}, - {'k', 1000, 1024}, - {'g', 1000 * 1000 * 1000, 1024 * 1024 * 1024} - }; - gint i; - - for (i = 0; i < 3; i ++) { - if (g_ascii_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 gdouble -rspamd_cl_lex_time_multiplier (const guchar c) { - const struct { - char c; - gdouble mult; - } multipliers[] = { - {'m', 60}, - {'h', 60 * 60}, - {'d', 60 * 60 * 24}, - {'w', 60 * 60 * 24 * 7}, - {'y', 60 * 60 * 24 * 7 * 365} - }; - gint i; - - for (i = 0; i < 5; i ++) { - if (g_ascii_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 gboolean -rspamd_cl_lex_is_atom_end (const guchar c) -{ - return rcl_test_character (c, RCL_CHARACTER_VALUE_END); -} - -static inline gboolean -rspamd_cl_lex_is_comment (const guchar c1, const guchar c2) -{ - if (c1 == '/') { - if (c2 == '*') { - return TRUE; - } - } - else if (c1 == '#') { - return TRUE; - } - return FALSE; -} - -/** - * Parse possible number - * @param parser - * @param chunk - * @param err - * @return TRUE if a number has been parsed - */ -static gboolean -rspamd_cl_lex_number (struct rspamd_cl_parser *parser, - struct rspamd_cl_chunk *chunk, rspamd_cl_object_t *obj, GError **err) -{ - const guchar *p = chunk->pos, *c = chunk->pos; - gchar *endptr; - gboolean got_dot = FALSE, got_exp = FALSE, need_double = FALSE, is_date = FALSE; - gdouble dv; - gint64 lv; - struct rspamd_cl_parser_saved_state s; - - rspamd_cl_chunk_save_state (chunk, &s); - - if (*p == '-') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - while (p < chunk->end) { - if (g_ascii_isdigit (*p)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else { - if (p == c) { - /* Empty digits sequence, not a number */ - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - else if (*p == '.') { - if (got_dot) { - /* Double dots, not a number */ - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - else { - got_dot = TRUE; - need_double = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - } - else if (*p == 'e' || *p == 'E') { - if (got_exp) { - /* Double exp, not a number */ - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - else { - got_exp = TRUE; - need_double = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - if (p >= chunk->end) { - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - if (!g_ascii_isdigit (*p) && *p != '+' && *p != '-') { - /* Wrong exponent sign */ - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - else { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - } - } - else { - /* Got the end of the number, need to check */ - break; - } - } - } - - errno = 0; - if (need_double) { - dv = strtod (c, &endptr); - } - else { - lv = strtoimax (c, &endptr, 10); - } - if (errno == ERANGE) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "numeric value is out of range", err); - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - rspamd_cl_chunk_restore_state (chunk, &s); - return FALSE; - } - - /* Now check endptr */ - if (endptr == NULL || rspamd_cl_lex_is_atom_end (*endptr) || *endptr == '\0') { - chunk->pos = endptr; - goto set_obj; - } - - if ((guchar *)endptr < chunk->end) { - p = endptr; - chunk->pos = p; - switch (*p) { - case 'm': - case 'M': - case 'g': - case 'G': - case 'k': - case 'K': - if (chunk->end - p > 2) { - if (p[1] == 's' || p[1] == 'S') { - /* Milliseconds */ - if (!need_double) { - need_double = TRUE; - dv = lv; - } - is_date = TRUE; - if (p[0] == 'm' || p[0] == 'M') { - dv /= 1000.; - } - else { - dv *= rspamd_cl_lex_num_multiplier (*p, FALSE); - } - rspamd_cl_chunk_skipc (chunk, *p); - rspamd_cl_chunk_skipc (chunk, *p); - p += 2; - goto set_obj; - } - else if (p[1] == 'b' || p[1] == 'B') { - /* Megabytes */ - if (need_double) { - need_double = FALSE; - lv = dv; - } - lv *= rspamd_cl_lex_num_multiplier (*p, TRUE); - rspamd_cl_chunk_skipc (chunk, *p); - rspamd_cl_chunk_skipc (chunk, *p); - p += 2; - goto set_obj; - } - else if (rspamd_cl_lex_is_atom_end (p[1])) { - if (need_double) { - dv *= rspamd_cl_lex_num_multiplier (*p, FALSE); - } - else { - lv *= rspamd_cl_lex_num_multiplier (*p, FALSE); - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - goto set_obj; - } - else if (chunk->end - p >= 3) { - if (g_ascii_tolower (p[0]) == 'm' && - g_ascii_tolower (p[1]) == 'i' && - g_ascii_tolower (p[2]) == 'n') { - /* Minutes */ - if (!need_double) { - need_double = TRUE; - dv = lv; - } - is_date = TRUE; - dv *= 60.; - rspamd_cl_chunk_skipc (chunk, *p); - rspamd_cl_chunk_skipc (chunk, *p); - rspamd_cl_chunk_skipc (chunk, *p); - p += 3; - goto set_obj; - } - } - } - else { - if (need_double) { - dv *= rspamd_cl_lex_num_multiplier (*p, FALSE); - } - else { - lv *= rspamd_cl_lex_num_multiplier (*p, FALSE); - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - goto set_obj; - } - break; - case 'S': - case 's': - if (p == chunk->end - 1 || rspamd_cl_lex_is_atom_end (*++p)) { - if (!need_double) { - need_double = TRUE; - dv = lv; - } - rspamd_cl_chunk_skipc (chunk, *p); - is_date = TRUE; - goto set_obj; - } - break; - case 'h': - case 'H': - case 'd': - case 'D': - case 'w': - case 'W': - case 'Y': - case 'y': - if (p == chunk->end - 1 || rspamd_cl_lex_is_atom_end (p[1])) { - if (!need_double) { - need_double = TRUE; - dv = lv; - } - is_date = TRUE; - dv *= rspamd_cl_lex_time_multiplier (*p); - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - goto set_obj; - } - break; - } - } - - chunk->pos = c; - return FALSE; - -set_obj: - if (need_double || is_date) { - if (!is_date) { - obj->type = RSPAMD_CL_FLOAT; - } - else { - obj->type = RSPAMD_CL_TIME; - } - obj->value.dv = dv; - } - else { - obj->type = RSPAMD_CL_INT; - obj->value.iv = lv; - } - chunk->pos = p; - return TRUE; -} - -/** - * Parse quoted string with possible escapes - * @param parser - * @param chunk - * @param err - * @return TRUE if a string has been parsed - */ -static gboolean -rspamd_cl_lex_json_string (struct rspamd_cl_parser *parser, - struct rspamd_cl_chunk *chunk, gboolean *need_unescape, GError **err) -{ - const guchar *p = chunk->pos; - guchar c; - gint i; - - while (p < chunk->end) { - c = *p; - if (c < 0x1F) { - /* Unmasked control character */ - if (c == '\n') { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected newline", err); - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected control character", err); - } - return FALSE; - } - if (c == '\\') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - c = *p; - if (p >= chunk->end) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unfinished escape character", err); - return FALSE; - } - if (*p == 'u') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - for (i = 0; i < 4 && p < chunk->end; i ++) { - if (!g_ascii_isxdigit (*p)) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "invalid utf escape", err); - return FALSE; - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - if (p >= chunk->end) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unfinished escape character", err); - return FALSE; - } - } - else if (c == '"' || c == '\\' || c == '/' || c == 'b' || - c == 'f' || c == 'n' || c == 'r' || c == 't') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "invalid escape character", err); - return FALSE; - } - *need_unescape = TRUE; - continue; - } - else if (c == '"') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - return TRUE; - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - - return FALSE; -} - -/** - * Parse a key in an object - * @param parser - * @param chunk - * @param err - * @return TRUE if a key has been parsed - */ -static gboolean -rspamd_cl_parse_key (struct rspamd_cl_parser *parser, - struct rspamd_cl_chunk *chunk, GError **err) -{ - const guchar *p, *c = NULL, *end; - gboolean got_quote = FALSE, got_eq = FALSE, got_semicolon = FALSE, need_unescape = FALSE; - rspamd_cl_object_t *nobj, *tobj, *container; - - p = chunk->pos; - - if (*p == '.') { - /* It is macro actually */ - rspamd_cl_chunk_skipc (chunk, *p); - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_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 (rspamd_cl_lex_is_comment (p[0], p[1])) { - if (!rspamd_cl_skip_comments (parser, err)) { - return FALSE; - } - p = chunk->pos; - } - else if (rcl_test_character (*p, RCL_CHARACTER_KEY_START)) { - /* The first symbol */ - c = p; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == '"') { - /* JSON style key */ - c = p + 1; - got_quote = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else { - /* Invalid identifier */ - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "key must begin with a letter", err); - return FALSE; - } - } - else { - /* Parse the body of a key */ - if (!got_quote) { - if (rcl_test_character (*p, RCL_CHARACTER_KEY)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == ' ' || *p == '\t' || *p == ':' || *p == '=') { - end = p; - break; - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "invalid character in a key", err); - return FALSE; - } - } - else { - /* We need to parse json like quoted string */ - if (!rspamd_cl_lex_json_string (parser, chunk, &need_unescape, err)) { - return FALSE; - } - end = chunk->pos - 1; - p = chunk->pos; - break; - } - } - } - - if (p >= chunk->end) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unfinished key", err); - return FALSE; - } - - /* We are now at the end of the key, need to parse the rest */ - while (p < chunk->end) { - if (rcl_test_character (*p, RCL_CHARACTER_WHITESPACE)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == '=') { - if (!got_eq && !got_semicolon) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - got_eq = TRUE; - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected '=' character", err); - return FALSE; - } - } - else if (*p == ':') { - if (!got_eq && !got_semicolon) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - got_semicolon = TRUE; - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected ':' character", err); - return FALSE; - } - } - else if (rspamd_cl_lex_is_comment (p[0], p[1])) { - /* Check for comment */ - if (!rspamd_cl_skip_comments (parser, err)) { - return FALSE; - } - p = chunk->pos; - } - else { - /* Start value */ - break; - } - } - - if (p >= chunk->end) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unfinished key", err); - return FALSE; - } - - /* Create a new object */ - nobj = rspamd_cl_object_new (); - nobj->key = g_malloc (end - c + 1); - if (nobj->key == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_EINTERNAL, "cannot allocate memory for a key", err); - return FALSE; - } - if (parser->flags & RSPAMD_CL_FLAG_KEY_LOWERCASE) { - rspamd_strlcpy_tolower (nobj->key, c, end - c + 1); - } - else { - rspamd_strlcpy (nobj->key, c, end - c + 1); - } - - if (need_unescape) { - rspamd_cl_unescape_json_string (nobj->key); - } - - container = parser->stack->obj->value.ov; - HASH_FIND_STR (container, nobj->key, tobj); - if (tobj != NULL) { - /* Just insert a new object as the next element */ - HASH_DELETE (hh, container, tobj); - LL_PREPEND (tobj, nobj); - } - - HASH_ADD_KEYPTR (hh, container, nobj->key, strlen (nobj->key), nobj); - parser->stack->obj->value.ov = container; - - parser->cur_obj = nobj; - - return TRUE; -} - -/** - * Parse a cl string - * @param parser - * @param chunk - * @param err - * @return TRUE if a key has been parsed - */ -static gboolean -rspamd_cl_parse_string_value (struct rspamd_cl_parser *parser, - struct rspamd_cl_chunk *chunk, GError **err) -{ - const guchar *p; - - p = chunk->pos; - - while (p < chunk->end) { - if (rspamd_cl_lex_is_atom_end (*p) || rspamd_cl_lex_is_comment (p[0], p[1])) { - break; - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - - if (p >= chunk->end) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unfinished value", err); - return FALSE; - } - - return TRUE; -} - -/** - * 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 gboolean -rspamd_cl_maybe_parse_boolean (rspamd_cl_object_t *obj, const guchar *start, gsize len) -{ - const guchar *p = start; - gboolean ret = FALSE, val = FALSE; - - if (len == 5) { - if (g_ascii_tolower (p[0]) == 'f' && g_ascii_strncasecmp (p, "false", 5) == 0) { - ret = TRUE; - val = FALSE; - } - } - else if (len == 4) { - if (g_ascii_tolower (p[0]) == 't' && g_ascii_strncasecmp (p, "true", 4) == 0) { - ret = TRUE; - val = TRUE; - } - } - else if (len == 3) { - if (g_ascii_tolower (p[0]) == 'y' && g_ascii_strncasecmp (p, "yes", 3) == 0) { - ret = TRUE; - val = TRUE; - } - if (g_ascii_tolower (p[0]) == 'o' && g_ascii_strncasecmp (p, "off", 3) == 0) { - ret = TRUE; - val = FALSE; - } - } - else if (len == 2) { - if (g_ascii_tolower (p[0]) == 'n' && g_ascii_strncasecmp (p, "no", 2) == 0) { - ret = TRUE; - val = FALSE; - } - else if (g_ascii_tolower (p[0]) == 'o' && g_ascii_strncasecmp (p, "on", 2) == 0) { - ret = TRUE; - val = TRUE; - } - } - - if (ret) { - obj->type = RSPAMD_CL_BOOLEAN; - obj->value.iv = val; - } - - return ret; -} - -/** - * Handle value data - * @param parser - * @param chunk - * @param err - * @return - */ -static gboolean -rspamd_cl_parse_value (struct rspamd_cl_parser *parser, struct rspamd_cl_chunk *chunk, GError **err) -{ - const guchar *p, *c; - struct rspamd_cl_stack *st; - rspamd_cl_object_t *obj = NULL; - guint stripped_spaces; - gint str_len; - gboolean need_unescape = FALSE; - - p = chunk->pos; - - while (p < chunk->end) { - if (obj == NULL) { - if (parser->stack->obj->type == RSPAMD_CL_ARRAY) { - /* Object must be allocated */ - obj = rspamd_cl_object_new (); - parser->cur_obj = obj; - LL_PREPEND (parser->stack->obj->value.ov, parser->cur_obj); - } - else { - /* Object has been already allocated */ - obj = parser->cur_obj; - } - } - c = p; - switch (*p) { - case '"': - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - if (!rspamd_cl_lex_json_string (parser, chunk, &need_unescape, err)) { - return FALSE; - } - obj->value.sv = g_malloc (chunk->pos - c - 1); - if (obj->value.sv == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_EINTERNAL, "cannot allocate memory for a string", err); - return FALSE; - } - rspamd_strlcpy (obj->value.sv, c + 1, chunk->pos - c - 1); - if (need_unescape) { - rspamd_cl_unescape_json_string (obj->value.sv); - } - obj->type = RSPAMD_CL_STRING; - parser->state = RSPAMD_RCL_STATE_AFTER_VALUE; - p = chunk->pos; - return TRUE; - break; - case '{': - /* We have a new object */ - obj->type = RSPAMD_CL_OBJECT; - - parser->state = RSPAMD_RCL_STATE_KEY; - st = g_slice_alloc0 (sizeof (struct rspamd_cl_stack)); - st->obj = obj; - LL_PREPEND (parser->stack, st); - parser->cur_obj = obj; - - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - return TRUE; - break; - case '[': - /* We have a new array */ - obj = parser->cur_obj; - obj->type = RSPAMD_CL_ARRAY; - - parser->state = RSPAMD_RCL_STATE_VALUE; - st = g_slice_alloc0 (sizeof (struct rspamd_cl_stack)); - st->obj = obj; - LL_PREPEND (parser->stack, st); - parser->cur_obj = obj; - - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - return TRUE; - break; - default: - /* Skip any spaces and comments */ - if (rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE) || - rspamd_cl_lex_is_comment (p[0], p[1])) { - while (p < chunk->end && rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - if (!rspamd_cl_skip_comments (parser, err)) { - return FALSE; - } - p = chunk->pos; - continue; - } - /* Parse atom */ - if (rcl_test_character (*p, RCL_CHARACTER_VALUE_DIGIT_START)) { - if (!rspamd_cl_lex_number (parser, chunk, obj, err)) { - if (parser->state == RSPAMD_RCL_STATE_ERROR) { - return FALSE; - } - if (!rspamd_cl_parse_string_value (parser, chunk, err)) { - return FALSE; - } - if (!rspamd_cl_maybe_parse_boolean (obj, c, chunk->pos - c)) { - /* Cut trailing spaces */ - stripped_spaces = 0; - while (rcl_test_character (*(chunk->pos - 1 - stripped_spaces), - RCL_CHARACTER_WHITESPACE)) { - stripped_spaces ++; - } - str_len = chunk->pos - c + 1 - stripped_spaces; - if (str_len <= 0) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "string value must not be empty", err); - return FALSE; - } - obj->value.sv = g_malloc (str_len); - if (obj->value.sv == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_EINTERNAL, "cannot allocate memory for a string", err); - return FALSE; - } - rspamd_strlcpy (obj->value.sv, c, str_len); - obj->type = RSPAMD_CL_STRING; - } - parser->state = RSPAMD_RCL_STATE_AFTER_VALUE; - return TRUE; - } - else { - parser->state = RSPAMD_RCL_STATE_AFTER_VALUE; - return TRUE; - } - } - else { - if (!rspamd_cl_parse_string_value (parser, chunk, err)) { - return FALSE; - } - if (!rspamd_cl_maybe_parse_boolean (obj, c, chunk->pos - c)) { - /* Cut trailing spaces */ - stripped_spaces = 0; - while (rcl_test_character (*(chunk->pos - 1 - stripped_spaces), - RCL_CHARACTER_WHITESPACE)) { - stripped_spaces ++; - } - str_len = chunk->pos - c + 1 - stripped_spaces; - if (str_len <= 0) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "string value must not be empty", err); - return FALSE; - } - obj->value.sv = g_malloc (str_len); - if (obj->value.sv == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_EINTERNAL, "cannot allocate memory for a string", err); - return FALSE; - } - rspamd_strlcpy (obj->value.sv, c, str_len); - obj->type = RSPAMD_CL_STRING; - } - parser->state = RSPAMD_RCL_STATE_AFTER_VALUE; - return TRUE; - } - p = chunk->pos; - break; - } - } - - return TRUE; -} - -/** - * Handle after value data - * @param parser - * @param chunk - * @param err - * @return - */ -static gboolean -rspamd_cl_parse_after_value (struct rspamd_cl_parser *parser, struct rspamd_cl_chunk *chunk, GError **err) -{ - const guchar *p; - gboolean got_sep = FALSE; - struct rspamd_cl_stack *st; - - p = chunk->pos; - - while (p < chunk->end) { - if (rcl_test_character (*p, RCL_CHARACTER_WHITESPACE)) { - /* Skip whitespaces */ - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (rspamd_cl_lex_is_comment (p[0], p[1])) { - /* Skip comment */ - if (!rspamd_cl_skip_comments (parser, err)) { - return FALSE; - } - /* Treat comment as a separator */ - got_sep = TRUE; - p = chunk->pos; - } - else if (*p == ',') { - /* Got a separator */ - got_sep = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == ';') { - /* Got a separator */ - got_sep = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == '\n') { - got_sep = TRUE; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (*p == '}' || *p == ']') { - if (parser->stack == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected } detected", err); - return FALSE; - } - if ((*p == '}' && parser->stack->obj->type == RSPAMD_CL_OBJECT) || - (*p == ']' && parser->stack->obj->type == RSPAMD_CL_ARRAY)) { - /* Pop object from a stack */ - - st = parser->stack; - parser->stack = st->next; - g_slice_free1 (sizeof (struct rspamd_cl_stack), st); - } - else { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "unexpected terminating symbol detected", err); - return FALSE; - } - - if (parser->stack == NULL) { - /* Ignore everything after a top object */ - return TRUE; - } - else { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - got_sep = TRUE; - } - else { - /* Anything else */ - if (!got_sep) { - rspamd_cl_set_err (chunk, RSPAMD_CL_ESYNTAX, "delimiter is missing", err); - return FALSE; - } - return TRUE; - } - } - - return TRUE; -} - -/** - * Handle macro data - * @param parser - * @param chunk - * @param err - * @return - */ -static gboolean -rspamd_cl_parse_macro_value (struct rspamd_cl_parser *parser, - struct rspamd_cl_chunk *chunk, struct rspamd_cl_macro *macro, - guchar const **macro_start, gsize *macro_len, GError **err) -{ - const guchar *p, *c; - gboolean need_unescape = FALSE; - - p = chunk->pos; - - switch (*p) { - case '"': - /* We have macro value encoded in quotes */ - c = p; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - if (!rspamd_cl_lex_json_string (parser, chunk, &need_unescape, err)) { - return FALSE; - } - - *macro_start = c + 1; - *macro_len = chunk->pos - c - 2; - p = chunk->pos; - break; - case '{': - /* We got a multiline macro body */ - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - /* Skip spaces at the beginning */ - while (p < chunk->end) { - if (rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else { - break; - } - } - c = p; - while (p < chunk->end) { - if (*p == '}') { - break; - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - *macro_start = c; - *macro_len = p - c; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - break; - default: - /* Macro is not enclosed in quotes or braces */ - c = p; - while (p < chunk->end) { - if (rspamd_cl_lex_is_atom_end (*p)) { - break; - } - rspamd_cl_chunk_skipc (chunk, *p); - 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 (!rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE) && *p != ';') { - break; - } - rspamd_cl_chunk_skipc (chunk, *p); - 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 - * @param err if *err is NULL it is set to parser error - * @return TRUE if chunk has been parsed and FALSE in case of error - */ -static gboolean -rspamd_cl_state_machine (struct rspamd_cl_parser *parser, GError **err) -{ - rspamd_cl_object_t *obj; - struct rspamd_cl_chunk *chunk = parser->chunks; - struct rspamd_cl_stack *st; - const guchar *p, *c, *macro_start = NULL; - gsize macro_len = 0; - struct rspamd_cl_macro *macro = NULL; - - p = chunk->pos; - while (chunk->pos < chunk->end) { - switch (parser->state) { - case RSPAMD_RCL_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 - */ - if (!rspamd_cl_skip_comments (parser, err)) { - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - else { - p = chunk->pos; - obj = rspamd_cl_object_new (); - if (*p == '[') { - parser->state = RSPAMD_RCL_STATE_VALUE; - obj->type = RSPAMD_CL_ARRAY; - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else { - parser->state = RSPAMD_RCL_STATE_KEY; - obj->type = RSPAMD_CL_OBJECT; - if (*p == '{') { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - }; - parser->cur_obj = obj; - parser->top_obj = obj; - st = g_slice_alloc0 (sizeof (struct rspamd_cl_stack)); - st->obj = obj; - LL_PREPEND (parser->stack, st); - } - break; - case RSPAMD_RCL_STATE_KEY: - /* Skip any spaces */ - while (p < chunk->end && rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - if (*p == '}') { - /* We have the end of an object */ - parser->state = RSPAMD_RCL_STATE_AFTER_VALUE; - continue; - } - if (!rspamd_cl_parse_key (parser, chunk, err)) { - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - if (parser->state != RSPAMD_RCL_STATE_MACRO_NAME) { - parser->state = RSPAMD_RCL_STATE_VALUE; - } - else { - c = chunk->pos; - } - p = chunk->pos; - break; - case RSPAMD_RCL_STATE_VALUE: - /* We need to check what we do have */ - if (!rspamd_cl_parse_value (parser, chunk, err)) { - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - /* State is set in rspamd_cl_parse_value call */ - p = chunk->pos; - break; - case RSPAMD_RCL_STATE_AFTER_VALUE: - if (!rspamd_cl_parse_after_value (parser, chunk, err)) { - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - if (parser->stack != NULL) { - if (parser->stack->obj->type == RSPAMD_CL_OBJECT) { - parser->state = RSPAMD_RCL_STATE_KEY; - } - else { - /* Array */ - parser->state = RSPAMD_RCL_STATE_VALUE; - } - } - else { - /* Skip everything at the end */ - return TRUE; - } - p = chunk->pos; - break; - case RSPAMD_RCL_STATE_MACRO_NAME: - if (!rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE)) { - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - else if (p - c > 0) { - /* We got macro name */ - HASH_FIND (hh, parser->macroes, c, p - c, macro); - if (macro == NULL) { - rspamd_cl_set_err (chunk, RSPAMD_CL_EMACRO, "unknown macro", err); - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - /* Now we need to skip all spaces */ - while (p < chunk->end) { - if (!rcl_test_character (*p, RCL_CHARACTER_WHITESPACE_UNSAFE)) { - if (rspamd_cl_lex_is_comment (p[0], p[1])) { - /* Skip comment */ - if (!rspamd_cl_skip_comments (parser, err)) { - return FALSE; - } - p = chunk->pos; - } - break; - } - rspamd_cl_chunk_skipc (chunk, *p); - p ++; - } - parser->state = RSPAMD_RCL_STATE_MACRO; - } - break; - case RSPAMD_RCL_STATE_MACRO: - if (!rspamd_cl_parse_macro_value (parser, chunk, macro, - ¯o_start, ¯o_len, err)) { - parser->prev_state = parser->state; - parser->state = RSPAMD_RCL_STATE_ERROR; - return FALSE; - } - parser->state = parser->prev_state; - if (!macro->handler (macro_start, macro_len, macro->ud, err)) { - return FALSE; - } - p = chunk->pos; - break; - default: - /* TODO: add all states */ - return FALSE; - } - } - - return TRUE; -} - -struct rspamd_cl_parser* -rspamd_cl_parser_new (gint flags) -{ - struct rspamd_cl_parser *new; - - new = g_slice_alloc0 (sizeof (struct rspamd_cl_parser)); - - rspamd_cl_parser_register_macro (new, "include", rspamd_cl_include_handler, new); - rspamd_cl_parser_register_macro (new, "includes", rspamd_cl_includes_handler, new); - - new->flags = flags; - - return new; -} - - -void -rspamd_cl_parser_register_macro (struct rspamd_cl_parser *parser, const gchar *macro, - rspamd_cl_macro_handler handler, gpointer ud) -{ - struct rspamd_cl_macro *new; - - new = g_slice_alloc0 (sizeof (struct rspamd_cl_macro)); - new->handler = handler; - new->name = g_strdup (macro); - new->ud = ud; - HASH_ADD_KEYPTR (hh, parser->macroes, new->name, strlen (new->name), new); -} - -gboolean -rspamd_cl_parser_add_chunk (struct rspamd_cl_parser *parser, const guchar *data, - gsize len, GError **err) -{ - struct rspamd_cl_chunk *chunk; - - if (parser->state != RSPAMD_RCL_STATE_ERROR) { - chunk = g_slice_alloc (sizeof (struct rspamd_cl_chunk)); - 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 > RCL_MAX_RECURSION) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_ERECURSION, "maximum include nesting limit is reached: %d", - parser->recursion); - return FALSE; - } - return rspamd_cl_state_machine (parser, err); - } - - g_set_error (err, RCL_ERROR, RSPAMD_CL_ESTATE, "a parser is in an invalid state"); - - return FALSE; -} diff --git a/src/rcl/rcl_util.c b/src/rcl/rcl_util.c deleted file mode 100644 index c351fa859..000000000 --- a/src/rcl/rcl_util.c +++ /dev/null @@ -1,623 +0,0 @@ -/* 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 "config.h" -#include "rcl.h" -#include "rcl_internal.h" -#include "util.h" - -#ifdef HAVE_OPENSSL -#include <openssl/err.h> -#include <openssl/sha.h> -#include <openssl/rsa.h> -#include <openssl/ssl.h> -#include <openssl/evp.h> -#endif - -/** - * @file rcl_util.c - * Utilities for rcl parsing - */ - - -static void -rspamd_cl_obj_free_internal (rspamd_cl_object_t *obj, gboolean allow_rec) -{ - rspamd_cl_object_t *sub, *tmp; - - while (obj != NULL) { - if (obj->key != NULL) { - g_free (obj->key); - } - - if (obj->type == RSPAMD_CL_STRING) { - g_free (obj->value.sv); - } - else if (obj->type == RSPAMD_CL_ARRAY) { - sub = obj->value.ov; - while (sub != NULL) { - tmp = sub->next; - rspamd_cl_obj_free_internal (sub, FALSE); - sub = tmp; - } - } - else if (obj->type == RSPAMD_CL_OBJECT) { - HASH_ITER (hh, obj->value.ov, sub, tmp) { - HASH_DELETE (hh, obj->value.ov, sub); - rspamd_cl_obj_free_internal (sub, TRUE); - } - } - tmp = obj->next; - g_slice_free1 (sizeof (rspamd_cl_object_t), obj); - obj = tmp; - - if (!allow_rec) { - break; - } - } -} - -void -rspamd_cl_obj_free (rspamd_cl_object_t *obj) -{ - rspamd_cl_obj_free_internal (obj, TRUE); -} - -void -rspamd_cl_unescape_json_string (gchar *str) -{ - gchar *t = str, *h = str; - gint i, uval; - - /* t is target (tortoise), h is source (hare) */ - - while (*h != '\0') { - 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; - for (i = 0; i < 4; i++) { - uval <<= 4; - if (g_ascii_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; - } - } - h += 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++ = '?'; - } - break; - default: - *t++ = '?'; - break; - } - h ++; - } - else { - *t++ = *h++; - } - } - *t = '\0'; -} - -rspamd_cl_object_t* -rspamd_cl_parser_get_object (struct rspamd_cl_parser *parser, GError **err) -{ - if (parser->state != RSPAMD_RCL_STATE_INIT && parser->state != RSPAMD_RCL_STATE_ERROR) { - return rspamd_cl_obj_ref (parser->top_obj); - } - - return NULL; -} - -void -rspamd_cl_parser_free (struct rspamd_cl_parser *parser) -{ - struct rspamd_cl_stack *stack, *stmp; - struct rspamd_cl_macro *macro, *mtmp; - struct rspamd_cl_chunk *chunk, *ctmp; - struct rspamd_cl_pubkey *key, *ktmp; - - if (parser->top_obj != NULL) { - rspamd_cl_obj_unref (parser->top_obj); - } - - LL_FOREACH_SAFE (parser->stack, stack, stmp) { - g_slice_free1 (sizeof (struct rspamd_cl_stack), stack); - } - HASH_ITER (hh, parser->macroes, macro, mtmp) { - g_free (macro->name); - HASH_DEL (parser->macroes, macro); - g_slice_free1 (sizeof (struct rspamd_cl_macro), macro); - } - LL_FOREACH_SAFE (parser->chunks, chunk, ctmp) { - g_slice_free1 (sizeof (struct rspamd_cl_chunk), chunk); - } - LL_FOREACH_SAFE (parser->keys, key, ktmp) { - g_slice_free1 (sizeof (struct rspamd_cl_pubkey), key); - } - - g_slice_free1 (sizeof (struct rspamd_cl_parser), parser); -} - -gboolean -rspamd_cl_pubkey_add (struct rspamd_cl_parser *parser, const guchar *key, gsize len, GError **err) -{ - struct rspamd_cl_pubkey *nkey; -#ifndef HAVE_OPENSSL - g_set_error (err, RCL_ERROR, RSPAMD_CL_EINTERNAL, "cannot check signatures without openssl"); - return FALSE; -#else -# if (OPENSSL_VERSION_NUMBER < 0x10000000L) - g_set_error (err, RCL_ERROR, RSPAMD_CL_EINTERNAL, "cannot check signatures, openssl version is unsupported"); - return EXIT_FAILURE; -# else - BIO *mem; - - mem = BIO_new_mem_buf ((void *)key, len); - nkey = g_slice_alloc0 (sizeof (struct rspamd_cl_pubkey)); - nkey->key = PEM_read_bio_PUBKEY (mem, &nkey->key, NULL, NULL); - BIO_free (mem); - if (nkey->key == NULL) { - g_slice_free1 (sizeof (struct rspamd_cl_pubkey), nkey); - g_set_error (err, RCL_ERROR, RSPAMD_CL_ESSL, "%s", - ERR_error_string (ERR_get_error (), NULL)); - return FALSE; - } - LL_PREPEND (parser->keys, nkey); -# endif -#endif - return TRUE; -} - -#ifdef CURL_FOUND -struct rspamd_cl_curl_cbdata { - guchar *buf; - gsize buflen; -}; - -static gsize -rspamd_cl_curl_write_callback (gpointer contents, gsize size, gsize nmemb, gpointer ud) -{ - struct rspamd_cl_curl_cbdata *cbdata = ud; - gsize realsize = size * nmemb; - - cbdata->buf = g_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 gboolean -rspamd_cl_fetch_url (const guchar *url, guchar **buf, gsize *buflen, GError **err) -{ - -#ifdef HAVE_FETCH_H - struct url *fetch_url; - struct url_stat us; - FILE *in; - - fetch_url = fetchParseURL (url); - if (fetch_url == NULL) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "invalid URL %s: %s", - url, strerror (errno)); - return FALSE; - } - if ((in = fetchXGet (fetch_url, &us, "")) == NULL) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "cannot fetch URL %s: %s", - url, strerror (errno)); - fetchFreeURL (fetch_url); - return FALSE; - } - - *buflen = us.size; - *buf = g_malloc (*buflen); - if (*buf == NULL) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "cannot allocate buffer for URL %s: %s", - url, strerror (errno)); - fclose (in); - fetchFreeURL (fetch_url); - return FALSE; - } - - if (fread (*buf, *buflen, 1, in) != 1) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "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; - gint r; - struct rspamd_cl_curl_cbdata cbdata; - - curl = curl_easy_init (); - if (curl == NULL) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EINTERNAL, "CURL interface is broken"); - return FALSE; - } - if ((r = curl_easy_setopt (curl, CURLOPT_URL, url)) != CURLE_OK) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "invalid URL %s: %s", - url, curl_easy_strerror (r)); - curl_easy_cleanup (curl); - return FALSE; - } - curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, rspamd_cl_curl_write_callback); - cbdata.buf = *buf; - cbdata.buflen = *buflen; - curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbdata); - - if ((r = curl_easy_perform (curl)) != CURLE_OK) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "error fetching URL %s: %s", - url, curl_easy_strerror (r)); - curl_easy_cleanup (curl); - if (buf != NULL) { - g_free (buf); - } - return FALSE; - } - *buf = cbdata.buf; - *buflen = cbdata.buflen; - - return TRUE; -#else - g_set_error (err, RCL_ERROR, RSPAMD_CL_EINTERNAL, "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 gboolean -rspamd_cl_fetch_file (const guchar *filename, guchar **buf, gsize *buflen, GError **err) -{ - gint fd; - struct stat st; - - if (stat (filename, &st) == -1) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "cannot stat file %s: %s", - filename, strerror (errno)); - return FALSE; - } - if ((fd = open (filename, O_RDONLY)) == -1) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "cannot open file %s: %s", - filename, strerror (errno)); - return FALSE; - } - if ((*buf = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { - close (fd); - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "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 gboolean -rspamd_cl_sig_check (const guchar *data, gsize datalen, - const guchar *sig, gsize siglen, struct rspamd_cl_parser *parser) -{ - struct rspamd_cl_pubkey *key; - gchar dig[EVP_MAX_MD_SIZE]; - guint 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 gboolean -rspamd_cl_include_url (const guchar *data, gsize len, - struct rspamd_cl_parser *parser, gboolean check_signature, GError **err) -{ - - gboolean res; - guchar *buf = NULL, *sigbuf = NULL; - gsize buflen = 0, siglen = 0; - struct rspamd_cl_chunk *chunk; - gchar urlbuf[PATH_MAX]; - - rspamd_snprintf (urlbuf, sizeof (urlbuf), "%*s", len, data); - - if (!rspamd_cl_fetch_url (urlbuf, &buf, &buflen, err)) { - return FALSE; - } - - if (check_signature) { -#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) - /* We need to check signature first */ - rspamd_snprintf (urlbuf, sizeof (urlbuf), "%*s.sig", len, data); - if (!rspamd_cl_fetch_file (urlbuf, &sigbuf, &siglen, err)) { - return FALSE; - } - if (!rspamd_cl_sig_check (buf, buflen, sigbuf, siglen, parser)) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_ESSL, "cannot verify url %s: %s", - urlbuf, - ERR_error_string (ERR_get_error (), NULL)); - munmap (sigbuf, siglen); - return FALSE; - } - munmap (sigbuf, siglen); -#endif - } - - res = rspamd_cl_parser_add_chunk (parser, buf, buflen, err); - if (res == TRUE) { - /* Remove chunk from the stack */ - chunk = parser->chunks; - if (chunk != NULL) { - parser->chunks = chunk->next; - g_slice_free1 (sizeof (struct rspamd_cl_chunk), chunk); - } - } - g_free (buf); - - return res; -} - -/** - * Include a file to configuration - * @param data - * @param len - * @param parser - * @param err - * @return - */ -static gboolean -rspamd_cl_include_file (const guchar *data, gsize len, - struct rspamd_cl_parser *parser, gboolean check_signature, GError **err) -{ - gboolean res; - struct rspamd_cl_chunk *chunk; - guchar *buf = NULL, *sigbuf = NULL; - gsize buflen, siglen; - gchar filebuf[PATH_MAX], realbuf[PATH_MAX]; - - rspamd_snprintf (filebuf, sizeof (filebuf), "%*s", len, data); - if (realpath (filebuf, realbuf) == NULL) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_EIO, "cannot open file %s: %s", - filebuf, - strerror (errno)); - return FALSE; - } - - if (!rspamd_cl_fetch_file (realbuf, &buf, &buflen, err)) { - return FALSE; - } - - if (check_signature) { -#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) - /* We need to check signature first */ - rspamd_snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf); - if (!rspamd_cl_fetch_file (filebuf, &sigbuf, &siglen, err)) { - return FALSE; - } - if (!rspamd_cl_sig_check (buf, buflen, sigbuf, siglen, parser)) { - g_set_error (err, RCL_ERROR, RSPAMD_CL_ESSL, "cannot verify file %s: %s", - filebuf, - ERR_error_string (ERR_get_error (), NULL)); - munmap (sigbuf, siglen); - return FALSE; - } - munmap (sigbuf, siglen); -#endif - } - - res = rspamd_cl_parser_add_chunk (parser, buf, buflen, err); - if (res == TRUE) { - /* Remove chunk from the stack */ - chunk = parser->chunks; - if (chunk != NULL) { - parser->chunks = chunk->next; - g_slice_free1 (sizeof (struct rspamd_cl_chunk), chunk); - } - } - 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 - */ -gboolean -rspamd_cl_include_handler (const guchar *data, gsize len, gpointer ud, GError **err) -{ - struct rspamd_cl_parser *parser = ud; - - if (*data == '/' || *data == '.') { - /* Try to load a file */ - return rspamd_cl_include_file (data, len, parser, FALSE, err); - } - - return rspamd_cl_include_url (data, len, parser, FALSE, err); -} - -/** - * Handle includes macro - * @param data include data - * @param len length of data - * @param ud user data - * @param err error ptr - * @return - */ -gboolean -rspamd_cl_includes_handler (const guchar *data, gsize len, gpointer ud, GError **err) -{ - struct rspamd_cl_parser *parser = ud; - - if (*data == '/' || *data == '.') { - /* Try to load a file */ - return rspamd_cl_include_file (data, len, parser, TRUE, err); - } - - return rspamd_cl_include_url (data, len, parser, TRUE, err); -} - -gboolean -rspamd_cl_parser_add_file (struct rspamd_cl_parser *parser, const gchar *filename, - GError **err) -{ - guchar *buf; - gsize len; - gboolean ret; - - if (!rspamd_cl_fetch_file (filename, &buf, &len, err)) { - return FALSE; - } - - ret = rspamd_cl_parser_add_chunk (parser, buf, len, err); - - munmap (buf, len); - - return ret; -} diff --git a/src/ucl/.gitignore b/src/ucl/.gitignore new file mode 100644 index 000000000..ea72388b3 --- /dev/null +++ b/src/ucl/.gitignore @@ -0,0 +1,3 @@ +.cproject +.project +.settings diff --git a/src/ucl/CMakeLists.txt b/src/ucl/CMakeLists.txt new file mode 100644 index 000000000..73937d199 --- /dev/null +++ b/src/ucl/CMakeLists.txt @@ -0,0 +1,20 @@ +SET(UCLSRC src/ucl_util.c + src/ucl_parser.c + src/ucl_emitter.c) + +ADD_LIBRARY(rspamd-ucl ${LINK_TYPE} ${UCLSRC}) +SET_TARGET_PROPERTIES(rspamd-ucl PROPERTIES VERSION ${RSPAMD_VERSION}) +SET_TARGET_PROPERTIES(rspamd-ucl PROPERTIES COMPILE_FLAGS "-DRSPAMD_LIB") + +IF(HAVE_FETCH_H) + TARGET_LINK_LIBRARIES(rspamd-ucl fetch) +ELSE(HAVE_FETCH_H) + IF(CURL_FOUND) + TARGET_LINK_LIBRARIES(rspamd-ucl ${CURL_LIBRARIES}) + ENDIF(CURL_FOUND) +ENDIF(HAVE_FETCH_H) +IF(NO_SHARED MATCHES "OFF") + INSTALL(TARGETS rspamd-ucl + LIBRARY DESTINATION ${LIBDIR} + PUBLIC_HEADER DESTINATION ${INCLUDEDIR}) +ENDIF(NO_SHARED MATCHES "OFF")
\ No newline at end of file diff --git a/src/ucl/README.md b/src/ucl/README.md new file mode 100644 index 000000000..18779a835 --- /dev/null +++ b/src/ucl/README.md @@ -0,0 +1,236 @@ +## Introduction + +This document describes the main features and principles of the configuration +language called `UCL` - universal configuration language. + +## Basic structure + +UCL is heavily infused by `nginx` configuration as the example of a convenient configuration +system. However, UCL is fully compatible with `JSON` format and is able to parse json files. +For example, you can write the same configuration in the following ways: + +* in nginx like: + +```nginx +param = value; +section { + param = value; + param1 = value1; + flag = true; + number = 10k; + time = 0.2s; + string = "something"; + subsection { + host = { + host = "hostname"; + port = 900; + } + host = { + host = "hostname"; + port = 901; + } + } +} +``` + +* or in JSON: + +```json +{ + "param": "value", + "param1": "value1", + "flag": true, + "subsection": { + "host": [ + { + "host": "hostname", + "port": 900 + }, + { + "host": "hostname", + "port": 901 + } + ] + } +} +``` + +## Improvements to the json notation. + +There are various things that make ucl configuration more convenient for editing than strict json: + +### General syntax sugar + +* Braces are not necessary to enclose a top object: it is automatically treated as an object: + +```json +"key": "value" +``` +is equal to: +```json +{"key": "value"} +``` + +* There is no requirement of quotes for strings and keys, moreover, `:` may be replaced `=` or even be skipped for objects: + +```nginx +key = value; +section { + key = value; +} +``` +is equal to: +```json +{ + "key": "value", + "section": { + "key": "value" + } +} +``` + +* No commas mess: you can safely place a comma or semicolon for the last element in an array or an object: + +```json +{ + "key1": "value", + "key2": "value", +} +``` +### Automatic arrays creation + +* Non-unique keys in an object are allowed and are automatically converted to the arrays internally: + +```json +{ + "key": "value1", + "key": "value2" +} +``` +is converted to: +```json +{ + "key": ["value1", "value2"] +} +``` + +### Convenient numbers and booleans + +* Numbers can have suffixes to specify standard multipliers: + + `[kKmMgG]` - standard 10 base multipliers (so `1k` is translated to 1000) + + `[kKmMgG]b` - 2 power multipliers (so `1kb` is translated to 1024) + + `[s|min|d|w|y]` - time multipliers, all time values are translated to float number of seconds, for example `10min` is translated to 600.0 and `10ms` is translated to 0.01 +* Booleans can be specified as `true` or `yes` or `on` and `false` or `no` or `off`. +* It is still possible to treat numbers and booleans as strings by enclosing them in double quotes. + +## General improvements + +### Commments + +UCL supports different style of comments: + +* single line: `#` +* multiline: `/* ... */` + +Multiline comments may be nested: +```c +# Sample single line comment +/* + some comment + /* nested comment */ + end of comment +*/ +``` + +### Macros support + +UCL supports external macros both multiline and single line ones: +```nginx +.macro "sometext"; +.macro { + Some long text + .... +}; +``` +There are two internal macros provided by UCL: + +* `include` - read a file `/path/to/file` or an url `http://example.com/file` and include it to the current place of +UCL configuration; +* `includes` - read a file or an url like the previous macro, but fetch and check the signature file (which is obtained +by `.sig` suffix appending). + +Public keys which are used for the last command are specified by the concrete UCL user. + +### Multiline strings + +UCL can handle multiline strings as well as single line ones. It uses shell/perl like notation for such objects: +``` +key = <<EOD +some text +splitted to +lines +EOD +``` + +In this example `key` will be interpreted as the following string: `some text\nsplitted to\nlines`. +Here are some rules for this syntax: + +* Multiline terminator must start just after `<<` symbols and it must consist of capital letters only (e.g. `<<eof` or `<< EOF` won't work); +* Terminator must end with a single newline character (and no spaces are allowed between terminator and newline character); +* To finish multiline string you need to include a terminator string just after newline and followed by a newline (no spaces or other characters are allowed as well); +* The initial and the final newlines are not inserted to the resulting string, but you can still specify newlines at the begin and at the end of a value, for example: + +``` +key <<EOD + +some +text + +EOD +``` + +## Emitter + +Each UCL object can be serialized to one of the three supported formats: + +* `JSON` - canonic json notation (with spaces indented structure); +* `Compacted JSON` - compact json notation (without spaces or newlines); +* `Configuration` - nginx like notation; +* `YAML` - yaml inlined notation. + +## Performance + +Are UCL parser and emitter fast enough? Well, there are some numbers. +I got a 19Mb file that consist of ~700 thousands lines of json (obtained via +http://www.json-generator.com/). Then I checked jansson library that performs json +parsing and emitting and compared it with UCL. Here are results: + +``` +jansson: parsed json in 1.3899 seconds +jansson: emitted object in 0.2609 seconds + +ucl: parsed input in 0.6649 seconds +ucl: emitted config in 0.2423 seconds +ucl: emitted json in 0.2329 seconds +ucl: emitted compact json in 0.1811 seconds +ucl: emitted yaml in 0.2489 seconds +``` + +So far, UCL seems to be significantly faster than jansson on parsing and slightly faster on emitting. Moreover, +UCL compiled with optimizations (-O3) performs significantly faster: +``` +ucl: parsed input in 0.3002 seconds +ucl: emitted config in 0.1174 seconds +ucl: emitted json in 0.1174 seconds +ucl: emitted compact json in 0.0991 seconds +ucl: emitted yaml in 0.1354 seconds +``` + +You can do your own benchmarks by running `make test` in libucl top directory. + +## Conclusion + +UCL has clear design that should be very convenient for reading and writing. At the same time it is compatible with +JSON language and therefore can be used as a simple JSON parser. Macroes logic provides an ability to extend configuration +language (for example by including some lua code) and comments allows to disable or enable the parts of a configuration +quickly. diff --git a/src/ucl/include/ucl.h b/src/ucl/include/ucl.h new file mode 100644 index 000000000..1b8fa8631 --- /dev/null +++ b/src/ucl/include/ucl.h @@ -0,0 +1,731 @@ +/* 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 <string.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdarg.h> +#include <stdio.h> +#include "config.h" + +#include "uthash.h" +#include "utlist.h" +#include "utstring.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 +}; + +enum ucl_type { + UCL_OBJECT = 0, + UCL_ARRAY, + UCL_INT, + UCL_FLOAT, + UCL_STRING, + UCL_BOOLEAN, + UCL_TIME, + UCL_USERDATA +}; + +enum ucl_emitter { + UCL_EMIT_JSON = 0, + UCL_EMIT_JSON_COMPACT, + UCL_EMIT_CONFIG, + UCL_EMIT_YAML +}; + +enum ucl_flags { + UCL_FLAG_KEY_LOWERCASE = 0x1, + UCL_FLAG_ZEROCOPY = 0x2 +}; + +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 */ + int ref; /**< reference count */ + 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; +} + +/** + * 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) */ +}; + +/** + * 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 + */ +static inline ucl_object_t * +ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt, + const char *key, size_t keylen, bool copy_key) +{ + ucl_object_t *found; + + if (elt == NULL || key == NULL) { + return NULL; + } + + if (top == NULL) { + top = ucl_object_new (); + top->type = UCL_OBJECT; + } + if (keylen == 0) { + keylen = strlen (key); + } + + HASH_FIND (hh, top->value.ov, key, keylen, found); + + if (!found) { + HASH_ADD_KEYPTR (hh, top->value.ov, key, keylen, elt); + } + DL_APPEND (found, elt); + + if (copy_key) { + ucl_copy_key_trash (elt); + } + + return top; +} + +/** + * 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, UT_string **err); + +/* 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, UT_string **err); + +/** + * 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, + UT_string **err); + +/** + * 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, UT_string **err); + +/** + * 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, UT_string **err); + +#endif /* RCL_H_ */ diff --git a/src/ucl/src/ucl_chartable.h b/src/ucl/src/ucl_chartable.h new file mode 100644 index 000000000..232043c1b --- /dev/null +++ b/src/ucl/src/ucl_chartable.h @@ -0,0 +1,266 @@ +/* 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_CHARTABLE_H_ +#define UCL_CHARTABLE_H_ + +#include "ucl_internal.h" + +static const unsigned int ucl_chartable[255] = { +UCL_CHARACTER_VALUE_END, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_JSON_UNSAFE, +UCL_CHARACTER_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_JSON_UNSAFE, +UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE, +UCL_CHARACTER_WHITESPACE_UNSAFE, +UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_JSON_UNSAFE, +UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, +UCL_CHARACTER_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP /* */, +UCL_CHARACTER_VALUE_STR /* ! */, +UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE /* " */, +UCL_CHARACTER_VALUE_END /* # */, UCL_CHARACTER_VALUE_STR /* $ */, +UCL_CHARACTER_VALUE_STR /* % */, UCL_CHARACTER_VALUE_STR /* & */, +UCL_CHARACTER_VALUE_STR /* ' */, UCL_CHARACTER_VALUE_STR /* ( */, +UCL_CHARACTER_VALUE_STR /* ) */, UCL_CHARACTER_VALUE_STR /* * */, +UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* + */, +UCL_CHARACTER_VALUE_END /* , */, +UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* - */, +UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* . */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE /* / */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 0 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 1 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 2 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 3 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 4 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 5 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 6 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 7 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 8 */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 9 */, +UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP /* : */, +UCL_CHARACTER_VALUE_END /* ; */, UCL_CHARACTER_VALUE_STR /* < */, +UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP /* = */, +UCL_CHARACTER_VALUE_STR /* > */, UCL_CHARACTER_VALUE_STR /* ? */, +UCL_CHARACTER_VALUE_STR /* @ */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* A */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* B */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* C */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* D */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* E */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* F */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* G */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* H */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* I */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* J */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* K */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* L */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* M */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* N */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* O */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* P */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Q */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* R */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* S */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* T */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* U */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* V */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* W */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* X */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Y */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Z */, +UCL_CHARACTER_VALUE_STR /* [ */, +UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE /* \ */, +UCL_CHARACTER_VALUE_END /* ] */, UCL_CHARACTER_VALUE_STR /* ^ */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR /* _ */, +UCL_CHARACTER_VALUE_STR /* ` */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* a */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* b */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* c */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* d */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* e */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* f */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* g */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* h */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* i */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* j */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* k */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* l */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* m */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* n */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* o */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* p */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* q */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* r */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* s */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* t */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* u */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* v */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* w */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* x */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* y */, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* z */, +UCL_CHARACTER_VALUE_STR /* { */, UCL_CHARACTER_VALUE_STR /* | */, +UCL_CHARACTER_VALUE_END /* } */, UCL_CHARACTER_VALUE_STR /* ~ */, +UCL_CHARACTER_DENIED, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR, +UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR +}; + +static inline bool +ucl_test_character (unsigned char c, int type_flags) +{ + return (ucl_chartable[c] & type_flags) != 0; +} + +#endif /* UCL_CHARTABLE_H_ */ diff --git a/src/ucl/src/ucl_emitter.c b/src/ucl/src/ucl_emitter.c new file mode 100644 index 000000000..78e321c53 --- /dev/null +++ b/src/ucl/src/ucl_emitter.c @@ -0,0 +1,629 @@ +/* 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 <float.h> +#include <math.h> +#include "ucl.h" +#include "ucl_internal.h" +#include "ucl_chartable.h" + +/** + * @file rcl_emitter.c + * Serialise RCL object to the RCL format + */ + + +static void ucl_obj_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact); +static void ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, + bool start_tabs, bool is_top, bool expand_array); +static void ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, + bool start_tabs, bool compact, bool expand_array); + +/** + * Add tabulation to the output buffer + * @param buf target buffer + * @param tabs number of tabs to add + */ +static inline void +ucl_add_tabs (UT_string *buf, unsigned int tabs, bool compact) +{ + char *p; + unsigned int i; + + if (!compact) { + while (buf->n - buf->i <= tabs * 4) { + utstring_reserve (buf, buf->n * 2); + } + p = &buf->d[buf->i]; + for (i = 0; i < tabs; i ++) { + memset (&p[i * 4], ' ', 4); + } + buf->i += i * 4; + buf->d[buf->i] = '\0'; + } +} + +/** + * Serialise string + * @param str string to emit + * @param buf target buffer + */ +static void +ucl_elt_string_write_json (const char *str, size_t size, UT_string *buf) +{ + const char *p = str, *c = str; + size_t len = 0; + + utstring_append_c (buf, '"'); + while (size) { + if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) { + if (len > 0) { + utstring_append_len (buf, c, len); + } + switch (*p) { + case '\n': + utstring_append_len (buf, "\\n", 2); + break; + case '\r': + utstring_append_len (buf, "\\r", 2); + break; + case '\b': + utstring_append_len (buf, "\\b", 2); + break; + case '\t': + utstring_append_len (buf, "\\t", 2); + break; + case '\f': + utstring_append_len (buf, "\\f", 2); + break; + case '\\': + utstring_append_len (buf, "\\\\", 2); + break; + case '"': + utstring_append_len (buf, "\\\"", 2); + break; + } + len = 0; + c = ++p; + } + else { + p ++; + len ++; + } + size --; + } + if (len > 0) { + utstring_append_len (buf, c, len); + } + utstring_append_c (buf, '"'); +} + +static inline void +ucl_print_float (UT_string *buf, double val) +{ + if (val == (double)(int)val) { + utstring_printf (buf, "%.1lf", val); + } + else if (fabs (val - (double)(int)val) < 0.0000001) { + /* Write at maximum precision */ + utstring_printf (buf, "%.*lg", DBL_DIG, val); + } + else { + utstring_printf (buf, "%lf", val); + } +} + +/** + * Write a single object to the buffer + * @param obj object to write + * @param buf target buffer + */ +static void +ucl_elt_obj_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact) +{ + ucl_object_t *cur, *tmp; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + if (compact) { + utstring_append_c (buf, '{'); + } + else { + utstring_append_len (buf, "{\n", 2); + } + HASH_ITER (hh, obj, cur, tmp) { + ucl_add_tabs (buf, tabs + 1, compact); + if (cur->hh.keylen > 0) { + ucl_elt_string_write_json (cur->hh.key, cur->hh.keylen, buf); + } + else { + utstring_append_len (buf, "null", 4); + } + if (compact) { + utstring_append_c (buf, ':'); + } + else { + utstring_append_len (buf, ": ", 2); + } + ucl_obj_write_json (cur, buf, tabs + 1, false, compact); + if (cur->hh.next != NULL) { + if (compact) { + utstring_append_c (buf, ','); + } + else { + utstring_append_len (buf, ",\n", 2); + } + } + else if (!compact) { + utstring_append_c (buf, '\n'); + } + } + ucl_add_tabs (buf, tabs, compact); + utstring_append_c (buf, '}'); +} + +/** + * Write a single array to the buffer + * @param obj array to write + * @param buf target buffer + */ +static void +ucl_elt_array_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact) +{ + ucl_object_t *cur = obj; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + if (compact) { + utstring_append_c (buf, '['); + } + else { + utstring_append_len (buf, "[\n", 2); + } + while (cur) { + ucl_elt_write_json (cur, buf, tabs + 1, true, compact); + if (cur->next != NULL) { + if (compact) { + utstring_append_c (buf, ','); + } + else { + utstring_append_len (buf, ",\n", 2); + } + } + else if (!compact) { + utstring_append_c (buf, '\n'); + } + cur = cur->next; + } + ucl_add_tabs (buf, tabs, compact); + utstring_append_c (buf, ']'); +} + +/** + * Emit a single element + * @param obj object + * @param buf buffer + */ +void +ucl_elt_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact) +{ + switch (obj->type) { + case UCL_INT: + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj)); + break; + case UCL_FLOAT: + case UCL_TIME: + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + ucl_print_float (buf, ucl_object_todouble (obj)); + break; + case UCL_BOOLEAN: + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false"); + break; + case UCL_STRING: + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + ucl_elt_string_write_json (obj->value.sv, obj->len, buf); + break; + case UCL_OBJECT: + ucl_elt_obj_write_json (obj->value.ov, buf, tabs, start_tabs, compact); + break; + case UCL_ARRAY: + ucl_elt_array_write_json (obj->value.ov, buf, tabs, start_tabs, compact); + break; + case UCL_USERDATA: + break; + } +} + +/** + * Write a single object to the buffer + * @param obj object + * @param buf target buffer + */ +static void +ucl_obj_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact) +{ + ucl_object_t *cur; + bool is_array = (obj->next != NULL); + + if (is_array) { + /* This is an array actually */ + if (start_tabs) { + ucl_add_tabs (buf, tabs, compact); + } + + if (compact) { + utstring_append_c (buf, '['); + } + else { + utstring_append_len (buf, "[\n", 2); + } + cur = obj; + while (cur != NULL) { + ucl_elt_write_json (cur, buf, tabs + 1, true, compact); + if (cur->next) { + utstring_append_c (buf, ','); + } + if (!compact) { + utstring_append_c (buf, '\n'); + } + cur = cur->next; + } + ucl_add_tabs (buf, tabs, compact); + utstring_append_c (buf, ']'); + } + else { + ucl_elt_write_json (obj, buf, tabs, start_tabs, compact); + } + +} + +/** + * Emit an object to json + * @param obj object + * @return json output (should be freed after using) + */ +static UT_string * +ucl_object_emit_json (ucl_object_t *obj, bool compact) +{ + UT_string *buf; + + /* Allocate large enough buffer */ + utstring_new (buf); + + ucl_obj_write_json (obj, buf, 0, false, compact); + + return buf; +} + +/** + * Write a single object to the buffer + * @param obj object to write + * @param buf target buffer + */ +static void +ucl_elt_obj_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top) +{ + ucl_object_t *cur, *tmp; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, is_top); + } + if (!is_top) { + utstring_append_len (buf, "{\n", 2); + } + + HASH_ITER (hh, obj, cur, tmp) { + ucl_add_tabs (buf, tabs + 1, is_top); + utstring_append_len (buf, cur->hh.key, cur->hh.keylen); + if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) { + utstring_append_len (buf, " = ", 3); + } + else { + utstring_append_c (buf, ' '); + } + ucl_elt_write_rcl (cur, buf, is_top ? tabs : tabs + 1, false, false, true); + if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) { + utstring_append_len (buf, ";\n", 2); + } + else { + utstring_append_c (buf, '\n'); + } + } + + ucl_add_tabs (buf, tabs, is_top); + if (!is_top) { + utstring_append_c (buf, '}'); + } +} + +/** + * Write a single array to the buffer + * @param obj array to write + * @param buf target buffer + */ +static void +ucl_elt_array_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top) +{ + ucl_object_t *cur = obj; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + + utstring_append_len (buf, "[\n", 2); + while (cur) { + ucl_elt_write_rcl (cur, buf, tabs + 1, true, false, false); + utstring_append_len (buf, ",\n", 2); + cur = cur->next; + } + ucl_add_tabs (buf, tabs, false); + utstring_append_c (buf, ']'); +} + +/** + * Emit a single element + * @param obj object + * @param buf buffer + */ +static void +ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, + bool start_tabs, bool is_top, bool expand_array) +{ + if (expand_array && obj->next != NULL) { + ucl_elt_array_write_rcl (obj, buf, tabs, start_tabs, is_top); + } + else { + switch (obj->type) { + case UCL_INT: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj)); + break; + case UCL_FLOAT: + case UCL_TIME: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + ucl_print_float (buf, ucl_object_todouble (obj)); + break; + case UCL_BOOLEAN: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false"); + break; + case UCL_STRING: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + ucl_elt_string_write_json (obj->value.sv, obj->len, buf); + break; + case UCL_OBJECT: + ucl_elt_obj_write_rcl (obj->value.ov, buf, tabs, start_tabs, is_top); + break; + case UCL_ARRAY: + ucl_elt_array_write_rcl (obj->value.ov, buf, tabs, start_tabs, is_top); + break; + case UCL_USERDATA: + break; + } + } +} + +/** + * Emit an object to rcl + * @param obj object + * @return rcl output (should be freed after using) + */ +static UT_string * +ucl_object_emit_rcl (ucl_object_t *obj) +{ + UT_string *buf; + + /* Allocate large enough buffer */ + utstring_new (buf); + + ucl_elt_write_rcl (obj, buf, 0, false, true, true); + + return buf; +} + + +/** + * Write a single object to the buffer + * @param obj object to write + * @param buf target buffer + */ +static void +ucl_elt_obj_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top) +{ + ucl_object_t *cur, *tmp; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, is_top); + } + if (!is_top) { + utstring_append_len (buf, ": {\n", 4); + } + + HASH_ITER (hh, obj, cur, tmp) { + ucl_add_tabs (buf, tabs + 1, is_top); + utstring_append_len (buf, cur->hh.key, cur->hh.keylen); + if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) { + utstring_append_len (buf, " : ", 3); + } + else { + utstring_append_c (buf, ' '); + } + ucl_elt_write_yaml (cur, buf, is_top ? tabs : tabs + 1, false, false, true); + if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) { + if (!is_top) { + utstring_append_len (buf, ",\n", 2); + } + else { + utstring_append_c (buf, '\n'); + } + } + else { + utstring_append_c (buf, '\n'); + } + } + + ucl_add_tabs (buf, tabs, is_top); + if (!is_top) { + utstring_append_c (buf, '}'); + } +} + +/** + * Write a single array to the buffer + * @param obj array to write + * @param buf target buffer + */ +static void +ucl_elt_array_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top) +{ + ucl_object_t *cur = obj; + + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + + utstring_append_len (buf, ": [\n", 4); + while (cur) { + ucl_elt_write_yaml (cur, buf, tabs + 1, true, false, false); + utstring_append_len (buf, ",\n", 2); + cur = cur->next; + } + ucl_add_tabs (buf, tabs, false); + utstring_append_c (buf, ']'); +} + +/** + * Emit a single element + * @param obj object + * @param buf buffer + */ +static void +ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs, + bool start_tabs, bool is_top, bool expand_array) +{ + if (expand_array && obj->next != NULL) { + ucl_elt_array_write_yaml (obj, buf, tabs, start_tabs, is_top); + } + else { + switch (obj->type) { + case UCL_INT: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj)); + break; + case UCL_FLOAT: + case UCL_TIME: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + ucl_print_float (buf, ucl_object_todouble (obj)); + break; + case UCL_BOOLEAN: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false"); + break; + case UCL_STRING: + if (start_tabs) { + ucl_add_tabs (buf, tabs, false); + } + ucl_elt_string_write_json (obj->value.sv, obj->len, buf); + break; + case UCL_OBJECT: + ucl_elt_obj_write_yaml (obj->value.ov, buf, tabs, start_tabs, is_top); + break; + case UCL_ARRAY: + ucl_elt_array_write_yaml (obj->value.ov, buf, tabs, start_tabs, is_top); + break; + case UCL_USERDATA: + break; + } + } +} + +/** + * Emit an object to rcl + * @param obj object + * @return rcl output (should be freed after using) + */ +static UT_string * +ucl_object_emit_yaml (ucl_object_t *obj) +{ + UT_string *buf; + + /* Allocate large enough buffer */ + utstring_new (buf); + + ucl_elt_write_yaml (obj, buf, 0, false, true, true); + + return buf; +} + +unsigned char * +ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type) +{ + UT_string *buf = NULL; + unsigned char *res = NULL; + + if (emit_type == UCL_EMIT_JSON) { + buf = ucl_object_emit_json (obj, false); + } + else if (emit_type == UCL_EMIT_JSON_COMPACT) { + buf = ucl_object_emit_json (obj, true); + } + else if (emit_type == UCL_EMIT_YAML) { + buf = ucl_object_emit_yaml (obj); + } + else { + buf = ucl_object_emit_rcl (obj); + } + + if (buf != NULL) { + res = utstring_body (buf); + free (buf); + } + + return res; +} diff --git a/src/ucl/src/ucl_internal.h b/src/ucl/src/ucl_internal.h new file mode 100644 index 000000000..97613c824 --- /dev/null +++ b/src/ucl/src/ucl_internal.h @@ -0,0 +1,249 @@ +/* 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_ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> + +#include "utlist.h" +#include "ucl.h" + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#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 +}; + +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; +}; + +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_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; +}; + +/** + * 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, UT_string **err); + +/** + * 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, UT_string **err); + +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); + + +void ucl_elt_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, + bool start_tabs, bool compact); + +#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 (tolower (p[0]) == 'f' && strncasecmp (p, "false", 5) == 0) { + ret = true; + val = false; + } + } + else if (len == 4) { + if (tolower (p[0]) == 't' && strncasecmp (p, "true", 4) == 0) { + ret = true; + val = true; + } + } + else if (len == 3) { + if (tolower (p[0]) == 'y' && strncasecmp (p, "yes", 3) == 0) { + ret = true; + val = true; + } + if (tolower (p[0]) == 'o' && strncasecmp (p, "off", 3) == 0) { + ret = true; + val = false; + } + } + else if (len == 2) { + if (tolower (p[0]) == 'n' && strncasecmp (p, "no", 2) == 0) { + ret = true; + val = false; + } + else if (tolower (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); + +#endif /* UCL_INTERNAL_H_ */ diff --git a/src/ucl/src/ucl_parser.c b/src/ucl/src/ucl_parser.c new file mode 100644 index 000000000..30106ebd3 --- /dev/null +++ b/src/ucl/src/ucl_parser.c @@ -0,0 +1,1420 @@ +/* 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) + +/** + * Save parser state + * @param chunk + * @param s + */ +static inline void +ucl_chunk_save_state (struct ucl_chunk *chunk, struct ucl_parser_saved_state *s) +{ + s->column = chunk->column; + s->pos = chunk->pos; + s->line = chunk->line; + s->remain = chunk->remain; +} + +/** + * Restore parser state + * @param chunk + * @param s + */ +static inline void +ucl_chunk_restore_state (struct ucl_chunk *chunk, struct ucl_parser_saved_state *s) +{ + chunk->column = s->column; + chunk->pos = s->pos; + chunk->line = s->line; + chunk->remain = s->remain; +} + +static inline void +ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err) +{ + ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'", + chunk->line, chunk->column, str, *chunk->pos); +} + +static bool +ucl_skip_comments (struct ucl_parser *parser, UT_string **err) +{ + 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, "comments nesting is invalid", 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; +} + +static inline size_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, UT_string **err) +{ + size_t ret = 0; + + if (need_unescape || need_lowercase || !(parser->flags & UCL_FLAG_ZEROCOPY)) { + /* Copy string */ + *dst = UCL_ALLOC (in_len + 1); + if (*dst == NULL) { + ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", 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); + } + *dst_const = *dst; + } + else { + *dst_const = src; + ret = in_len; + } + + return ret; +} + +int +ucl_maybe_parse_number (ucl_object_t *obj, + const char *start, const char *end, const char **pos, bool allow_double) +{ + const char *p = start, *c = start; + char *endptr; + bool got_dot = false, got_exp = false, need_double = false, is_date = false; + double dv; + int64_t lv; + + if (*p == '-') { + p ++; + } + while (p < end) { + if (isdigit (*p)) { + p ++; + } + 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; + } + } + + errno = 0; + if (need_double) { + dv = strtod (c, &endptr); + } + 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') { + p = endptr; + goto set_obj; + } + + if (endptr < end) { + 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_date = 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 (p[1] == 'b' || p[1] == 'B') { + /* Megabytes */ + 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, false); + } + p ++; + goto set_obj; + } + else if (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_date = 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, false); + } + p ++; + goto set_obj; + } + break; + case 'S': + case 's': + if (p == end - 1 || ucl_lex_is_atom_end (p[1])) { + if (!need_double) { + need_double = true; + dv = lv; + } + p ++; + is_date = true; + goto set_obj; + } + break; + case 'h': + case 'H': + case 'd': + case 'D': + case 'w': + case 'W': + case 'Y': + case 'y': + if (p == end - 1 || ucl_lex_is_atom_end (p[1])) { + if (!need_double) { + need_double = true; + dv = lv; + } + is_date = true; + dv *= ucl_lex_time_multiplier (*p); + p ++; + goto set_obj; + } + break; + } + } + + *pos = c; + return EINVAL; + + set_obj: + if (allow_double && (need_double || is_date)) { + if (!is_date) { + obj->type = UCL_FLOAT; + } + else { + obj->type = UCL_TIME; + } + obj->value.dv = dv; + } + else { + obj->type = UCL_INT; + obj->value.iv = lv; + } + *pos = p; + return 0; +} + +/** + * Parse possible number + * @param parser + * @param chunk + * @param err + * @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, UT_string **err) +{ + const unsigned char *pos; + int ret; + + ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos, true); + + 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", err); + } + + return false; +} + +/** + * Parse quoted string with possible escapes + * @param parser + * @param chunk + * @param err + * @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, UT_string **err) +{ + 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", err); + } + else { + ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", 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", 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", err); + return false; + } + ucl_chunk_skipc (chunk, p); + } + if (p >= chunk->end) { + ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", err); + return false; + } + } + else { + ucl_chunk_skipc (chunk, p); + } + } + else { + ucl_set_err (chunk, UCL_ESYNTAX, "invalid escape character", err); + return false; + } + *need_unescape = true; + continue; + } + else if (c == '"') { + ucl_chunk_skipc (chunk, p); + return true; + } + ucl_chunk_skipc (chunk, p); + } + + ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", err); + return false; +} + +/** + * Parse a key in an object + * @param parser + * @param chunk + * @param err + * @return true if a key has been parsed + */ +static bool +ucl_parse_key (struct ucl_parser *parser, + struct ucl_chunk *chunk, UT_string **err) +{ + const unsigned char *p, *c = NULL, *end; + const char *key; + bool got_quote = false, got_eq = false, got_semicolon = false, need_unescape = false; + ucl_object_t *nobj, *tobj, *container; + size_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 (ucl_lex_is_comment (p[0], p[1])) { + if (!ucl_skip_comments (parser, err)) { + return false; + } + p = chunk->pos; + } + else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) { + /* The first symbol */ + c = p; + ucl_chunk_skipc (chunk, p); + } + else if (*p == '"') { + /* JSON style key */ + c = p + 1; + got_quote = true; + ucl_chunk_skipc (chunk, p); + } + else { + /* Invalid identifier */ + ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", err); + return false; + } + } + else { + /* Parse the body of a key */ + if (!got_quote) { + if (ucl_test_character (*p, UCL_CHARACTER_KEY)) { + 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", err); + return false; + } + } + else { + /* We need to parse json like quoted string */ + if (!ucl_lex_json_string (parser, chunk, &need_unescape, err)) { + return false; + } + end = chunk->pos - 1; + p = chunk->pos; + break; + } + } + } + + if (p >= chunk->end) { + ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", err); + return 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", 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", err); + return false; + } + } + else if (ucl_lex_is_comment (p[0], p[1])) { + /* Check for comment */ + if (!ucl_skip_comments (parser, err)) { + return false; + } + p = chunk->pos; + } + else { + /* Start value */ + break; + } + } + + if (p >= chunk->end) { + ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", err); + return false; + } + + /* 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_FLAG_KEY_LOWERCASE, err); + if (keylen == 0) { + return false; + } + + container = parser->stack->obj->value.ov; + HASH_FIND (hh, container, key, keylen, tobj); + if (tobj == NULL) { + DL_APPEND (tobj, nobj); + HASH_ADD_KEYPTR (hh, container, key, keylen, nobj); + } + else { + DL_APPEND (tobj, nobj); + } + + parser->stack->obj->value.ov = container; + + parser->cur_obj = nobj; + + return true; +} + +/** + * Parse a cl string + * @param parser + * @param chunk + * @param err + * @return true if a key has been parsed + */ +static bool +ucl_parse_string_value (struct ucl_parser *parser, + struct ucl_chunk *chunk, UT_string **err) +{ + 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; + } + } + + if (ucl_lex_is_atom_end (*p) || 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", err); + return false; + } + + return true; +} + +/** + * Parse multiline string ending with \n{term}\n + * @param parser + * @param chunk + * @param term + * @param term_len + * @param err + * @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, UT_string **err) +{ + 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 { + newline = false; + } + ucl_chunk_skipc (chunk, p); + } + + return len; +} + +/** + * Handle value data + * @param parser + * @param chunk + * @param err + * @return + */ +static bool +ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk, UT_string **err) +{ + const unsigned char *p, *c; + struct ucl_stack *st; + ucl_object_t *obj = NULL, *t; + unsigned int stripped_spaces; + int str_len; + bool need_unescape = false; + + p = chunk->pos; + + while (p < chunk->end) { + if (obj == NULL) { + if (parser->stack->obj->type == UCL_ARRAY) { + /* Object must be allocated */ + obj = ucl_object_new (); + t = parser->stack->obj->value.ov; + DL_APPEND (t, obj); + parser->cur_obj = obj; + parser->stack->obj->value.ov = t; + } + else { + /* Object has been already allocated */ + obj = parser->cur_obj; + } + } + c = p; + switch (*p) { + case '"': + ucl_chunk_skipc (chunk, p); + if (!ucl_lex_json_string (parser, chunk, &need_unescape, err)) { + 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, err)) == 0) { + return false; + } + obj->len = str_len; + parser->state = UCL_STATE_AFTER_VALUE; + p = chunk->pos; + return true; + break; + case '{': + /* We have a new object */ + obj->type = UCL_OBJECT; + + parser->state = UCL_STATE_KEY; + st = UCL_ALLOC (sizeof (struct ucl_stack)); + st->obj = obj; + LL_PREPEND (parser->stack, st); + parser->cur_obj = obj; + + ucl_chunk_skipc (chunk, p); + return true; + break; + case '[': + /* We have a new array */ + obj = parser->cur_obj; + obj->type = UCL_ARRAY; + + parser->state = UCL_STATE_VALUE; + st = UCL_ALLOC (sizeof (struct ucl_stack)); + st->obj = obj; + LL_PREPEND (parser->stack, st); + parser->cur_obj = obj; + + ucl_chunk_skipc (chunk, p); + return true; + break; + case '<': + /* 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, err)) == 0) { + ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", 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, err)) == 0) { + return false; + } + obj->len = str_len; + parser->state = UCL_STATE_AFTER_VALUE; + return true; + } + } + } + /* Fallback to ordinary strings */ + default: + /* Skip any spaces and comments */ + if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) || + 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, err)) { + return false; + } + p = chunk->pos; + continue; + } + /* Parse atom */ + if (ucl_test_character (*p, UCL_CHARACTER_VALUE_DIGIT_START)) { + if (!ucl_lex_number (parser, chunk, obj, err)) { + if (parser->state == UCL_STATE_ERROR) { + return false; + } + if (!ucl_parse_string_value (parser, chunk, err)) { + return false; + } + if (!ucl_maybe_parse_boolean (obj, c, chunk->pos - c)) { + /* 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", 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, false, false, err)) == 0) { + return false; + } + obj->len = str_len; + } + parser->state = UCL_STATE_AFTER_VALUE; + return true; + } + else { + parser->state = UCL_STATE_AFTER_VALUE; + return true; + } + } + else { + if (!ucl_parse_string_value (parser, chunk, err)) { + return false; + } + if (!ucl_maybe_parse_boolean (obj, c, chunk->pos - c)) { + /* TODO: remove cut&paste */ + /* 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", 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, false, false, err)) == 0) { + return false; + } + obj->len = str_len; + } + parser->state = UCL_STATE_AFTER_VALUE; + return true; + } + p = chunk->pos; + break; + } + } + + return true; +} + +/** + * Handle after value data + * @param parser + * @param chunk + * @param err + * @return + */ +static bool +ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk, UT_string **err) +{ + 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 (ucl_lex_is_comment (p[0], p[1])) { + /* Skip comment */ + if (!ucl_skip_comments (parser, err)) { + 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, "unexpected } detected", err); + return false; + } + if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) || + (*p == ']' && parser->stack->obj->type == UCL_ARRAY)) { + /* Pop object from a stack */ + + st = parser->stack; + parser->stack = st->next; + UCL_FREE (sizeof (struct ucl_stack), st); + } + else { + ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", 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", err); + return false; + } + return true; + } + } + + return true; +} + +/** + * Handle macro data + * @param parser + * @param chunk + * @param err + * @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, UT_string **err) +{ + const unsigned char *p, *c; + bool need_unescape = 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, err)) { + 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 + * @param err if *err is NULL it is set to parser error + * @return true if chunk has been parsed and false in case of error + */ +static bool +ucl_state_machine (struct ucl_parser *parser, UT_string **err) +{ + ucl_object_t *obj; + struct ucl_chunk *chunk = parser->chunks; + struct ucl_stack *st; + const unsigned char *p, *c, *macro_start = NULL; + size_t macro_len = 0; + struct ucl_macro *macro = NULL; + + 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 + */ + if (!ucl_skip_comments (parser, err)) { + parser->prev_state = parser->state; + parser->state = UCL_STATE_ERROR; + return false; + } + else { + p = chunk->pos; + obj = ucl_object_new (); + if (*p == '[') { + parser->state = UCL_STATE_VALUE; + obj->type = UCL_ARRAY; + ucl_chunk_skipc (chunk, p); + } + else { + parser->state = UCL_STATE_KEY; + obj->type = UCL_OBJECT; + if (*p == '{') { + ucl_chunk_skipc (chunk, p); + } + }; + parser->cur_obj = obj; + parser->top_obj = obj; + st = UCL_ALLOC (sizeof (struct ucl_stack)); + st->obj = obj; + LL_PREPEND (parser->stack, st); + } + 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 (!ucl_parse_key (parser, chunk, err)) { + parser->prev_state = parser->state; + parser->state = UCL_STATE_ERROR; + return false; + } + if (parser->state != UCL_STATE_MACRO_NAME) { + 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, err)) { + 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, err)) { + 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 */ + HASH_FIND (hh, parser->macroes, c, (p - c), macro); + if (macro == NULL) { + ucl_set_err (chunk, UCL_EMACRO, "unknown macro", err); + 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 (ucl_lex_is_comment (p[0], p[1])) { + /* Skip comment */ + if (!ucl_skip_comments (parser, err)) { + 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, err)) { + parser->prev_state = parser->state; + parser->state = UCL_STATE_ERROR; + return false; + } + parser->state = parser->prev_state; + if (!macro->handler (macro_start, macro_len, macro->ud, err)) { + return false; + } + p = chunk->pos; + break; + default: + /* TODO: add all states */ + ucl_set_err (chunk, UCL_EMACRO, "internal error: parser is in an unknown state", 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)); + memset (new, 0, sizeof (struct ucl_parser)); + + ucl_parser_register_macro (new, "include", ucl_include_handler, new); + ucl_parser_register_macro (new, "includes", ucl_includes_handler, new); + + new->flags = flags; + + return new; +} + + +void +ucl_parser_register_macro (struct ucl_parser *parser, const char *macro, + ucl_macro_handler handler, void* ud) +{ + struct ucl_macro *new; + + new = UCL_ALLOC (sizeof (struct ucl_macro)); + 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); +} + +bool +ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data, + size_t len, UT_string **err) +{ + struct ucl_chunk *chunk; + + if (parser->state != UCL_STATE_ERROR) { + chunk = UCL_ALLOC (sizeof (struct ucl_chunk)); + 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 (err, "maximum include nesting limit is reached: %d", + parser->recursion); + return false; + } + return ucl_state_machine (parser, err); + } + + ucl_create_err (err, "a parser is in an invalid state"); + + return false; +} diff --git a/src/ucl/src/ucl_util.c b/src/ucl/src/ucl_util.c new file mode 100644 index 000000000..510035c55 --- /dev/null +++ b/src/ucl/src/ucl_util.c @@ -0,0 +1,841 @@ +/* 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_OPENSSL +#include <openssl/err.h> +#include <openssl/sha.h> +#include <openssl/rsa.h> +#include <openssl/ssl.h> +#include <openssl/evp.h> +#endif + +/** + * @file rcl_util.c + * Utilities for rcl parsing + */ + + +static void +ucl_object_free_internal (ucl_object_t *obj, bool allow_rec) +{ + ucl_object_t *sub, *tmp; + + while (obj != NULL) { + 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]); + } + + if (obj->type == UCL_ARRAY) { + sub = obj->value.ov; + while (sub != NULL) { + tmp = sub->next; + ucl_object_free_internal (sub, false); + sub = tmp; + } + } + else if (obj->type == UCL_OBJECT) { + HASH_ITER (hh, obj->value.ov, sub, tmp) { + HASH_DELETE (hh, obj->value.ov, sub); + ucl_object_free_internal (sub, true); + } + } + tmp = obj->next; + UCL_FREE (sizeof (ucl_object_t), obj); + obj = tmp; + + if (!allow_rec) { + break; + } + } +} + +void +ucl_obj_free (ucl_object_t *obj) +{ + ucl_object_free_internal (obj, true); +} + +size_t +ucl_unescape_json_string (char *str, size_t len) +{ + char *t = str, *h = str; + int i, uval; + + /* 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; + 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; + } + } + 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++ = '?'; + } + break; + default: + *t++ = '?'; + break; + } + h ++; + len --; + } + else { + *t++ = *h++; + } + len --; + } + *t = '\0'; + + return (t - str); +} + +char * +ucl_copy_key_trash (ucl_object_t *obj) +{ + if (obj->trash_stack[UCL_TRASH_KEY] == NULL && obj->hh.key != NULL) { + obj->trash_stack[UCL_TRASH_KEY] = malloc (obj->hh.keylen + 1); + if (obj->trash_stack[UCL_TRASH_KEY] != NULL) { + memcpy (obj->trash_stack[UCL_TRASH_KEY], obj->hh.key, obj->hh.keylen); + obj->trash_stack[UCL_TRASH_KEY][obj->hh.keylen] = '\0'; + } + obj->hh.key = obj->trash_stack[UCL_TRASH_KEY]; + } + + return obj->trash_stack[UCL_TRASH_KEY]; +} + +char * +ucl_copy_value_trash (ucl_object_t *obj) +{ + UT_string *emitted; + if (obj->trash_stack[UCL_TRASH_VALUE] == NULL) { + if (obj->type == UCL_STRING) { + /* Special case for strings */ + obj->trash_stack[UCL_TRASH_VALUE] = malloc (obj->len + 1); + if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) { + memcpy (obj->trash_stack[UCL_TRASH_VALUE], obj->value.sv, obj->len); + obj->trash_stack[UCL_TRASH_VALUE][obj->len] = '\0'; + } + } + else { + /* Just emit value in json notation */ + utstring_new (emitted); + + if (emitted != NULL) { + ucl_elt_write_json (obj, emitted, 0, 0, true); + obj->trash_stack[UCL_TRASH_VALUE] = emitted->d; + obj->len = emitted->i; + free (emitted); + } + } + } + return obj->trash_stack[UCL_TRASH_VALUE]; +} + +ucl_object_t* +ucl_parser_get_object (struct ucl_parser *parser, UT_string **err) +{ + if (parser->state != UCL_STATE_INIT && parser->state != UCL_STATE_ERROR) { + return ucl_object_ref (parser->top_obj); + } + + return NULL; +} + +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; + + 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); + } + + UCL_FREE (sizeof (struct ucl_parser), parser); +} + +bool +ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len, UT_string **err) +{ + struct ucl_pubkey *nkey; +#ifndef HAVE_OPENSSL + ucl_create_err (err, "cannot check signatures without openssl"); + return false; +#else +# if (OPENSSL_VERSION_NUMBER < 0x10000000L) + ucl_create_err (err, "cannot check signatures, openssl version is unsupported"); + return EXIT_FAILURE; +# else + BIO *mem; + + mem = BIO_new_mem_buf ((void *)key, len); + nkey = UCL_ALLOC (sizeof (struct ucl_pubkey)); + 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 (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 = g_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) +{ + +#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) { + ucl_create_err (err, "cannot fetch URL %s: %s", + url, strerror (errno)); + fetchFreeURL (fetch_url); + return false; + } + + *buflen = us.size; + *buf = g_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) { + ucl_create_err (err, "error fetching URL %s: %s", + url, curl_easy_strerror (r)); + curl_easy_cleanup (curl); + if (buf != NULL) { + free (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) +{ + int fd; + struct stat st; + + if (stat (filename, &st) == -1) { + ucl_create_err (err, "cannot stat file %s: %s", + filename, strerror (errno)); + return false; + } + if ((fd = open (filename, O_RDONLY)) == -1) { + ucl_create_err (err, "cannot open file %s: %s", + filename, strerror (errno)); + return false; + } + if ((*buf = 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, UT_string **err) +{ + + bool res; + unsigned char *buf = NULL, *sigbuf = NULL; + size_t buflen = 0, siglen = 0; + struct ucl_chunk *chunk; + char urlbuf[PATH_MAX]; + + snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data); + + if (!ucl_fetch_url (urlbuf, &buf, &buflen, err)) { + return false; + } + + if (check_signature) { +#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) + /* We need to check signature first */ + snprintf (urlbuf, sizeof (urlbuf), "%.*s.sig", (int)len, data); + if (!ucl_fetch_file (urlbuf, &sigbuf, &siglen, err)) { + return false; + } + if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) { + ucl_create_err (err, "cannot verify url %s: %s", + urlbuf, + ERR_error_string (ERR_get_error (), NULL)); + munmap (sigbuf, siglen); + return false; + } + munmap (sigbuf, siglen); +#endif + } + + res = ucl_parser_add_chunk (parser, buf, buflen, err); + 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); + } + } + 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, UT_string **err) +{ + bool res; + struct ucl_chunk *chunk; + unsigned char *buf = NULL, *sigbuf = NULL; + size_t buflen, siglen; + char filebuf[PATH_MAX], realbuf[PATH_MAX]; + + snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data); + if (realpath (filebuf, realbuf) == NULL) { + ucl_create_err (err, "cannot open file %s: %s", + filebuf, + strerror (errno)); + return false; + } + + if (!ucl_fetch_file (realbuf, &buf, &buflen, err)) { + return false; + } + + if (check_signature) { +#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L) + /* We need to check signature first */ + snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf); + if (!ucl_fetch_file (filebuf, &sigbuf, &siglen, err)) { + return false; + } + if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) { + ucl_create_err (err, "cannot verify file %s: %s", + filebuf, + ERR_error_string (ERR_get_error (), NULL)); + munmap (sigbuf, siglen); + return false; + } + munmap (sigbuf, siglen); +#endif + } + + res = ucl_parser_add_chunk (parser, buf, buflen, err); + 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); + } + } + 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 + */ +bool +ucl_include_handler (const unsigned char *data, size_t len, void* ud, UT_string **err) +{ + struct ucl_parser *parser = ud; + + if (*data == '/' || *data == '.') { + /* Try to load a file */ + return ucl_include_file (data, len, parser, false, err); + } + + return ucl_include_url (data, len, parser, false, err); +} + +/** + * 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, UT_string **err) +{ + struct ucl_parser *parser = ud; + + if (*data == '/' || *data == '.') { + /* Try to load a file */ + return ucl_include_file (data, len, parser, true, err); + } + + return ucl_include_url (data, len, parser, true, err); +} + +bool +ucl_parser_add_file (struct ucl_parser *parser, const char *filename, + UT_string **err) +{ + unsigned char *buf; + size_t len; + bool ret; + + if (!ucl_fetch_file (filename, &buf, &len, err)) { + return false; + } + + ret = ucl_parser_add_chunk (parser, buf, len, err); + + 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); + } + } + else { + ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos, + flags & UCL_STRING_PARSE_DOUBLE); + } + } + } + + return obj; +} diff --git a/src/ucl/tests/1.in b/src/ucl/tests/1.in new file mode 100644 index 000000000..41b0cfb3c --- /dev/null +++ b/src/ucl/tests/1.in @@ -0,0 +1,11 @@ +{ +"key1": value; +"key1": value2; +"key1": "value;" +"key1": 1.0, +"key1": -1e-10, +"key1": 1 +"key1": true +"key1": no +"key1": yes +} diff --git a/src/ucl/tests/1.res b/src/ucl/tests/1.res new file mode 100644 index 000000000..789c82769 --- /dev/null +++ b/src/ucl/tests/1.res @@ -0,0 +1,12 @@ +key1 [ + "value", + "value2", + "value;", + 1.0, + -1e-10, + 1, + true, + false, + true, +] + diff --git a/src/ucl/tests/2.in b/src/ucl/tests/2.in new file mode 100644 index 000000000..59a4d0c38 --- /dev/null +++ b/src/ucl/tests/2.in @@ -0,0 +1,18 @@ +section1 { param1 = value; param2 = value, +section3 {param = value; param2 = value, param3 = ["value1", value2, 100500]}} +section2 { param1 = {key = value}, param1 = ["key"]} + +# Numbers +key1 = 1s +key2 = 1min +key3 = 1kb +key4 = 5M +key5 = 10mS +key6 = 10y + +# Strings +key1 = "some string"; +key2 = /some/path; +key3 = 111some, +key4: s1, +"key5": "\n\r123" diff --git a/src/ucl/tests/2.res b/src/ucl/tests/2.res new file mode 100644 index 000000000..3a8966f65 --- /dev/null +++ b/src/ucl/tests/2.res @@ -0,0 +1,45 @@ +section1 { + param1 = "value"; + param2 = "value"; + section3 { + param = "value"; + param2 = "value"; + param3 [ + "value1", + "value2", + 100500, + ] + } +} +section2 { + param1 [ + { + key = "value"; + }, + [ + "key", + ], + ] +} +key1 [ + 1.0, + "some string", +] +key2 [ + 60.0, + "/some/path", +] +key3 [ + 1024, + "111some", +] +key4 [ + 5000000, + "s1", +] +key5 [ + 0.010000, + "\n\r123", +] +key6 = 2207520000.000000; + diff --git a/src/ucl/tests/3.in b/src/ucl/tests/3.in new file mode 100644 index 000000000..b3e369670 --- /dev/null +++ b/src/ucl/tests/3.in @@ -0,0 +1,31 @@ +/* + * Pkg conf + */ + +#packagesite http//pkg.freebsd.org/freebsd-9-amd64/latest +#packagesite http//pkg.freebsd.org/freebsd-9-amd64/latest +packagesite: http://pkg-test.freebsd.org/pkg-test/${ABI}/latest +squaretest: some[]value +ALIAS : { + all-depends: query %dn-%dv, + annotations: info -A, + build-depends: info -qd, + download: fetch, + iinfo: info -i -g -x, + isearch: search -i -g -x, + leaf: query -e '%a == 0' '%n-%v', + leaf: query -e '%a == 0' '%n-%v', + list: info -ql, + origin: info -qo, + provided-depends: info -qb, + raw: info -R, + required-depends: info -qr, + shared-depends: info -qB, + show: info -f -k, + size: info -sq, + } + +repo_dirs : [ + /home/bapt, + /usr/local/etc +] diff --git a/src/ucl/tests/3.res b/src/ucl/tests/3.res new file mode 100644 index 000000000..2f84ed69e --- /dev/null +++ b/src/ucl/tests/3.res @@ -0,0 +1,27 @@ +packagesite = "http://pkg-test.freebsd.org/pkg-test/${ABI}/latest"; +squaretest = "some[]value"; +alias { + all-depends = "query %dn-%dv"; + annotations = "info -A"; + build-depends = "info -qd"; + download = "fetch"; + iinfo = "info -i -g -x"; + isearch = "search -i -g -x"; + leaf [ + "query -e '%a == 0' '%n-%v'", + "query -e '%a == 0' '%n-%v'", + ] + list = "info -ql"; + origin = "info -qo"; + provided-depends = "info -qb"; + raw = "info -R"; + required-depends = "info -qr"; + shared-depends = "info -qB"; + show = "info -f -k"; + size = "info -sq"; +} +repo_dirs [ + "/home/bapt", + "/usr/local/etc", +] + diff --git a/src/ucl/tests/4.in b/src/ucl/tests/4.in new file mode 100644 index 000000000..2b296efff --- /dev/null +++ b/src/ucl/tests/4.in @@ -0,0 +1,47 @@ +name : "pkgconf" +version : "0.9.3" +origin : "devel/pkgconf" +comment : "Utility to help to configure compiler and linker flags" +arch : "freebsd:9:x86:64" +maintainer : "bapt@FreeBSD.org" +prefix : "/usr/local" +licenselogic : "single" +licenses : [ + "BSD", +] +flatsize : 60523 +desc : "pkgconf is a program which helps to configure compiler and linker flags for\ndevelopment frameworks. It is similar to pkg-config, but was written from\nscratch in Summer of 2011 to replace pkg-config, which now needs itself to build\nitself.\n\nWWW: https://github.com/pkgconf/pkgconf" +categories : [ + "devel", +] +files : { + /usr/local/bin/pkg-config : "-", + /usr/local/bin/pkgconf : "4a0fc53e5ad64e8085da2e61652d61c50b192a086421d865703f1de9f724da38", + /usr/local/share/aclocal/pkg.m4 : "cffab33d659adfe36497ec57665eec36fa6fb7b007e578e6ac2434cc28be8820", + /usr/local/share/licenses/pkgconf-0.9.3/BSD : "85e7a53b5e2d3e350e2d084fed2f94b7f63005f8e1168740e1e84aa9fa5d48ce", + /usr/local/share/licenses/pkgconf-0.9.3/LICENSE : "d9cce0db43502eb1bd8fbef7e960cfaa43b5647186f7f7379923b336209fd77b", + /usr/local/share/licenses/pkgconf-0.9.3/catalog.mk : "e7b131acce7c3d3c61f2214607b11b34526e03b05afe89a608f50586a898e2ef", +} +directories : { + /usr/local/share/licenses/pkgconf-0.9.3/ : false, + /usr/local/share/licenses/ : true, +} +scripts : { + post-install : "cd /usr/local\nn", + pre-deinstall : "cd /usr/local\nn", + post-deinstall : "cd /usr/local\nn", +} +multiline-key : <<EOD +test +test +test\n +/* comment like */ +# Some invalid endings + EOD +EOD +EOF +# Valid ending + empty string + +EOD + +normal-key : <<EODnot diff --git a/src/ucl/tests/4.res b/src/ucl/tests/4.res new file mode 100644 index 000000000..58c359943 --- /dev/null +++ b/src/ucl/tests/4.res @@ -0,0 +1,36 @@ +name = "pkgconf"; +version = "0.9.3"; +origin = "devel/pkgconf"; +comment = "Utility to help to configure compiler and linker flags"; +arch = "freebsd:9:x86:64"; +maintainer = "bapt@FreeBSD.org"; +prefix = "/usr/local"; +licenselogic = "single"; +licenses [ + "BSD", +] +flatsize = 60523; +desc = "pkgconf is a program which helps to configure compiler and linker flags for\ndevelopment frameworks. It is similar to pkg-config, but was written from\nscratch in Summer of 2011 to replace pkg-config, which now needs itself to build\nitself.\n\nWWW: https://github.com/pkgconf/pkgconf"; +categories [ + "devel", +] +files { + /usr/local/bin/pkg-config = "-"; + /usr/local/bin/pkgconf = "4a0fc53e5ad64e8085da2e61652d61c50b192a086421d865703f1de9f724da38"; + /usr/local/share/aclocal/pkg.m4 = "cffab33d659adfe36497ec57665eec36fa6fb7b007e578e6ac2434cc28be8820"; + /usr/local/share/licenses/pkgconf-0.9.3/bsd = "85e7a53b5e2d3e350e2d084fed2f94b7f63005f8e1168740e1e84aa9fa5d48ce"; + /usr/local/share/licenses/pkgconf-0.9.3/license = "d9cce0db43502eb1bd8fbef7e960cfaa43b5647186f7f7379923b336209fd77b"; + /usr/local/share/licenses/pkgconf-0.9.3/catalog.mk = "e7b131acce7c3d3c61f2214607b11b34526e03b05afe89a608f50586a898e2ef"; +} +directories { + /usr/local/share/licenses/pkgconf-0.9.3/ = false; + /usr/local/share/licenses/ = true; +} +scripts { + post-install = "cd /usr/local\nn"; + pre-deinstall = "cd /usr/local\nn"; + post-deinstall = "cd /usr/local\nn"; +} +multiline-key = "test\ntest\ntest\\n\n/* comment like */\n# Some invalid endings\n EOD\nEOD \nEOF\n# Valid ending + empty string\n"; +normal-key = "<<EODnot"; + diff --git a/src/ucl/tests/generate.res b/src/ucl/tests/generate.res new file mode 100644 index 000000000..e7659ec3a --- /dev/null +++ b/src/ucl/tests/generate.res @@ -0,0 +1,18 @@ +key1 = "test string"; +key2 = "test \\nstring"; +key3 = " test string \n"; +key4 [ + [ + 10, + 10.100000, + ], + true, +] +key5 = ""; +key6 = ""; +key7 = " \\n"; +key8 = 1048576; +key9 = 3.140000; +key10 = true; +key11 = false; + diff --git a/src/ucl/tests/rcl_test.json.xz b/src/ucl/tests/rcl_test.json.xz Binary files differnew file mode 100644 index 000000000..98c3559ad --- /dev/null +++ b/src/ucl/tests/rcl_test.json.xz diff --git a/src/ucl/tests/run_tests.sh b/src/ucl/tests/run_tests.sh new file mode 100755 index 000000000..71e6f1b08 --- /dev/null +++ b/src/ucl/tests/run_tests.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +if [ $# -lt 1 ] ; then + echo 'Specify binary to run as the first argument' + exit 1 +fi + + +for _tin in ${TEST_DIR}/*.in ; do + _t=`echo $_tin | sed -e 's/.in$//'` + $1 $_t.in $_t.out + if [ $? -ne 0 ] ; then + echo "Test: $_t failed, output:" + cat $_t.out + rm $_t.out + exit 1 + fi + if [ -f $_t.res ] ; then + diff -s $_t.out $_t.res -u 2>/dev/null + if [ $? -ne 0 ] ; then + rm $_t.out + echo "Test: $_t output missmatch" + exit 1 + fi + fi + rm $_t.out +done + +if [ $# -gt 2 ] ; then + $3 ${TEST_DIR}/generate.out + diff -s ${TEST_DIR}/generate.out ${TEST_DIR}/generate.res -u 2>/dev/null + if [ $? -ne 0 ] ; then + rm ${TEST_DIR}/generate.out + echo "Test: generate.res output missmatch" + exit 1 + fi + rm ${TEST_DIR}/generate.out +fi + +if [ $# -gt 1 -a -x "/usr/bin/xz" ] ; then + echo 'Running speed tests' + for _tin in ${TEST_DIR}/*.xz ; do + echo "Unpacking $_tin..." + xz -cd < $_tin > ${TEST_DIR}/test_file + # Preread file to cheat benchmark! + cat ${TEST_DIR}/test_file > /dev/null + echo "Starting benchmarking for $_tin..." + $2 ${TEST_DIR}/test_file + if [ $? -ne 0 ] ; then + echo "Test: $_tin failed" + rm ${TEST_DIR}/test_file + exit 1 + fi + rm ${TEST_DIR}/test_file + done +fi + diff --git a/src/ucl/tests/test_basic.c b/src/ucl/tests/test_basic.c new file mode 100644 index 000000000..d4d4786d6 --- /dev/null +++ b/src/ucl/tests/test_basic.c @@ -0,0 +1,113 @@ +/* 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 <stdio.h> +#include <errno.h> +#include "ucl.h" + +int +main (int argc, char **argv) +{ + char inbuf[8192]; + struct ucl_parser *parser, *parser2; + ucl_object_t *obj; + FILE *in, *out; + UT_string *err = NULL; + unsigned char *emitted; + const char *fname_in = NULL, *fname_out = NULL; + int ret = 0; + + switch (argc) { + case 2: + fname_in = argv[1]; + break; + case 3: + fname_in = argv[1]; + fname_out = argv[2]; + break; + } + + if (fname_in != NULL) { + in = fopen (fname_in, "r"); + if (in == NULL) { + exit (-errno); + } + } + else { + in = stdin; + } + parser = ucl_parser_new (UCL_FLAG_KEY_LOWERCASE); + + while (!feof (in)) { + fread (inbuf, sizeof (inbuf), 1, in); + ucl_parser_add_chunk (parser, inbuf, strlen (inbuf), &err); + } + fclose (in); + + if (fname_out != NULL) { + out = fopen (fname_out, "w"); + if (out == NULL) { + exit (-errno); + } + } + else { + out = stdout; + } + if (err != NULL) { + fprintf (out, "Error occurred: %s\n", err->d); + ret = 1; + goto end; + } + obj = ucl_parser_get_object (parser, &err); + emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG); + ucl_parser_free (parser); + ucl_object_unref (obj); + parser2 = ucl_parser_new (UCL_FLAG_KEY_LOWERCASE); + ucl_parser_add_chunk (parser2, emitted, strlen (emitted), &err); + + if (err != NULL) { + fprintf (out, "Error occurred: %s\n", err->d); + fprintf (out, "%s\n", emitted); + ret = 1; + goto end; + } + if (emitted != NULL) { + free (emitted); + } + obj = ucl_parser_get_object (parser2, &err); + emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG); + + fprintf (out, "%s\n", emitted); + ucl_object_unref (obj); + +end: + if (emitted != NULL) { + free (emitted); + } + if (parser2 != NULL) { + ucl_parser_free (parser2); + } + fclose (out); + + return ret; +} diff --git a/src/ucl/tests/test_generate.c b/src/ucl/tests/test_generate.c new file mode 100644 index 000000000..a61b35396 --- /dev/null +++ b/src/ucl/tests/test_generate.c @@ -0,0 +1,98 @@ +/* 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 <stdio.h> +#include <errno.h> +#include "ucl.h" + +int +main (int argc, char **argv) +{ + ucl_object_t *obj, *cur, *ar; + 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_new (); + /* Create some strings */ + cur = ucl_object_fromstring_common (" test string ", 0, UCL_STRING_TRIM); + obj = ucl_object_insert_key (obj, cur, "key1", 0, false); + cur = ucl_object_fromstring_common (" test \nstring\n ", 0, UCL_STRING_TRIM | UCL_STRING_ESCAPE); + obj = ucl_object_insert_key (obj, cur, "key2", 0, false); + cur = ucl_object_fromstring_common (" test string \n", 0, 0); + obj = ucl_object_insert_key (obj, cur, "key3", 0, false); + /* Array of numbers */ + cur = ucl_object_fromint (10); + ar = ucl_array_append (NULL, cur); + cur = ucl_object_fromdouble (10.1); + ar = ucl_array_append (ar, cur); + obj = ucl_object_insert_key (obj, ar, "key4", 0, false); + cur = ucl_object_frombool (true); + obj = ucl_object_insert_key (obj, cur, "key4", 0, false); + /* Empty strings */ + cur = ucl_object_fromstring_common (" ", 0, UCL_STRING_TRIM); + obj = ucl_object_insert_key (obj, cur, "key5", 0, false); + cur = ucl_object_fromstring_common ("", 0, UCL_STRING_ESCAPE); + obj = ucl_object_insert_key (obj, cur, "key6", 0, false); + cur = ucl_object_fromstring_common (" \n", 0, UCL_STRING_ESCAPE); + obj = 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); + obj = ucl_object_insert_key (obj, cur, "key8", 0, false); + cur = ucl_object_fromstring_common ("3.14", 0, UCL_STRING_PARSE); + obj = ucl_object_insert_key (obj, cur, "key9", 0, false); + cur = ucl_object_fromstring_common ("true", 0, UCL_STRING_PARSE); + obj = ucl_object_insert_key (obj, cur, "key10", 0, false); + cur = ucl_object_fromstring_common (" off ", 0, UCL_STRING_PARSE | UCL_STRING_TRIM); + obj = ucl_object_insert_key (obj, cur, "key11", 0, false); + + emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG); + + fprintf (out, "%s\n", emitted); + ucl_object_unref (obj); + + if (emitted != NULL) { + free (emitted); + } + fclose (out); + + return ret; +} diff --git a/src/ucl/tests/test_speed.c b/src/ucl/tests/test_speed.c new file mode 100644 index 000000000..92a972aa5 --- /dev/null +++ b/src/ucl/tests/test_speed.c @@ -0,0 +1,128 @@ +/* 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 <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "ucl.h" + +int +main (int argc, char **argv) +{ + void *map; + struct ucl_parser *parser; + ucl_object_t *obj; + int fin; + UT_string *err = NULL; + unsigned char *emitted; + struct stat st; + const char *fname_in = NULL; + int ret = 0; + struct timespec start, end; + double seconds; + + switch (argc) { + case 2: + fname_in = argv[1]; + break; + } + + fin = open (fname_in, O_RDONLY); + if (fin == -1) { + perror ("open failed"); + exit (EXIT_FAILURE); + } + parser = ucl_parser_new (UCL_FLAG_ZEROCOPY); + + (void)fstat (fin, &st); + map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fin, 0); + if (map == MAP_FAILED) { + perror ("mmap failed"); + exit (EXIT_FAILURE); + } + + close (fin); + + clock_gettime (CLOCK_MONOTONIC, &start); + ucl_parser_add_chunk (parser, map, st.st_size, &err); + + obj = ucl_parser_get_object (parser, &err); + clock_gettime (CLOCK_MONOTONIC, &end); + + seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.; + printf ("ucl: parsed input in %.4f seconds\n", seconds); + if (err != NULL) { + printf ("Error occurred: %s\n", err->d); + ret = 1; + } + + clock_gettime (CLOCK_MONOTONIC, &start); + emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG); + clock_gettime (CLOCK_MONOTONIC, &end); + + seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.; + printf ("ucl: emitted config in %.4f seconds\n", seconds); + + free (emitted); + + clock_gettime (CLOCK_MONOTONIC, &start); + emitted = ucl_object_emit (obj, UCL_EMIT_JSON); + clock_gettime (CLOCK_MONOTONIC, &end); + + seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.; + printf ("ucl: emitted json in %.4f seconds\n", seconds); + + free (emitted); + + clock_gettime (CLOCK_MONOTONIC, &start); + emitted = ucl_object_emit (obj, UCL_EMIT_JSON_COMPACT); + clock_gettime (CLOCK_MONOTONIC, &end); + + seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.; + printf ("ucl: emitted compact json in %.4f seconds\n", seconds); + + free (emitted); + + clock_gettime (CLOCK_MONOTONIC, &start); + emitted = ucl_object_emit (obj, UCL_EMIT_YAML); + clock_gettime (CLOCK_MONOTONIC, &end); + + seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.; + printf ("ucl: emitted yaml in %.4f seconds\n", seconds); + + free (emitted); + + ucl_parser_free (parser); + ucl_object_unref (obj); + + munmap (map, st.st_size); + + return ret; +} diff --git a/src/ucl/utils/chargen.c b/src/ucl/utils/chargen.c new file mode 100644 index 000000000..724b37899 --- /dev/null +++ b/src/ucl/utils/chargen.c @@ -0,0 +1,124 @@ +/* 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. + */ + +/** + * @file this utility generates character table for ucl + */ + +#include <stdio.h> +#include <ctype.h> +#include <stdbool.h> + +static inline int +print_flag (const char *flag, bool *need_or, char *val) +{ + int res; + res = sprintf (val, "%s%s", *need_or ? "|" : "", flag); + + *need_or |= true; + + return res; +} + +int +main (int argc, char **argv) +{ + int i, col, r; + const char *name = "ucl_chartable"; + bool need_or; + char valbuf[2048]; + + col = 0; + + if (argc > 1) { + name = argv[1]; + } + + printf ("static const unsigned int %s[255] = {\n", name); + + for (i = 0; i < 255; i ++) { + need_or = false; + r = 0; + /* UCL_CHARACTER_VALUE_END */ + + if (i == ' ' || i == '\t') { + r += print_flag ("UCL_CHARACTER_WHITESPACE", &need_or, valbuf + r); + } + if (isspace (i)) { + r += print_flag ("UCL_CHARACTER_WHITESPACE_UNSAFE", &need_or, valbuf + r); + } + if (isalnum (i) || i >= 0x80 || i == '/' || i == '_') { + r += print_flag ("UCL_CHARACTER_KEY_START", &need_or, valbuf + r); + } + if (isalnum (i) || i == '-' || i == '_' || i == '/' || i == '.' || i >= 0x80) { + r += print_flag ("UCL_CHARACTER_KEY", &need_or, valbuf + r); + } + if (i == 0 || i == '\r' || i == '\n' || i == ']' || i == '}' || i == ';' || i == ',' || i == '#') { + r += print_flag ("UCL_CHARACTER_VALUE_END", &need_or, valbuf + r); + } + else { + if (isprint (i) || i >= 0x80) { + r += print_flag ("UCL_CHARACTER_VALUE_STR", &need_or, valbuf + r); + } + if (isdigit (i) || i == '-') { + r += print_flag ("UCL_CHARACTER_VALUE_DIGIT_START", &need_or, valbuf + r); + } + if (isalnum (i) || i == '.' || i == '-' || i == '+') { + r += print_flag ("UCL_CHARACTER_VALUE_DIGIT", &need_or, valbuf + r); + } + } + if (i == '"' || i == '\\' || i == '/' || i == 'b' || + i == 'f' || i == 'n' || i == 'r' || i == 't' || i == 'u') { + r += print_flag ("UCL_CHARACTER_ESCAPE", &need_or, valbuf + r); + } + if (i == ' ' || i == '\t' || i == ':' || i == '=') { + r += print_flag ("UCL_CHARACTER_KEY_SEP", &need_or, valbuf + r); + } + if (i == '\n' || i == '\r' || i == '\\' || i == '\b' || i == '\t' || + i == '"' || i == '\f') { + r += print_flag ("UCL_CHARACTER_JSON_UNSAFE", &need_or, valbuf + r); + } + + if (!need_or) { + r += print_flag ("UCL_CHARACTER_DENIED", &need_or, valbuf + r); + } + + if (isprint (i)) { + r += sprintf (valbuf + r, " /* %c */", i); + } + if (i != 254) { + r += sprintf (valbuf + r, ", "); + } + col += r; + if (col > 80) { + printf ("\n%s", valbuf); + col = r; + } + else { + printf ("%s", valbuf); + } + } + printf ("\n}\n"); + + return 0; +} diff --git a/src/ucl/utils/objdump.c b/src/ucl/utils/objdump.c new file mode 100644 index 000000000..985106a09 --- /dev/null +++ b/src/ucl/utils/objdump.c @@ -0,0 +1,158 @@ +/* Copyright (c) 2013, Dmitriy V. Reshetnikov + * 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 <stdio.h> +#include <errno.h> + +#include "ucl.h" + +void +ucl_obj_dump(ucl_object_t *obj, unsigned int shift) +{ + int num = shift * 4 + 5; + char *pre = (char *) malloc (num * sizeof(char)); + ucl_object_t *cur, *tmp; + + pre[--num] = 0x00; + while (num--) + pre[num] = 0x20; + + while (obj != NULL ) { + printf ("%sucl object address: %p\n", pre + 4, obj); + if (obj->hh.key != NULL) { + printf ("%skey: \"%s\"\n", pre, ucl_object_key (obj)); + } + printf ("%sref: %d\n", pre, obj->ref); + printf ("%slen: %zd\n", pre, obj->len); + printf ("%sprev: %p\n", pre, obj->prev); + printf ("%snext: %p\n", pre, obj->next); + if (obj->type == UCL_OBJECT) { + printf ("%stype: UCL_OBJECT\n", pre); + printf ("%svalue: %p\n", pre, obj->value.ov); + HASH_ITER (hh, obj->value.ov, cur, tmp) { + ucl_obj_dump (cur, shift + 2); + } + } + else if (obj->type == UCL_ARRAY) { + printf ("%stype: UCL_ARRAY\n", pre); + printf ("%svalue: %p\n", pre, obj->value.ov); + ucl_obj_dump (obj->value.ov, shift + 2); + } + else if (obj->type == UCL_INT) { + printf ("%stype: UCL_INT\n", pre); + printf ("%svalue: %ld\n", pre, obj->value.iv); + } + else if (obj->type == UCL_FLOAT) { + printf ("%stype: UCL_FLOAT\n", pre); + printf ("%svalue: %f\n", pre, obj->value.dv); + } + else if (obj->type == UCL_STRING) { + printf ("%stype: UCL_STRING\n", pre); + printf ("%svalue: \"%s\"\n", pre, ucl_obj_tostring (obj)); + } + else if (obj->type == UCL_BOOLEAN) { + printf ("%stype: UCL_BOOLEAN\n", pre); + printf ("%svalue: %s\n", pre, (obj->value.iv) ? "true" : "false"); + } + else if (obj->type == UCL_TIME) { + printf ("%stype: UCL_TIME\n", pre); + printf ("%svalue: %f\n", pre, obj->value.dv); + } + else if (obj->type == UCL_USERDATA) { + printf ("%stype: UCL_USERDATA\n", pre); + printf ("%svalue: %p\n", pre, obj->value.ud); + } + obj = obj->next; + } + + free (pre); +} + +int +main(int argc, char **argv) +{ + const char *fn = NULL; + char inbuf[8192]; + struct ucl_parser *parser; + UT_string *err = NULL; + int k, ret = 0; + ucl_object_t *obj = NULL; + ucl_object_t *par; + FILE *in; + + if (argc > 1) { + fn = argv[1]; + } + + if (fn != NULL) { + in = fopen (fn, "r"); + if (in == NULL) { + exit (-errno); + } + } + else { + in = stdin; + } + + parser = ucl_parser_new (0); + while (!feof (in)) { + fread (inbuf, sizeof (inbuf), 1, in); + ucl_parser_add_chunk (parser, inbuf, strlen (inbuf), &err); + } + fclose (in); + if (err != NULL ) { + printf ("Error occured: %s\n", err->d); + ret = 1; + goto end; + } + + obj = ucl_parser_get_object (parser, &err); + if (err != NULL ) { + printf ("Error occured: %s\n", err->d); + ret = 1; + goto end; + } + + if (argc > 2) { + for (k = 2; k < argc; k++) { + printf ("search for \"%s\"... ", argv[k]); + par = ucl_obj_get_key (obj, argv[k]); + printf ("%sfound\n", (par == NULL )?"not ":""); + ucl_obj_dump (par, 0); + } + } + else { + ucl_obj_dump (obj, 0); + } + +end: + if (parser != NULL ) { + ucl_parser_free (parser); + } + if (obj != NULL ) { + ucl_obj_unref (obj); + } + + return ret; +} |