summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/librdns/CMakeLists.txt10
-rw-r--r--contrib/librdns/compression.c154
-rw-r--r--contrib/librdns/compression.h45
-rw-r--r--contrib/librdns/curve.c472
-rw-r--r--contrib/librdns/dns_private.h262
-rw-r--r--contrib/librdns/logger.c53
-rw-r--r--contrib/librdns/logger.h42
-rw-r--r--contrib/librdns/packet.c271
-rw-r--r--contrib/librdns/packet.h71
-rw-r--r--contrib/librdns/parse.c419
-rw-r--r--contrib/librdns/parse.h65
-rw-r--r--contrib/librdns/punycode.c297
-rw-r--r--contrib/librdns/punycode.h59
-rw-r--r--contrib/librdns/rdns.h372
-rw-r--r--contrib/librdns/rdns_curve.h75
-rw-r--r--contrib/librdns/rdns_ev.h232
-rw-r--r--contrib/librdns/rdns_event.h231
-rw-r--r--contrib/librdns/ref.h71
-rw-r--r--contrib/librdns/resolver.c782
-rw-r--r--contrib/librdns/upstream.h257
-rw-r--r--contrib/librdns/util.c523
-rw-r--r--contrib/librdns/util.h62
-rw-r--r--contrib/libucl/CMakeLists.txt18
-rw-r--r--contrib/libucl/khash.h627
-rw-r--r--contrib/libucl/kvec.h103
-rw-r--r--contrib/libucl/lua_ucl.c820
-rw-r--r--contrib/libucl/lua_ucl.h69
-rw-r--r--contrib/libucl/tree.h212
-rw-r--r--contrib/libucl/ucl.h1144
-rw-r--r--contrib/libucl/ucl_chartable.h267
-rw-r--r--contrib/libucl/ucl_emitter.c511
-rw-r--r--contrib/libucl/ucl_emitter_streamline.c165
-rw-r--r--contrib/libucl/ucl_emitter_utils.c487
-rw-r--r--contrib/libucl/ucl_hash.c353
-rw-r--r--contrib/libucl/ucl_hash.h93
-rw-r--r--contrib/libucl/ucl_internal.h399
-rw-r--r--contrib/libucl/ucl_parser.c2222
-rw-r--r--contrib/libucl/ucl_schema.c1015
-rw-r--r--contrib/libucl/ucl_util.c2568
-rw-r--r--contrib/libucl/xxhash.c475
-rw-r--r--contrib/libucl/xxhash.h164
41 files changed, 16537 insertions, 0 deletions
diff --git a/contrib/librdns/CMakeLists.txt b/contrib/librdns/CMakeLists.txt
new file mode 100644
index 000000000..f2cd9a3e1
--- /dev/null
+++ b/contrib/librdns/CMakeLists.txt
@@ -0,0 +1,10 @@
+SET(LIBRDNSSRC util.c
+ logger.c
+ compression.c
+ punycode.c
+ curve.c
+ parse.c
+ packet.c
+ resolver.c)
+
+ADD_LIBRARY(rdns STATIC ${LIBRDNSSRC}) \ No newline at end of file
diff --git a/contrib/librdns/compression.c b/contrib/librdns/compression.c
new file mode 100644
index 000000000..ac31a92c3
--- /dev/null
+++ b/contrib/librdns/compression.c
@@ -0,0 +1,154 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 "compression.h"
+#include "logger.h"
+
+static struct rdns_compression_entry *
+rdns_can_compress (const char *pos, struct rdns_compression_entry *comp)
+{
+ struct rdns_compression_entry *res;
+
+ HASH_FIND_STR (comp, pos, res);
+
+ return res;
+}
+
+static unsigned int
+rdns_calculate_label_len (const char *pos, const char *end)
+{
+ const char *p = pos;
+ unsigned int res = 0;
+
+ while (p != end) {
+ if (*p == '.') {
+ break;
+ }
+ res ++;
+ p ++;
+ }
+ return res;
+}
+
+static void
+rdns_add_compressed (const char *pos, const char *end,
+ struct rdns_compression_entry **comp, int offset)
+{
+ struct rdns_compression_entry *new;
+
+ assert (offset >= 0);
+ new = malloc (sizeof (*new));
+ if (new != NULL) {
+ new->label = pos;
+ new->offset = offset;
+ HASH_ADD_KEYPTR (hh, *comp, pos, (end - pos), new);
+ }
+}
+
+void
+rnds_compression_free (struct rdns_compression_entry *comp)
+{
+ struct rdns_compression_entry *cur, *tmp;
+
+ if (comp) {
+ free (comp->hh.tbl->buckets);
+ free (comp->hh.tbl);
+
+ HASH_ITER (hh, comp, cur, tmp) {
+ free (cur);
+ }
+ }
+}
+
+bool
+rdns_write_name_compressed (struct rdns_request *req,
+ const char *name, unsigned int namelen,
+ struct rdns_compression_entry **comp)
+{
+ uint8_t *target = req->packet + req->pos;
+ const char *pos = name, *end = name + namelen;
+ unsigned int remain = req->packet_len - req->pos - 5, label_len;
+ struct rdns_compression_entry *head = NULL, *test;
+ struct rdns_resolver *resolver = req->resolver;
+ uint16_t pointer;
+
+ if (comp != NULL) {
+ head = *comp;
+ }
+
+ while (pos < end && remain > 0) {
+ if (head != NULL) {
+ test = rdns_can_compress (pos, head);
+ if (test != NULL) {
+ if (remain < 2) {
+ rdns_info ("no buffer remain for constructing query");
+ return false;
+ }
+
+ pointer = htons ((uint16_t)test->offset) | DNS_COMPRESSION_BITS;
+ memcpy (target, &pointer, sizeof (pointer));
+ req->pos += 2;
+
+ return true;
+ }
+ }
+
+
+ label_len = rdns_calculate_label_len (pos, end);
+ if (label_len == 0) {
+ /* We have empty label it is allowed only if pos == end - 1 */
+ if (pos == end - 1) {
+ break;
+ }
+ else {
+ rdns_err ("double dots in the name requested");
+ return false;
+ }
+ }
+ else if (label_len > DNS_D_MAXLABEL) {
+ rdns_err ("too large label: %d", (int)label_len);
+ return false;
+ }
+
+ if (label_len + 1 > remain) {
+ rdns_info ("no buffer remain for constructing query, strip %d to %d",
+ (int)label_len, (int)remain);
+ label_len = remain - 1;
+ }
+
+ if (comp != NULL) {
+ rdns_add_compressed (pos, end, comp, target - req->packet);
+ }
+ /* Write label as is */
+ *target++ = (uint8_t)label_len;
+ memcpy (target, pos, label_len);
+ target += label_len;
+ pos += label_len + 1;
+ }
+
+ /* Termination label */
+ *target++ = '\0';
+ req->pos = target - req->packet;
+
+ return true;
+}
diff --git a/contrib/librdns/compression.h b/contrib/librdns/compression.h
new file mode 100644
index 000000000..adae2f3ef
--- /dev/null
+++ b/contrib/librdns/compression.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 COMPRESSION_H_
+#define COMPRESSION_H_
+
+#include "uthash.h"
+#include "dns_private.h"
+
+struct rdns_compression_entry {
+ const char *label; /**< label packed */
+ unsigned int offset; /**< offset in the packet */
+ UT_hash_handle hh; /**< hash handle */
+};
+
+/**
+ * Try to compress name passed or write it 'as is'
+ * @return
+ */
+bool rdns_write_name_compressed (struct rdns_request *req,
+ const char *name, unsigned int namelen,
+ struct rdns_compression_entry **comp);
+
+void rnds_compression_free (struct rdns_compression_entry *comp);
+
+#endif /* COMPRESSION_H_ */
diff --git a/contrib/librdns/curve.c b/contrib/librdns/curve.c
new file mode 100644
index 000000000..4c2a64135
--- /dev/null
+++ b/contrib/librdns/curve.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "rdns.h"
+#include "dns_private.h"
+#include "rdns_curve.h"
+#include "ottery.h"
+#include "ref.h"
+#include "logger.h"
+
+#ifdef TWEETNACL
+
+#include <tweetnacl.h>
+
+void
+randombytes(uint8_t *data, uint64_t len)
+{
+ ottery_rand_bytes (data, len);
+}
+void sodium_memzero (uint8_t *data, uint64_t len)
+{
+ volatile uint8_t *p = data;
+
+ while (len--) {
+ *p = '\0';
+ }
+}
+void sodium_init(void)
+{
+
+}
+
+ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data);
+ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len,
+ void *plugin_data, struct rdns_request **req_out);
+void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data);
+void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data);
+
+struct rdns_curve_entry {
+ char *name;
+ unsigned char pk[crypto_box_PUBLICKEYBYTES];
+ UT_hash_handle hh;
+};
+
+struct rdns_curve_nm_entry {
+ unsigned char k[crypto_box_BEFORENMBYTES];
+ struct rdns_curve_entry *entry;
+ struct rdns_curve_nm_entry *prev, *next;
+};
+
+struct rdns_curve_client_key {
+ unsigned char pk[crypto_box_PUBLICKEYBYTES];
+ unsigned char sk[crypto_box_SECRETKEYBYTES];
+ struct rdns_curve_nm_entry *nms;
+ uint64_t counter;
+ unsigned int uses;
+ ref_entry_t ref;
+};
+
+struct rdns_curve_request {
+ struct rdns_request *req;
+ struct rdns_curve_client_key *key;
+ struct rdns_curve_entry *entry;
+ struct rdns_curve_nm_entry *nm;
+ unsigned char nonce[crypto_box_NONCEBYTES];
+ UT_hash_handle hh;
+};
+
+struct rdns_curve_ctx {
+ struct rdns_curve_entry *entries;
+ struct rdns_curve_client_key *cur_key;
+ struct rdns_curve_request *requests;
+ double key_refresh_interval;
+ void *key_refresh_event;
+ struct rdns_resolver *resolver;
+};
+
+static struct rdns_curve_client_key *
+rdns_curve_client_key_new (struct rdns_curve_ctx *ctx)
+{
+ struct rdns_curve_client_key *new;
+ struct rdns_curve_nm_entry *nm;
+ struct rdns_curve_entry *entry, *tmp;
+
+ new = calloc (1, sizeof (struct rdns_curve_client_key));
+ crypto_box_keypair (new->pk, new->sk);
+
+ HASH_ITER (hh, ctx->entries, entry, tmp) {
+ nm = calloc (1, sizeof (struct rdns_curve_nm_entry));
+ nm->entry = entry;
+ crypto_box_beforenm (nm->k, entry->pk, new->sk);
+
+ DL_APPEND (new->nms, nm);
+ }
+
+ new->counter = ottery_rand_uint64 ();
+
+ return new;
+}
+
+static struct rdns_curve_nm_entry *
+rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry)
+{
+ struct rdns_curve_nm_entry *nm;
+
+ DL_FOREACH (key->nms, nm) {
+ if (nm->entry == entry) {
+ return nm;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+rdns_curve_client_key_free (struct rdns_curve_client_key *key)
+{
+ struct rdns_curve_nm_entry *nm, *tmp;
+
+ DL_FOREACH_SAFE (key->nms, nm, tmp) {
+ sodium_memzero (nm->k, sizeof (nm->k));
+ free (nm);
+ }
+ sodium_memzero (key->sk, sizeof (key->sk));
+ free (key);
+}
+
+struct rdns_curve_ctx*
+rdns_curve_ctx_new (double key_refresh_interval)
+{
+ struct rdns_curve_ctx *new;
+
+ new = calloc (1, sizeof (struct rdns_curve_ctx));
+ new->key_refresh_interval = key_refresh_interval;
+
+ return new;
+}
+
+void
+rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx,
+ const char *name, const unsigned char *pubkey)
+{
+ struct rdns_curve_entry *entry;
+ bool success = true;
+
+ entry = malloc (sizeof (struct rdns_curve_entry));
+ if (entry != NULL) {
+ entry->name = strdup (name);
+ if (entry->name == NULL) {
+ success = false;
+ }
+ memcpy (entry->pk, pubkey, sizeof (entry->pk));
+ if (success) {
+ HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry);
+ }
+ }
+}
+
+#define rdns_curve_write_hex(in, out, offset, base) do { \
+ *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \
+} while (0)
+
+static bool
+rdns_curve_hex_to_byte (const char *in, unsigned char *out)
+{
+ int i;
+
+ for (i = 0; i <= 1; i ++) {
+ if (in[i] >= '0' && in[i] <= '9') {
+ rdns_curve_write_hex (in, out, i, '0');
+ }
+ else if (in[i] >= 'a' && in[i] <= 'f') {
+ rdns_curve_write_hex (in, out, i, 'a' - 10);
+ }
+ else if (in[i] >= 'A' && in[i] <= 'F') {
+ rdns_curve_write_hex (in, out, i, 'A' - 10);
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+}
+
+#undef rdns_curve_write_hex
+
+unsigned char *
+rdns_curve_key_from_hex (const char *hex)
+{
+ unsigned int len = strlen (hex), i;
+ unsigned char *res = NULL;
+
+ if (len == crypto_box_PUBLICKEYBYTES * 2) {
+ res = calloc (1, crypto_box_PUBLICKEYBYTES);
+ for (i = 0; i < crypto_box_PUBLICKEYBYTES; i ++) {
+ if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) {
+ free (res);
+ return NULL;
+ }
+ }
+ }
+
+ return res;
+}
+
+void
+rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx)
+{
+ struct rdns_curve_entry *entry, *tmp;
+
+ HASH_ITER (hh, ctx->entries, entry, tmp) {
+ free (entry->name);
+ free (entry);
+ }
+
+ free (ctx);
+}
+
+static void
+rdns_curve_refresh_key_callback (void *user_data)
+{
+ struct rdns_curve_ctx *ctx = user_data;
+ struct rdns_resolver *resolver;
+
+ resolver = ctx->resolver;
+ rdns_info ("refresh dnscurve keys");
+ REF_RELEASE (ctx->cur_key);
+ ctx->cur_key = rdns_curve_client_key_new (ctx);
+ REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free);
+}
+
+void
+rdns_curve_register_plugin (struct rdns_resolver *resolver,
+ struct rdns_curve_ctx *ctx)
+{
+ struct rdns_plugin *plugin;
+
+ if (!resolver->async_binded) {
+ return;
+ }
+
+ plugin = calloc (1, sizeof (struct rdns_plugin));
+ if (plugin != NULL) {
+ plugin->data = ctx;
+ plugin->type = RDNS_PLUGIN_CURVE;
+ plugin->cb.curve_plugin.send_cb = rdns_curve_send;
+ plugin->cb.curve_plugin.recv_cb = rdns_curve_recv;
+ plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request;
+ plugin->dtor = rdns_curve_dtor;
+ sodium_init ();
+ ctx->cur_key = rdns_curve_client_key_new (ctx);
+ REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free);
+
+ if (ctx->key_refresh_interval > 0) {
+ ctx->key_refresh_event = resolver->async->add_periodic (
+ resolver->async->data, ctx->key_refresh_interval,
+ rdns_curve_refresh_key_callback, ctx);
+ }
+ ctx->resolver = resolver;
+ rdns_resolver_register_plugin (resolver, plugin);
+ }
+}
+
+ssize_t
+rdns_curve_send (struct rdns_request *req, void *plugin_data)
+{
+ struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+ struct rdns_curve_entry *entry;
+ struct iovec iov[4];
+ unsigned char *m;
+ static const char qmagic[] = "Q6fnvWj8";
+ struct rdns_curve_request *creq;
+ struct rdns_curve_nm_entry *nm;
+ ssize_t ret, boxed_len;
+
+ /* Check for key */
+ HASH_FIND_STR (ctx->entries, req->io->srv->name, entry);
+ if (entry != NULL) {
+ nm = rdns_curve_find_nm (ctx->cur_key, entry);
+ creq = malloc (sizeof (struct rdns_curve_request));
+ if (creq == NULL) {
+ return -1;
+ }
+
+ boxed_len = req->pos + crypto_box_ZEROBYTES;
+ m = malloc (boxed_len);
+ if (m == NULL) {
+ return -1;
+ }
+
+ /* Ottery is faster than sodium native PRG that uses /dev/random only */
+ memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t));
+ ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t));
+ sodium_memzero (creq->nonce + 12, crypto_box_NONCEBYTES - 12);
+
+ sodium_memzero (m, crypto_box_ZEROBYTES);
+ memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos);
+
+ if (crypto_box_afternm (m, m, boxed_len,
+ creq->nonce, nm->k) == -1) {
+ sodium_memzero (m, boxed_len);
+ free (m);
+ return -1;
+ }
+
+ creq->key = ctx->cur_key;
+ REF_RETAIN (ctx->cur_key);
+ creq->entry = entry;
+ creq->req = req;
+ creq->nm = nm;
+ HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq);
+ req->curve_plugin_data = creq;
+
+ ctx->cur_key->counter ++;
+ ctx->cur_key->uses ++;
+
+ /* Now form a dnscurve packet */
+ iov[0].iov_base = (void *)qmagic;
+ iov[0].iov_len = sizeof (qmagic) - 1;
+ iov[1].iov_base = ctx->cur_key->pk;
+ iov[1].iov_len = sizeof (ctx->cur_key->pk);
+ iov[2].iov_base = creq->nonce;
+ iov[2].iov_len = 12;
+ iov[3].iov_base = m + crypto_box_BOXZEROBYTES;
+ iov[3].iov_len = boxed_len - crypto_box_BOXZEROBYTES;
+
+ ret = writev (req->io->sock, iov, sizeof (iov) / sizeof (iov[0]));
+ sodium_memzero (m, boxed_len);
+ free (m);
+ }
+ else {
+ ret = write (req->io->sock, req->packet, req->pos);
+ req->curve_plugin_data = NULL;
+ }
+
+ return ret;
+}
+
+ssize_t
+rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data,
+ struct rdns_request **req_out)
+{
+ struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+ ssize_t ret, boxlen;
+ static const char rmagic[] = "R6fnvWJ8";
+ unsigned char *p, *box;
+ unsigned char enonce[crypto_box_NONCEBYTES];
+ struct rdns_curve_request *creq;
+ struct rdns_resolver *resolver;
+
+ resolver = ctx->resolver;
+ ret = read (ioc->sock, buf, len);
+
+ if (ret <= 0 || ret < 64) {
+ /* Definitely not a DNSCurve packet */
+ return ret;
+ }
+
+ if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) {
+ /* Likely DNSCurve packet */
+ p = ((unsigned char *)buf) + 8;
+ HASH_FIND (hh, ctx->requests, p, 12, creq);
+ if (creq == NULL) {
+ rdns_info ("unable to find nonce in the internal hash");
+ return ret;
+ }
+ memcpy (enonce, p, crypto_box_NONCEBYTES);
+ p += crypto_box_NONCEBYTES;
+ boxlen = ret - crypto_box_NONCEBYTES +
+ crypto_box_BOXZEROBYTES -
+ sizeof (rmagic) + 1;
+ if (boxlen < 0) {
+ return ret;
+ }
+ box = malloc (boxlen);
+ sodium_memzero (box, crypto_box_BOXZEROBYTES);
+ memcpy (box + crypto_box_BOXZEROBYTES, p,
+ boxlen - crypto_box_BOXZEROBYTES);
+
+ if (crypto_box_open_afternm (box, box, boxlen, enonce, creq->nm->k) != -1) {
+ memcpy (buf, box + crypto_box_ZEROBYTES,
+ boxlen - crypto_box_ZEROBYTES);
+ ret = boxlen - crypto_box_ZEROBYTES;
+ *req_out = creq->req;
+ }
+ else {
+ rdns_info ("unable open cryptobox of size %d", (int)boxlen);
+ }
+ free (box);
+ }
+
+ return ret;
+}
+
+void
+rdns_curve_finish_request (struct rdns_request *req, void *plugin_data)
+{
+ struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+ struct rdns_curve_request *creq = req->curve_plugin_data;
+
+ if (creq != NULL) {
+ REF_RELEASE (creq->key);
+ HASH_DELETE (hh, ctx->requests, creq);
+ }
+}
+
+void
+rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data)
+{
+ struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+
+ if (ctx->key_refresh_event != NULL) {
+ resolver->async->del_periodic (resolver->async->data,
+ ctx->key_refresh_event);
+ }
+ REF_RELEASE (ctx->cur_key);
+}
+
+#else
+
+/* Fake functions */
+struct rdns_curve_ctx* rdns_curve_ctx_new (double rekey_interval)
+{
+ return NULL;
+}
+void rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx,
+ const char *name, const unsigned char *pubkey)
+{
+
+}
+void rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx)
+{
+
+}
+void rdns_curve_register_plugin (struct rdns_resolver *resolver,
+ struct rdns_curve_ctx *ctx)
+{
+
+}
+
+unsigned char *
+rdns_curve_key_from_hex (const char *hex)
+{
+ return NULL;
+}
+#endif
diff --git a/contrib/librdns/dns_private.h b/contrib/librdns/dns_private.h
new file mode 100644
index 000000000..d23f8454d
--- /dev/null
+++ b/contrib/librdns/dns_private.h
@@ -0,0 +1,262 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 DNS_PRIVATE_H_
+#define DNS_PRIVATE_H_
+
+#include "uthash.h"
+#include "utlist.h"
+#include "rdns.h"
+#include "upstream.h"
+#include "ref.h"
+
+static const unsigned base = 36;
+static const unsigned t_min = 1;
+static const unsigned t_max = 26;
+static const unsigned skew = 38;
+static const unsigned damp = 700;
+static const unsigned initial_n = 128;
+static const unsigned initial_bias = 72;
+
+static const int dns_port = 53;
+static const int default_io_cnt = 8;
+
+#define UDP_PACKET_SIZE 4096
+
+#define DNS_COMPRESSION_BITS 0xC0
+
+#define DNS_D_MAXLABEL 63 /* + 1 '\0' */
+#define DNS_D_MAXNAME 255 /* + 1 '\0' */
+
+#define RESOLV_CONF "/etc/resolv.conf"
+
+/**
+ * Represents DNS server
+ */
+struct rdns_server {
+ char *name;
+ unsigned int port;
+ unsigned int io_cnt;
+
+ struct rdns_io_channel **io_channels;
+ upstream_entry_t up;
+};
+
+struct rdns_request {
+ struct rdns_resolver *resolver;
+ struct rdns_async_context *async;
+ struct rdns_io_channel *io;
+ struct rdns_reply *reply;
+ enum rdns_request_type type;
+
+ double timeout;
+ unsigned int retransmits;
+
+ int id;
+ struct rdns_request_name *requested_names;
+ unsigned int qcount;
+
+ enum {
+ RDNS_REQUEST_NEW = 0,
+ RDNS_REQUEST_REGISTERED = 1,
+ RDNS_REQUEST_SENT,
+ RDNS_REQUEST_REPLIED
+ } state;
+
+ uint8_t *packet;
+ off_t pos;
+ unsigned int packet_len;
+
+ dns_callback_type func;
+ void *arg;
+
+ void *async_event;
+
+#ifdef TWEETNACL
+ void *curve_plugin_data;
+#endif
+
+ UT_hash_handle hh;
+ ref_entry_t ref;
+};
+
+/**
+ * IO channel for a specific DNS server
+ */
+struct rdns_io_channel {
+ struct rdns_server *srv;
+ struct rdns_resolver *resolver;
+ int sock; /**< persistent socket */
+ bool active;
+ void *async_io; /** async opaque ptr */
+ struct rdns_request *requests; /**< requests in flight */
+ uint64_t uses;
+ ref_entry_t ref;
+};
+
+
+struct rdns_resolver {
+ struct rdns_server *servers;
+ struct rdns_io_channel *io_channels; /**< hash of io chains indexed by socket */
+ struct rdns_async_context *async; /** async callbacks */
+ void *periodic; /** periodic event for resolver */
+
+ struct rdns_plugin *curve_plugin;
+
+ rdns_log_function logger;
+ void *log_data;
+ enum rdns_log_level log_level;
+
+ uint64_t max_ioc_uses;
+ void *refresh_ioc_periodic;
+
+ bool async_binded;
+ bool initialized;
+ ref_entry_t ref;
+};
+
+struct dns_header;
+struct dns_query;
+
+/* Internal DNS structs */
+
+struct dns_header {
+ unsigned int qid :16;
+
+#if BYTE_ORDER == BIG_ENDIAN
+ unsigned int qr:1;
+ unsigned int opcode:4;
+ unsigned int aa:1;
+ unsigned int tc:1;
+ unsigned int rd:1;
+
+ unsigned int ra:1;
+ unsigned int unused:3;
+ unsigned int rcode:4;
+#else
+ unsigned int rd :1;
+ unsigned int tc :1;
+ unsigned int aa :1;
+ unsigned int opcode :4;
+ unsigned int qr :1;
+
+ unsigned int rcode :4;
+ unsigned int unused :3;
+ unsigned int ra :1;
+#endif
+
+ unsigned int qdcount :16;
+ unsigned int ancount :16;
+ unsigned int nscount :16;
+ unsigned int arcount :16;
+};
+
+enum dns_section {
+ DNS_S_QD = 0x01,
+#define DNS_S_QUESTION DNS_S_QD
+
+ DNS_S_AN = 0x02,
+#define DNS_S_ANSWER DNS_S_AN
+
+ DNS_S_NS = 0x04,
+#define DNS_S_AUTHORITY DNS_S_NS
+
+ DNS_S_AR = 0x08,
+#define DNS_S_ADDITIONAL DNS_S_AR
+
+ DNS_S_ALL = 0x0f
+};
+/* enum dns_section */
+
+enum dns_opcode {
+ DNS_OP_QUERY = 0,
+ DNS_OP_IQUERY = 1,
+ DNS_OP_STATUS = 2,
+ DNS_OP_NOTIFY = 4,
+ DNS_OP_UPDATE = 5,
+};
+/* dns_opcode */
+
+enum dns_class {
+ DNS_C_IN = 1,
+
+ DNS_C_ANY = 255
+};
+/* enum dns_class */
+
+struct dns_query {
+ char *qname;
+ unsigned int qtype :16;
+ unsigned int qclass :16;
+};
+
+enum dns_type {
+ DNS_T_A = RDNS_REQUEST_A,
+ DNS_T_NS = RDNS_REQUEST_NS,
+ DNS_T_CNAME = 5,
+ DNS_T_SOA = RDNS_REQUEST_SOA,
+ DNS_T_PTR = RDNS_REQUEST_PTR,
+ DNS_T_MX = RDNS_REQUEST_MX,
+ DNS_T_TXT = RDNS_REQUEST_TXT,
+ DNS_T_AAAA = RDNS_REQUEST_AAAA,
+ DNS_T_SRV = RDNS_REQUEST_SRV,
+ DNS_T_OPT = 41,
+ DNS_T_SSHFP = 44,
+ DNS_T_TLSA = RDNS_REQUEST_TLSA,
+ DNS_T_SPF = RDNS_REQUEST_SPF,
+ DNS_T_ALL = RDNS_REQUEST_ANY
+};
+/* enum dns_type */
+
+static const char dns_rcodes[][32] = {
+ [RDNS_RC_NOERROR] = "no error",
+ [RDNS_RC_FORMERR] = "query format error",
+ [RDNS_RC_SERVFAIL] = "server fail",
+ [RDNS_RC_NXDOMAIN] = "no records with this name",
+ [RDNS_RC_NOTIMP] = "not implemented",
+ [RDNS_RC_REFUSED] = "query refused",
+ [RDNS_RC_YXDOMAIN] = "YXDOMAIN",
+ [RDNS_RC_YXRRSET] = "YXRRSET",
+ [RDNS_RC_NXRRSET] = "NXRRSET",
+ [RDNS_RC_NOTAUTH] = "not authorized",
+ [RDNS_RC_NOTZONE] = "no such zone",
+ [RDNS_RC_TIMEOUT] = "query timed out",
+ [RDNS_RC_NETERR] = "network error",
+ [RDNS_RC_NOREC] = "requested record is not found"
+};
+
+static const char dns_types[][16] = {
+ [RDNS_REQUEST_A] = "A request",
+ [RDNS_REQUEST_NS] = "NS request",
+ [RDNS_REQUEST_PTR] = "PTR request",
+ [RDNS_REQUEST_MX] = "MX request",
+ [RDNS_REQUEST_TXT] = "TXT request",
+ [RDNS_REQUEST_SRV] = "SRV request",
+ [RDNS_REQUEST_SPF] = "SPF request",
+ [RDNS_REQUEST_AAAA] = "AAAA request",
+ [RDNS_REQUEST_TLSA] = "TLSA request",
+ [RDNS_REQUEST_ANY] = "ANY request"
+};
+
+
+#endif /* DNS_PRIVATE_H_ */
diff --git a/contrib/librdns/logger.c b/contrib/librdns/logger.c
new file mode 100644
index 000000000..c9ed2d926
--- /dev/null
+++ b/contrib/librdns/logger.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 "dns_private.h"
+#include "logger.h"
+
+void
+rdns_logger_internal (void *log_data, enum rdns_log_level level,
+ const char *function, const char *format,
+ va_list args)
+{
+ struct rdns_resolver *resolver = log_data;
+
+ if (level <= resolver->log_level) {
+ fprintf (stderr, "rdns: %s: ", function);
+ vfprintf (stderr, format, args);
+ fprintf (stderr, "\n");
+ }
+}
+
+void rdns_logger_helper (struct rdns_resolver *resolver,
+ enum rdns_log_level level,
+ const char *function, const char *format, ...)
+{
+ va_list va;
+
+ if (resolver->logger != NULL) {
+ va_start (va, format);
+ resolver->logger (resolver->log_data, level, function, format, va);
+ va_end (va);
+ }
+}
diff --git a/contrib/librdns/logger.h b/contrib/librdns/logger.h
new file mode 100644
index 000000000..91108f4e3
--- /dev/null
+++ b/contrib/librdns/logger.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 LOGGER_H_
+#define LOGGER_H_
+
+#include <stdarg.h>
+#include "dns_private.h"
+
+void rdns_logger_internal (void *log_data, enum rdns_log_level level,
+ const char *function, const char *format,
+ va_list args);
+
+void rdns_logger_helper (struct rdns_resolver *resolver,
+ enum rdns_log_level level,
+ const char *function, const char *format, ...);
+
+#define rdns_err(...) do { rdns_logger_helper (resolver, RDNS_LOG_ERROR, __FUNCTION__, __VA_ARGS__); } while (0)
+#define rdns_warn(...) do { rdns_logger_helper (resolver, RDNS_LOG_WARNING, __FUNCTION__, __VA_ARGS__); } while (0)
+#define rdns_info(...) do { rdns_logger_helper (resolver, RDNS_LOG_INFO, __FUNCTION__, __VA_ARGS__); } while (0)
+#define rdns_debug(...) do { rdns_logger_helper (resolver, RDNS_LOG_DEBUG, __FUNCTION__, __VA_ARGS__); } while (0)
+
+#endif /* LOGGER_H_ */
diff --git a/contrib/librdns/packet.c b/contrib/librdns/packet.c
new file mode 100644
index 000000000..88e51dfba
--- /dev/null
+++ b/contrib/librdns/packet.c
@@ -0,0 +1,271 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 "rdns.h"
+#include "dns_private.h"
+#include "punycode.h"
+#include "packet.h"
+#include "util.h"
+#include "logger.h"
+#include "compression.h"
+
+void
+rdns_allocate_packet (struct rdns_request* req, unsigned int namelen)
+{
+ namelen += 96 + 2 + 4 + 11; /* EDNS0 RR */
+ req->packet = malloc (namelen);
+ req->pos = 0;
+ req->packet_len = namelen;
+}
+
+
+void
+rdns_make_dns_header (struct rdns_request *req, unsigned int qcount)
+{
+ struct dns_header *header;
+
+ /* Set DNS header values */
+ header = (struct dns_header *)req->packet;
+ memset (header, 0 , sizeof (struct dns_header));
+ header->qid = rdns_permutor_generate_id ();
+ header->rd = 1;
+ header->qdcount = htons (qcount);
+ header->arcount = htons (1);
+ req->pos += sizeof (struct dns_header);
+ req->id = header->qid;
+}
+
+static bool
+rdns_maybe_punycode_label (const uint8_t *begin,
+ uint8_t const **dot, size_t *label_len)
+{
+ bool ret = false;
+ const uint8_t *p = begin;
+
+ *dot = NULL;
+
+ while (*p) {
+ if (*p == '.') {
+ *dot = p;
+ break;
+ }
+ else if ((*p) & 0x80) {
+ ret = true;
+ }
+ p ++;
+ }
+
+ if (*p) {
+ *label_len = p - begin;
+ }
+ else {
+ *label_len = p - begin;
+ }
+
+ return ret;
+}
+
+bool
+rdns_format_dns_name (struct rdns_resolver *resolver, const char *in,
+ size_t inlen,
+ char **out, size_t *outlen)
+{
+ const uint8_t *dot;
+ const uint8_t *p = in, *end = in + inlen;
+ char *o;
+ int labels = 0;
+ size_t label_len, olen, remain;
+ uint32_t *uclabel;
+ size_t punylabel_len, uclabel_len;
+ char tmp_label[DNS_D_MAXLABEL];
+ bool need_encode = false;
+
+ if (inlen == 0) {
+ inlen = strlen (in);
+ }
+
+ /* Check for non-ascii characters */
+ while (p != end) {
+ if (*p >= 0x80) {
+ need_encode = true;
+ }
+ else if (*p == '.') {
+ labels ++;
+ }
+ p ++;
+ }
+
+ if (!need_encode) {
+ *out = malloc (inlen + 1);
+ if (*out == NULL) {
+ return false;
+ }
+ o = *out;
+ memcpy (o, in, inlen);
+ o[inlen] = '\0';
+ *outlen = inlen;
+
+ return true;
+ }
+
+ /* We need to encode */
+
+ p = in;
+ olen = inlen + 1 + sizeof ("xn--") * labels;
+ *out = malloc (olen);
+
+ if (*out == NULL) {
+ return false;
+ }
+
+ o = *out;
+ remain = olen;
+
+ while (p != end) {
+ /* Check label for unicode characters */
+ if (rdns_maybe_punycode_label (p, &dot, &label_len)) {
+ /* Convert to ucs4 */
+ if (rdns_utf8_to_ucs4 (p, label_len, &uclabel, &uclabel_len) == 0) {
+ punylabel_len = DNS_D_MAXLABEL;
+
+ rdns_punycode_label_toascii (uclabel, uclabel_len,
+ tmp_label, &punylabel_len);
+ if (remain >= punylabel_len + 1) {
+ memcpy (o, tmp_label, punylabel_len);
+ o += punylabel_len;
+ *o++ = '.';
+ remain -= punylabel_len + 1;
+ }
+ else {
+ rdns_info ("no buffer remain for punycoding query");
+ free (*out);
+ return false;
+ }
+
+ free (uclabel);
+
+ if (dot) {
+ p = dot + 1;
+ }
+ else {
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ }
+ else {
+ if (dot) {
+ if (label_len > DNS_D_MAXLABEL) {
+ rdns_info ("dns name component is longer than 63 bytes, should be stripped");
+ label_len = DNS_D_MAXLABEL;
+ }
+ if (remain < label_len + 1) {
+ rdns_info ("no buffer remain for punycoding query");
+ return false;
+ }
+ if (label_len == 0) {
+ /* Two dots in order, skip this */
+ rdns_info ("name contains two or more dots in a row, replace it with one dot");
+ p = dot + 1;
+ continue;
+ }
+ memcpy (o, p, label_len);
+ o += label_len;
+ *o++ = '.';
+ remain -= label_len + 1;
+ p = dot + 1;
+ }
+ else {
+ if (label_len == 0) {
+ /* If name is ended with dot */
+ break;
+ }
+ if (label_len > DNS_D_MAXLABEL) {
+ rdns_info ("dns name component is longer than 63 bytes, should be stripped");
+ label_len = DNS_D_MAXLABEL;
+ }
+ if (remain < label_len + 1) {
+ rdns_info ("no buffer remain for punycoding query");
+ return false;
+ }
+ memcpy (o, p, label_len);
+ o += label_len;
+ *o++ = '.';
+ remain -= label_len + 1;
+ p = dot + 1;
+ break;
+ }
+ }
+ if (remain == 0) {
+ rdns_info ("no buffer remain for punycoding query");
+ return false;
+ }
+ }
+ *o = '\0';
+
+ *outlen = o - *out;
+
+ return true;
+}
+
+bool
+rdns_add_rr (struct rdns_request *req, const char *name, unsigned int len,
+ enum dns_type type, struct rdns_compression_entry **comp)
+{
+ uint16_t *p;
+
+ if (!rdns_write_name_compressed (req, name, len, comp)) {
+ return false;
+ }
+ p = (uint16_t *)(req->packet + req->pos);
+ *p++ = htons (type);
+ *p = htons (DNS_C_IN);
+ req->pos += sizeof (uint16_t) * 2;
+
+ return true;
+}
+
+bool
+rdns_add_edns0 (struct rdns_request *req)
+{
+ uint8_t *p8;
+ uint16_t *p16;
+
+ p8 = (uint8_t *)(req->packet + req->pos);
+ *p8 = '\0'; /* Name is root */
+ p16 = (uint16_t *)(req->packet + req->pos + 1);
+ *p16++ = htons (DNS_T_OPT);
+ /* UDP packet length */
+ *p16++ = htons (UDP_PACKET_SIZE);
+ /* Extended rcode 00 00 */
+ *p16++ = 0;
+ /* Z 10000000 00000000 to allow dnssec, disabled currently */
+ *p16++ = 0;
+ /* Length */
+ *p16 = 0;
+ req->pos += sizeof (uint8_t) + sizeof (uint16_t) * 5;
+
+ return true;
+}
diff --git a/contrib/librdns/packet.h b/contrib/librdns/packet.h
new file mode 100644
index 000000000..f4c09b900
--- /dev/null
+++ b/contrib/librdns/packet.h
@@ -0,0 +1,71 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 PACKET_H_
+#define PACKET_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "dns_private.h"
+
+struct rdns_compression_entry;
+
+/**
+ * Allocate dns packet suitable to handle up to `namelen` name
+ * @param req request
+ * @param namelen requested name
+ */
+void rdns_allocate_packet (struct rdns_request* req, unsigned int namelen);
+
+/**
+ * Add basic header to the dns packet
+ * @param req
+ */
+void rdns_make_dns_header (struct rdns_request *req, unsigned int qcount);
+
+
+/**
+ * Format DNS name of the packet
+ * @param req request
+ * @param name name string
+ * @param namelen length of name
+ */
+bool rdns_format_dns_name (struct rdns_resolver *resolver,
+ const char *name, size_t namelen,
+ char **out, size_t *outlen);
+
+/**
+ * Add a resource record to the DNS packet
+ * @param req request
+ * @param name requested name
+ * @param type type of resource record
+ */
+bool rdns_add_rr (struct rdns_request *req, const char *name, unsigned int len,
+ enum dns_type type, struct rdns_compression_entry **comp);
+
+/**
+ * Add EDNS0 section
+ * @param req
+ */
+bool rdns_add_edns0 (struct rdns_request *req);
+
+#endif /* PACKET_H_ */
diff --git a/contrib/librdns/parse.c b/contrib/librdns/parse.c
new file mode 100644
index 000000000..e9527eaa4
--- /dev/null
+++ b/contrib/librdns/parse.c
@@ -0,0 +1,419 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 "rdns.h"
+#include "dns_private.h"
+#include "parse.h"
+#include "logger.h"
+
+static uint8_t *
+rdns_decompress_label (uint8_t *begin, uint16_t *len, uint16_t max)
+{
+ uint16_t offset = (*len);
+
+ if (offset > max) {
+ return NULL;
+ }
+ *len = *(begin + offset);
+ return begin + offset;
+}
+
+#define UNCOMPRESS_DNS_OFFSET(p) (((*(p)) ^ DNS_COMPRESSION_BITS) << 8) + *((p) + 1)
+
+uint8_t *
+rdns_request_reply_cmp (struct rdns_request *req, uint8_t *in, int len)
+{
+ uint8_t *p, *c, *l1, *l2;
+ uint16_t len1, len2;
+ int decompressed = 0;
+ struct rdns_resolver *resolver = req->resolver;
+
+ /* QR format:
+ * labels - len:octets
+ * null label - 0
+ * class - 2 octets
+ * type - 2 octets
+ */
+
+ /* In p we would store current position in reply and in c - position in request */
+ p = in;
+ c = req->packet + req->pos;
+
+ for (;;) {
+ /* Get current label */
+ len1 = *p;
+ len2 = *c;
+ if (p - in > len) {
+ rdns_info ("invalid dns reply");
+ return NULL;
+ }
+ /* This may be compressed, so we need to decompress it */
+ if (len1 & DNS_COMPRESSION_BITS) {
+ len1 = UNCOMPRESS_DNS_OFFSET(p);
+ l1 = rdns_decompress_label (in, &len1, len);
+ if (l1 == NULL) {
+ return NULL;
+ }
+ decompressed ++;
+ l1 ++;
+ p += 2;
+ }
+ else {
+ l1 = ++p;
+ p += len1;
+ }
+ if (len2 & DNS_COMPRESSION_BITS) {
+ len2 = UNCOMPRESS_DNS_OFFSET(c);
+ l2 = rdns_decompress_label (c, &len2, len);
+ if (l2 == NULL) {
+ rdns_info ("invalid DNS pointer, cannot decompress");
+ return NULL;
+ }
+ decompressed ++;
+ l2 ++;
+ c += 2;
+ }
+ else {
+ l2 = ++c;
+ c += len2;
+ }
+ if (len1 != len2) {
+ return NULL;
+ }
+ if (len1 == 0) {
+ break;
+ }
+
+ if (memcmp (l1, l2, len1) != 0) {
+ return NULL;
+ }
+ if (decompressed == 2) {
+ break;
+ }
+ }
+
+ /* p now points to the end of QR section */
+ /* Compare class and type */
+ if (memcmp (p, c, sizeof (uint16_t) * 2) == 0) {
+ req->pos = c - req->packet + sizeof (uint16_t) * 2;
+ return p + sizeof (uint16_t) * 2;
+ }
+ return NULL;
+}
+
+#define MAX_RECURSION_LEVEL 10
+
+bool
+rdns_parse_labels (struct rdns_resolver *resolver,
+ uint8_t *in, char **target, uint8_t **pos, struct rdns_reply *rep,
+ int *remain, bool make_name)
+{
+ uint16_t namelen = 0;
+ uint8_t *p = *pos, *begin = *pos, *l, *t, *end = *pos + *remain, *new_pos = *pos;
+ uint16_t llen;
+ int length = *remain, new_remain = *remain;
+ int ptrs = 0, labels = 0;
+ bool got_compression = false;
+
+ /* First go through labels and calculate name length */
+ while (p - begin < length) {
+ if (ptrs > MAX_RECURSION_LEVEL) {
+ rdns_info ("dns pointers are nested too much");
+ return false;
+ }
+ llen = *p;
+ if (llen == 0) {
+ if (!got_compression) {
+ /* In case of compression we have already decremented the processing position */
+ new_remain -= sizeof (uint8_t);
+ new_pos += sizeof (uint8_t);
+ }
+ break;
+ }
+ else if ((llen & DNS_COMPRESSION_BITS)) {
+ if (end - p > 1) {
+ ptrs ++;
+ llen = UNCOMPRESS_DNS_OFFSET(p);
+ l = rdns_decompress_label (in, &llen, end - in);
+ if (l == NULL) {
+ rdns_info ("invalid DNS pointer");
+ return false;
+ }
+ if (!got_compression) {
+ /* Our label processing is finished actually */
+ new_remain -= sizeof (uint16_t);
+ new_pos += sizeof (uint16_t);
+ got_compression = true;
+ }
+ if (l < in || l > begin + length) {
+ rdns_info ("invalid pointer in DNS packet");
+ return false;
+ }
+ begin = l;
+ length = end - begin;
+ p = l + *l + 1;
+ namelen += *l;
+ labels ++;
+ }
+ else {
+ rdns_info ("DNS packet has incomplete compressed label, input length: %d bytes, remain: %d",
+ *remain, new_remain);
+ return false;
+ }
+ }
+ else {
+ namelen += llen;
+ p += llen + 1;
+ labels ++;
+ if (!got_compression) {
+ new_remain -= llen + 1;
+ new_pos += llen + 1;
+ }
+ }
+ }
+
+ if (!make_name) {
+ goto end;
+ }
+ *target = malloc (namelen + labels + 3);
+ t = (uint8_t *)*target;
+ p = *pos;
+ begin = *pos;
+ length = *remain;
+ /* Now copy labels to name */
+ while (p - begin < length) {
+ llen = *p;
+ if (llen == 0) {
+ break;
+ }
+ else if (llen & DNS_COMPRESSION_BITS) {
+ llen = UNCOMPRESS_DNS_OFFSET(p);
+ l = rdns_decompress_label (in, &llen, end - in);
+ begin = l;
+ length = end - begin;
+ p = l + *l + 1;
+ memcpy (t, l + 1, *l);
+ t += *l;
+ *t ++ = '.';
+ }
+ else {
+ memcpy (t, p + 1, *p);
+ t += *p;
+ *t ++ = '.';
+ p += *p + 1;
+ }
+ }
+ if (t > (uint8_t *)*target) {
+ *(t - 1) = '\0';
+ }
+ else {
+ /* Handle empty labels */
+ **target = '\0';
+ }
+end:
+ *remain = new_remain;
+ *pos = new_pos;
+
+ return true;
+}
+
+#define GET8(x) do {(x) = ((*p)); p += sizeof (uint8_t); *remain -= sizeof (uint8_t); } while(0)
+#define GET16(x) do {(x) = ((*p) << 8) + *(p + 1); p += sizeof (uint16_t); *remain -= sizeof (uint16_t); } while(0)
+#define GET32(x) do {(x) = ((*p) << 24) + ((*(p + 1)) << 16) + ((*(p + 2)) << 8) + *(p + 3); p += sizeof (uint32_t); *remain -= sizeof (uint32_t); } while(0)
+#define SKIP(type) do { p += sizeof(type); *remain -= sizeof(type); } while (0)
+
+int
+rdns_parse_rr (struct rdns_resolver *resolver,
+ uint8_t *in, struct rdns_reply_entry *elt, uint8_t **pos,
+ struct rdns_reply *rep, int *remain)
+{
+ uint8_t *p = *pos, parts;
+ uint16_t type, datalen, txtlen, copied;
+ int32_t ttl;
+ bool parsed = false;
+
+ /* Skip the whole name */
+ if (! rdns_parse_labels (resolver, in, NULL, &p, rep, remain, false)) {
+ rdns_info ("bad RR name");
+ return -1;
+ }
+ if (*remain < (int)sizeof (uint16_t) * 6) {
+ rdns_info ("stripped dns reply: %d bytes remain", *remain);
+ return -1;
+ }
+ GET16 (type);
+ /* Skip class */
+ SKIP (uint16_t);
+ GET32 (ttl);
+ GET16 (datalen);
+ elt->type = type;
+ /* Now p points to RR data */
+ switch (type) {
+ case DNS_T_A:
+ if (!(datalen & 0x3) && datalen <= *remain) {
+ memcpy (&elt->content.a.addr, p, sizeof (struct in_addr));
+ p += datalen;
+ *remain -= datalen;
+ parsed = true;
+ }
+ else {
+ rdns_info ("corrupted A record");
+ return -1;
+ }
+ break;
+ case DNS_T_AAAA:
+ if (datalen == sizeof (struct in6_addr) && datalen <= *remain) {
+ memcpy (&elt->content.aaa.addr, p, sizeof (struct in6_addr));
+ p += datalen;
+ *remain -= datalen;
+ parsed = true;
+ }
+ else {
+ rdns_info ("corrupted AAAA record");
+ return -1;
+ }
+ break;
+ case DNS_T_PTR:
+ if (! rdns_parse_labels (resolver, in, &elt->content.ptr.name, &p,
+ rep, remain, true)) {
+ rdns_info ("invalid labels in PTR record");
+ return -1;
+ }
+ parsed = true;
+ break;
+ case DNS_T_NS:
+ if (! rdns_parse_labels (resolver, in, &elt->content.ns.name, &p,
+ rep, remain, true)) {
+ rdns_info ("invalid labels in NS record");
+ return -1;
+ }
+ parsed = true;
+ break;
+ case DNS_T_SOA:
+ if (! rdns_parse_labels (resolver, in, &elt->content.soa.mname, &p,
+ rep, remain, true)) {
+ rdns_info ("invalid labels in NS record");
+ return -1;
+ }
+ if (! rdns_parse_labels (resolver, in, &elt->content.soa.admin, &p,
+ rep, remain, true)) {
+ rdns_info ("invalid labels in NS record");
+ return -1;
+ }
+ GET32 (elt->content.soa.serial);
+ GET32 (elt->content.soa.refresh);
+ GET32 (elt->content.soa.retry);
+ GET32 (elt->content.soa.expire);
+ GET32 (elt->content.soa.minimum);
+ parsed = true;
+ break;
+ case DNS_T_MX:
+ GET16 (elt->content.mx.priority);
+ if (! rdns_parse_labels (resolver, in, &elt->content.mx.name, &p,
+ rep, remain, true)) {
+ rdns_info ("invalid labels in MX record");
+ return -1;
+ }
+ parsed = true;
+ break;
+ case DNS_T_TXT:
+ case DNS_T_SPF:
+ elt->content.txt.data = malloc (datalen + 1);
+ if (elt->content.txt.data == NULL) {
+ rdns_err ("failed to allocate %d bytes for TXT record", (int)datalen + 1);
+ return -1;
+ }
+ /* Now we should compose data from parts */
+ copied = 0;
+ parts = 0;
+ while (copied + parts < datalen) {
+ txtlen = *p;
+ if (txtlen + copied + parts <= datalen) {
+ parts ++;
+ memcpy (elt->content.txt.data + copied, p + 1, txtlen);
+ copied += txtlen;
+ p += txtlen + 1;
+ *remain -= txtlen + 1;
+ }
+ else {
+ break;
+ }
+ }
+ *(elt->content.txt.data + copied) = '\0';
+ parsed = true;
+ elt->type = RDNS_REQUEST_TXT;
+ break;
+ case DNS_T_SRV:
+ if (p - *pos > (int)(*remain - sizeof (uint16_t) * 3)) {
+ rdns_info ("stripped dns reply while reading SRV record");
+ return -1;
+ }
+ GET16 (elt->content.srv.priority);
+ GET16 (elt->content.srv.weight);
+ GET16 (elt->content.srv.port);
+ if (! rdns_parse_labels (resolver, in, &elt->content.srv.target,
+ &p, rep, remain, true)) {
+ rdns_info ("invalid labels in SRV record");
+ return -1;
+ }
+ parsed = true;
+ break;
+ case DNS_T_TLSA:
+ if (p - *pos > (int)(*remain - sizeof (uint8_t) * 3) || datalen <= 3) {
+ rdns_info ("stripped dns reply while reading TLSA record");
+ return -1;
+ }
+ GET8 (elt->content.tlsa.usage);
+ GET8 (elt->content.tlsa.selector);
+ GET8 (elt->content.tlsa.match_type);
+ datalen -= 3;
+ elt->content.tlsa.data = malloc (datalen);
+ if (elt->content.tlsa.data == NULL) {
+ rdns_err ("failed to allocate %d bytes for TLSA record", (int)datalen + 1);
+ return -1;
+ }
+ elt->content.tlsa.datalen = datalen;
+ memcpy (elt->content.tlsa.data, p, datalen);
+ p += datalen;
+ *remain -= datalen;
+ parsed = true;
+ break;
+ case DNS_T_CNAME:
+ /* Skip cname records */
+ p += datalen;
+ *remain -= datalen;
+ break;
+ default:
+ rdns_debug ("unexpected RR type: %d", type);
+ p += datalen;
+ *remain -= datalen;
+ break;
+ }
+ *pos = p;
+
+ if (parsed) {
+ elt->ttl = ttl;
+ return 1;
+ }
+ return 0;
+}
diff --git a/contrib/librdns/parse.h b/contrib/librdns/parse.h
new file mode 100644
index 000000000..ed8cd7057
--- /dev/null
+++ b/contrib/librdns/parse.h
@@ -0,0 +1,65 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 PARSE_H_
+#define PARSE_H_
+
+#include "dns_private.h"
+
+/**
+ * Compare request and reply checking names
+ * @param req request object
+ * @param in incoming packet
+ * @param len length of the incoming packet
+ * @return new position in the incoming packet or NULL if request is not equal to reply
+ */
+uint8_t * rdns_request_reply_cmp (struct rdns_request *req, uint8_t *in, int len);
+
+/**
+ * Parse labels in the packet
+ * @param in incoming packet
+ * @param target target to write the parsed label (out)
+ * @param pos output position in the packet (it/out)
+ * @param rep dns reply
+ * @param remain remaining bytes (in/out)
+ * @param make_name create name or just skip to the next label
+ * @return true if a label has been successfully parsed
+ */
+bool rdns_parse_labels (struct rdns_resolver *resolver,
+ uint8_t *in, char **target,
+ uint8_t **pos, struct rdns_reply *rep,
+ int *remain, bool make_name);
+
+/**
+ * Parse resource record
+ * @param in incoming packet
+ * @param elt new reply entry
+ * @param pos output position in the packet (it/out)
+ * @param rep dns reply
+ * @param remain remaining bytes (in/out)
+ * @return 1 if rr has been parsed, 0 if rr has been skipped and -1 if there was a parsing error
+ */
+int rdns_parse_rr (struct rdns_resolver *resolver,
+ uint8_t *in, struct rdns_reply_entry *elt, uint8_t **pos,
+ struct rdns_reply *rep, int *remain);
+
+#endif /* PARSE_H_ */
diff --git a/contrib/librdns/punycode.c b/contrib/librdns/punycode.c
new file mode 100644
index 000000000..6ce348495
--- /dev/null
+++ b/contrib/librdns/punycode.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ * Copyright (c) 2004, 2006, 2007, 2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "dns_private.h"
+
+/* Punycode utility */
+static unsigned int
+digit (unsigned n)
+{
+ static const char ascii[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ return ascii[n];
+}
+
+static unsigned int
+adapt (unsigned int delta, unsigned int numpoints, int first)
+{
+ unsigned int k;
+
+ if (first) {
+ delta = delta / damp;
+ }
+ else {
+ delta /= 2;
+ }
+ delta += delta / numpoints;
+ k = 0;
+ while (delta > ((base - t_min) * t_max) / 2) {
+ delta /= base - t_min;
+ k += base;
+ }
+ return k + (((base - t_min + 1) * delta) / (delta + skew));
+}
+
+/**
+ * Convert an UCS4 string to a puny-coded DNS label string suitable
+ * when combined with delimiters and other labels for DNS lookup.
+ *
+ * @param in an UCS4 string to convert
+ * @param in_len the length of in.
+ * @param out the resulting puny-coded string. The string is not NULL
+ * terminated.
+ * @param out_len before processing out_len should be the length of
+ * the out variable, after processing it will be the length of the out
+ * string.
+ *
+ * @return returns 0 on success, an wind error code otherwise
+ */
+
+bool
+rdns_punycode_label_toascii (const uint32_t *in, size_t in_len, char *out,
+ size_t *out_len)
+{
+ unsigned int n = initial_n;
+ unsigned int delta = 0;
+ unsigned int bias = initial_bias;
+ unsigned int h = 0;
+ unsigned int b;
+ unsigned int i;
+ unsigned int o = 0;
+ unsigned int m;
+
+ for (i = 0; i < in_len; ++i) {
+ if (in[i] < 0x80) {
+ ++h;
+ if (o >= *out_len) {
+ return false;
+ }
+ out[o++] = in[i];
+ }
+ }
+ b = h;
+ if (b > 0) {
+ if (o >= *out_len) {
+ return false;
+ }
+ out[o++] = 0x2D;
+ }
+ /* is this string punycoded */
+ if (h < in_len) {
+ if (o + 4 >= *out_len) {
+ return false;
+ }
+ memmove (out + 4, out, o);
+ memcpy (out, "xn--", 4);
+ o += 4;
+ }
+
+ while (h < in_len) {
+ m = (unsigned int) -1;
+ for (i = 0; i < in_len; ++i) {
+
+ if (in[i] < m && in[i] >= n) {
+ m = in[i];
+ }
+ }
+ delta += (m - n) * (h + 1);
+ n = m;
+ for (i = 0; i < in_len; ++i) {
+ if (in[i] < n) {
+ ++delta;
+ }
+ else if (in[i] == n) {
+ unsigned int q = delta;
+ unsigned int k;
+ for (k = base;; k += base) {
+ unsigned int t;
+ if (k <= bias) {
+ t = t_min;
+ }
+ else if (k >= bias + t_max) {
+ t = t_max;
+ }
+ else {
+ t = k - bias;
+ }
+ if (q < t) {
+ break;
+ }
+ if (o >= *out_len) {
+ return -1;
+ }
+ out[o++] = digit (t + ((q - t) % (base - t)));
+ q = (q - t) / (base - t);
+ }
+ if (o >= *out_len) {
+ return -1;
+ }
+ out[o++] = digit (q);
+ /* output */
+ bias = adapt (delta, h + 1, h == b);
+ delta = 0;
+ ++h;
+ }
+ }
+ ++delta;
+ ++n;
+ }
+
+ *out_len = o;
+ return true;
+}
+
+static int
+utf8toutf32 (const unsigned char **pp, uint32_t *out, size_t *remain)
+{
+ const unsigned char *p = *pp;
+ unsigned c = *p;
+ size_t reduce;
+
+ if (c & 0x80) {
+ if ((c & 0xE0) == 0xC0 && *remain >= 2) {
+ const unsigned c2 = *++p;
+ reduce = 2;
+ if ((c2 & 0xC0) == 0x80) {
+ *out = ((c & 0x1F) << 6) | (c2 & 0x3F);
+ }
+ else {
+ return -1;
+ }
+ }
+ else if ((c & 0xF0) == 0xE0 && *remain >= 3) {
+ const unsigned c2 = *++p;
+ if ((c2 & 0xC0) == 0x80) {
+ const unsigned c3 = *++p;
+ reduce = 3;
+ if ((c3 & 0xC0) == 0x80) {
+ *out = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6)
+ | (c3 & 0x3F);
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+ }
+ else if ((c & 0xF8) == 0xF0 && *remain >= 4) {
+ const unsigned c2 = *++p;
+ if ((c2 & 0xC0) == 0x80) {
+ const unsigned c3 = *++p;
+ if ((c3 & 0xC0) == 0x80) {
+ const unsigned c4 = *++p;
+ reduce = 4;
+ if ((c4 & 0xC0) == 0x80) {
+ *out = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12)
+ | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ *out = c;
+ reduce = 1;
+ }
+
+ *pp = ++p;
+ *remain -= reduce;
+
+ return 0;
+}
+
+/**
+ * Convert an UTF-8 string to an UCS4 string.
+ *
+ * @param in an UTF-8 string to convert.
+ * @param out the resulting UCS4 string
+ * @param out_len before processing out_len should be the length of
+ * the out variable, after processing it will be the length of the out
+ * string.
+ *
+ * @return returns 0 on success, an -1 otherwise
+ * @ingroup wind
+ */
+
+int
+rdns_utf8_to_ucs4 (const char *in, size_t in_len, uint32_t **out, size_t *out_len)
+{
+ const unsigned char *p;
+ size_t remain = in_len, olen = 0;
+ int ret;
+ uint32_t *res;
+
+ p = (const unsigned char *)in;
+ while (remain > 0) {
+ uint32_t u;
+
+ ret = utf8toutf32 (&p, &u, &remain);
+ if (ret != 0) {
+ return ret;
+ }
+
+ olen ++;
+ }
+ res = malloc (olen * sizeof (uint32_t));
+ if (res == NULL) {
+ return -1;
+ }
+
+ p = (const unsigned char *)in;
+ remain = in_len;
+ olen = 0;
+ while (remain > 0) {
+ uint32_t u;
+
+ (void)utf8toutf32 (&p, &u, &remain);
+ res[olen++] = u;
+ }
+
+ *out_len = olen;
+ *out = res;
+ return 0;
+}
diff --git a/contrib/librdns/punycode.h b/contrib/librdns/punycode.h
new file mode 100644
index 000000000..300fb92a6
--- /dev/null
+++ b/contrib/librdns/punycode.h
@@ -0,0 +1,59 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 PUNYCODE_H_
+#define PUNYCODE_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Convert an UCS4 string to a puny-coded DNS label string suitable
+ * when combined with delimiters and other labels for DNS lookup.
+ *
+ * @param in an UCS4 string to convert
+ * @param in_len the length of in.
+ * @param out the resulting puny-coded string. The string is not NULL
+ * terminated.
+ * @param out_len before processing out_len should be the length of
+ * the out variable, after processing it will be the length of the out
+ * string.
+ *
+ * @return returns 0 on success, an wind error code otherwise
+ */
+bool rdns_punycode_label_toascii (const uint32_t *in, size_t in_len, char *out, size_t *out_len);
+/**
+ * Convert an UTF-8 string to an UCS4 string.
+ *
+ * @param in an UTF-8 string to convert.
+ * @param out the resulting UCS4 string
+ * @param out_len before processing out_len should be the length of
+ * the out variable, after processing it will be the length of the out
+ * string.
+ *
+ * @return returns 0 on success, an -1 otherwise
+ * @ingroup wind
+ */
+
+int rdns_utf8_to_ucs4 (const char *in, size_t in_len, uint32_t **out, size_t *out_len);
+
+#endif /* PUNYCODE_H_ */
diff --git a/contrib/librdns/rdns.h b/contrib/librdns/rdns.h
new file mode 100644
index 000000000..a1cfad4d3
--- /dev/null
+++ b/contrib/librdns/rdns.h
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2013-2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RDNS_H
+#define RDNS_H
+
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rdns_reply;
+struct rdns_request;
+struct rdns_io_channel;
+
+typedef void (*dns_callback_type) (struct rdns_reply *reply, void *arg);
+
+enum rdns_request_type {
+ RDNS_REQUEST_A = 1,
+ RDNS_REQUEST_NS = 2,
+ RDNS_REQUEST_SOA = 6,
+ RDNS_REQUEST_PTR = 12,
+ RDNS_REQUEST_MX = 15,
+ RDNS_REQUEST_TXT = 16,
+ RDNS_REQUEST_SRV = 33,
+ RDNS_REQUEST_SPF = 99,
+ RDNS_REQUEST_AAAA = 28,
+ RDNS_REQUEST_TLSA = 52,
+ RDNS_REQUEST_ANY = 255
+};
+
+union rdns_reply_element_un {
+ struct {
+ struct in_addr addr;
+ } a;
+ struct {
+ struct in6_addr addr;
+ } aaa;
+ struct {
+ char *name;
+ } ptr;
+ struct {
+ char *name;
+ } ns;
+ struct {
+ char *name;
+ uint16_t priority;
+ } mx;
+ struct {
+ char *data;
+ } txt;
+ struct {
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ char *target;
+ } srv;
+ struct {
+ char *mname;
+ char *admin;
+ uint32_t serial;
+ int32_t refresh;
+ int32_t retry;
+ int32_t expire;
+ uint32_t minimum;
+ } soa;
+ struct {
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t match_type;
+ uint16_t datalen;
+ uint8_t *data;
+ } tlsa;
+};
+
+struct rdns_reply_entry {
+ union rdns_reply_element_un content;
+ uint16_t type;
+ int32_t ttl;
+ struct rdns_reply_entry *prev, *next;
+};
+
+
+enum dns_rcode {
+ RDNS_RC_NOERROR = 0,
+ RDNS_RC_FORMERR = 1,
+ RDNS_RC_SERVFAIL = 2,
+ RDNS_RC_NXDOMAIN = 3,
+ RDNS_RC_NOTIMP = 4,
+ RDNS_RC_REFUSED = 5,
+ RDNS_RC_YXDOMAIN = 6,
+ RDNS_RC_YXRRSET = 7,
+ RDNS_RC_NXRRSET = 8,
+ RDNS_RC_NOTAUTH = 9,
+ RDNS_RC_NOTZONE = 10,
+ RDNS_RC_TIMEOUT = 11,
+ RDNS_RC_NETERR = 12,
+ RDNS_RC_NOREC = 13
+};
+
+struct rdns_reply {
+ struct rdns_request *request;
+ struct rdns_resolver *resolver;
+ struct rdns_reply_entry *entries;
+ const char *requested_name;
+ enum dns_rcode code;
+};
+
+typedef void (*rdns_periodic_callback)(void *user_data);
+
+struct rdns_async_context {
+ void *data;
+ void* (*add_read)(void *priv_data, int fd, void *user_data);
+ void (*del_read)(void *priv_data, void *ev_data);
+ void* (*add_write)(void *priv_data, int fd, void *user_data);
+ void (*del_write)(void *priv_data, void *ev_data);
+ void* (*add_timer)(void *priv_data, double after, void *user_data);
+ void (*repeat_timer)(void *priv_data, void *ev_data);
+ void (*del_timer)(void *priv_data, void *ev_data);
+ void* (*add_periodic)(void *priv_data, double after,
+ rdns_periodic_callback cb, void *user_data);
+ void (*del_periodic)(void *priv_data, void *ev_data);
+ void (*cleanup)(void *priv_data);
+};
+
+/**
+ * Type of rdns plugin
+ */
+enum rdns_plugin_type {
+ RDNS_PLUGIN_CURVE = 0//!< use the specified plugin instead of send/recv functions
+};
+
+typedef ssize_t (*rdns_network_send_callback) (struct rdns_request *req, void *plugin_data);
+typedef ssize_t (*rdns_network_recv_callback) (struct rdns_io_channel *ioc, void *buf,
+ size_t len, void *plugin_data, struct rdns_request **req_out);
+typedef void (*rdns_network_finish_callback) (struct rdns_request *req, void *plugin_data);
+typedef void (*rdns_plugin_dtor_callback) (struct rdns_resolver *resolver, void *plugin_data);
+
+struct rdns_plugin {
+ enum rdns_plugin_type type;
+ union {
+ struct {
+ rdns_network_send_callback send_cb;
+ rdns_network_recv_callback recv_cb;
+ rdns_network_finish_callback finish_cb;
+ } curve_plugin;
+ } cb;
+ rdns_plugin_dtor_callback dtor;
+ void *data;
+};
+
+/*
+ * RDNS logger types
+ */
+/*
+ * These types are somehow compatible with glib
+ */
+enum rdns_log_level {
+ RDNS_LOG_ERROR = 1 << 3,
+ RDNS_LOG_WARNING = 1 << 4,
+ RDNS_LOG_INFO = 1 << 6,
+ RDNS_LOG_DEBUG = 1 << 7
+};
+typedef void (*rdns_log_function) (
+ void *log_data, //!< opaque data pointer
+ enum rdns_log_level level, //!< level of message
+ const char *function, //!< calling function
+ const char *format, //!< format
+ va_list args //!< set of arguments
+ );
+
+struct rdns_request_name {
+ char *name;
+ enum rdns_request_type type;
+ unsigned int len;
+};
+
+/*
+ * RDNS API
+ */
+
+/**
+ * Create DNS resolver structure
+ */
+struct rdns_resolver *rdns_resolver_new (void);
+
+/**
+ * Bind resolver to specified async context
+ * @param ctx
+ */
+void rdns_resolver_async_bind (struct rdns_resolver *resolver,
+ struct rdns_async_context *ctx);
+
+/**
+ * Add new DNS server definition to the resolver
+ * @param resolver resolver object
+ * @param name name of DNS server (should be ipv4 or ipv6 address)
+ * @param priority priority (can be 0 for fair round-robin)
+ * @param io_cnt a number of sockets that are simultaneously opened to this server
+ * @return true if a server has been added to resolver
+ */
+bool rdns_resolver_add_server (struct rdns_resolver *resolver,
+ const char *name, unsigned int port,
+ int priority, unsigned int io_cnt);
+
+
+/**
+ * Load nameservers definition from resolv.conf file
+ * @param resolver resolver object
+ * @param path path to resolv.conf file (/etc/resolv.conf typically)
+ * @return true if resolv.conf has been parsed
+ */
+bool rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver,
+ const char *path);
+
+/**
+ * Set an external logger function to log messages from the resolver
+ * @param resolver resolver object
+ * @param logger logger callback
+ * @param log_data opaque data
+ */
+void rdns_resolver_set_logger (struct rdns_resolver *resolver,
+ rdns_log_function logger, void *log_data);
+
+/**
+ * Set log level for an internal logger (stderr one)
+ * @param resolver resolver object
+ * @param level desired log level
+ */
+void rdns_resolver_set_log_level (struct rdns_resolver *resolver,
+ enum rdns_log_level level);
+
+
+/**
+ * Set maximum number of dns requests to be sent to a socket to be refreshed
+ * @param resolver resolver object
+ * @param max_ioc_uses unsigned count of socket usage limit
+ * @param check_time specifies how often to check for sockets and refresh them
+ */
+void rdns_resolver_set_max_io_uses (struct rdns_resolver *resolver,
+ uint64_t max_ioc_uses, double check_time);
+
+/**
+ * Register new plugin for rdns resolver
+ * @param resolver
+ * @param plugin
+ */
+void rdns_resolver_register_plugin (struct rdns_resolver *resolver,
+ struct rdns_plugin *plugin);
+
+/**
+ * Init DNS resolver
+ * @param resolver
+ * @return
+ */
+bool rdns_resolver_init (struct rdns_resolver *resolver);
+
+/**
+ * Decrease refcount for a resolver and free it if refcount is 0
+ * @param resolver
+ */
+void rdns_resolver_release (struct rdns_resolver *resolver);
+
+/**
+ * Make a DNS request
+ * @param resolver resolver object
+ * @param cb callback to call on resolve completing
+ * @param ud user data for callback
+ * @param timeout timeout in seconds
+ * @param repeats how much time to retransmit query
+ * @param queries how much RR queries to send
+ * @param ... -> queries in format: <query_type>[,type_argument[,type_argument...]]
+ * @return opaque request object or NULL
+ */
+struct rdns_request* rdns_make_request_full (
+ struct rdns_resolver *resolver,
+ dns_callback_type cb,
+ void *cbdata,
+ double timeout,
+ unsigned int repeats,
+ unsigned int queries,
+ ...
+ );
+
+/**
+ * Get textual presentation of DNS error code
+ */
+const char *rdns_strerror (enum dns_rcode rcode);
+
+/**
+ * Get textual presentation of DNS request type
+ */
+const char *rdns_strtype (enum rdns_request_type type);
+
+/**
+ * Increase refcount for a request
+ * @param req
+ * @return
+ */
+struct rdns_request* rdns_request_retain (struct rdns_request *req);
+
+/**
+ * Decrease refcount for a request and free it if refcount is 0
+ * @param req
+ */
+void rdns_request_release (struct rdns_request *req);
+
+/**
+ * Check whether a request contains `type` request
+ * @param req request object
+ * @param type check for a specified type
+ * @return true if `type` has been requested
+ */
+bool rdns_request_has_type (struct rdns_request *req, enum rdns_request_type type);
+
+/**
+ * Return requested name for a request
+ * @param req request object
+ * @return requested name as it was passed to `rdns_make_request`
+ */
+const struct rdns_request_name* rdns_request_get_name (struct rdns_request *req,
+ unsigned int *count);
+
+/**
+ * Return PTR string for a request (ipv4 or ipv6) addresses
+ * @param str string representation of IP address
+ * @return name to resolve or NULL if `str` is not an IP address; caller must free result when it is unused
+ */
+char * rdns_generate_ptr_from_str (const char *str);
+
+/*
+ * Private functions used by async libraries as callbacks
+ */
+
+void rdns_process_read (int fd, void *arg);
+void rdns_process_timer (void *arg);
+void rdns_process_retransmit (int fd, void *arg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/contrib/librdns/rdns_curve.h b/contrib/librdns/rdns_curve.h
new file mode 100644
index 000000000..365e91bb1
--- /dev/null
+++ b/contrib/librdns/rdns_curve.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef RDNS_CURVE_H_
+#define RDNS_CURVE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rdns_curve_ctx;
+
+/**
+ * Create new dnscurve ctx
+ * @return
+ */
+struct rdns_curve_ctx* rdns_curve_ctx_new (double rekey_interval);
+
+/**
+ * Add key for server `name`
+ * @param ctx curve context
+ * @param name name of server (ip address)
+ * @param pubkey pubkey bytes (must be `RDSN_CURVE_PUBKEY_LEN`)
+ */
+void rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx,
+ const char *name, const unsigned char *pubkey);
+
+/**
+ * Destroy curve context
+ * @param ctx
+ */
+void rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx);
+
+
+/**
+ * Register DNSCurve plugin (libsodium should be enabled for this)
+ * @param resolver
+ * @param ctx
+ */
+void rdns_curve_register_plugin (struct rdns_resolver *resolver,
+ struct rdns_curve_ctx *ctx);
+
+/**
+ * Create DNSCurve key from the base16 encoded string
+ * @param hex input hex (must be NULL terminated)
+ * @return a key or NULL (not NULL terminated)
+ */
+unsigned char * rdns_curve_key_from_hex (const char *hex);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDNS_CURVE_H_ */
diff --git a/contrib/librdns/rdns_ev.h b/contrib/librdns/rdns_ev.h
new file mode 100644
index 000000000..1b3554bc1
--- /dev/null
+++ b/contrib/librdns/rdns_ev.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef RDNS_EV_H_
+#define RDNS_EV_H_
+
+#include <ev.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rdns.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void* rdns_libev_add_read (void *priv_data, int fd, void *user_data);
+static void rdns_libev_del_read(void *priv_data, void *ev_data);
+static void* rdns_libev_add_write (void *priv_data, int fd, void *user_data);
+static void rdns_libev_del_write (void *priv_data, void *ev_data);
+static void* rdns_libev_add_timer (void *priv_data, double after, void *user_data);
+static void* rdns_libev_add_periodic (void *priv_data, double after,
+ rdns_periodic_callback cb, void *user_data);
+static void rdns_libev_del_periodic (void *priv_data, void *ev_data);
+static void rdns_libev_repeat_timer (void *priv_data, void *ev_data);
+static void rdns_libev_del_timer (void *priv_data, void *ev_data);
+
+struct rdns_ev_periodic_cbdata {
+ ev_timer *ev;
+ rdns_periodic_callback cb;
+ void *cbdata;
+};
+
+static void
+rdns_bind_libev (struct rdns_resolver *resolver, struct ev_loop *loop)
+{
+ static struct rdns_async_context ev_ctx = {
+ .data = NULL,
+ .add_read = rdns_libev_add_read,
+ .del_read = rdns_libev_del_read,
+ .add_write = rdns_libev_add_write,
+ .del_write = rdns_libev_del_write,
+ .add_timer = rdns_libev_add_timer,
+ .repeat_timer = rdns_libev_repeat_timer,
+ .del_timer = rdns_libev_del_timer,
+ .add_periodic = rdns_libev_add_periodic,
+ .del_periodic = rdns_libev_del_periodic,
+ .cleanup = NULL
+ }, *nctx;
+ void *ptr;
+
+ /* XXX: never got freed */
+ ptr = malloc (sizeof (struct rdns_async_context));
+ if (ptr != NULL) {
+ nctx = (struct rdns_async_context *)ptr;
+ memcpy (ptr, (void *)&ev_ctx, sizeof (struct rdns_async_context));
+ nctx->data = (void*)loop;
+ rdns_resolver_async_bind (resolver, nctx);
+ }
+}
+
+static void
+rdns_libev_read_event (struct ev_loop *loop, ev_io *ev, int revents)
+{
+ rdns_process_read (ev->fd, ev->data);
+}
+
+static void
+rdns_libev_write_event (struct ev_loop *loop, ev_io *ev, int revents)
+{
+ rdns_process_retransmit (ev->fd, ev->data);
+}
+
+static void
+rdns_libev_timer_event (struct ev_loop *loop, ev_timer *ev, int revents)
+{
+ rdns_process_timer (ev->data);
+}
+
+static void
+rdns_libev_periodic_event (struct ev_loop *loop, ev_timer *ev, int revents)
+{
+ struct rdns_ev_periodic_cbdata *cbdata = (struct rdns_ev_periodic_cbdata *)
+ ev->data;
+ cbdata->cb (cbdata->cbdata);
+}
+
+static void*
+rdns_libev_add_read (void *priv_data, int fd, void *user_data)
+{
+ ev_io *ev;
+ void *ptr;
+
+ ptr = malloc (sizeof (ev_io));
+ if (ptr != NULL) {
+ ev = (ev_io *)ptr;
+ ev_io_init (ev, rdns_libev_read_event, fd, EV_READ);
+ ev->data = user_data;
+ ev_io_start ((struct ev_loop *)priv_data, ev);
+ }
+ return ptr;
+}
+
+static void
+rdns_libev_del_read (void *priv_data, void *ev_data)
+{
+ ev_io *ev = (ev_io*)ev_data;
+ if (ev != NULL) {
+ ev_io_stop ((struct ev_loop *)priv_data, ev);
+ free ((void *)ev);
+ }
+}
+static void*
+rdns_libev_add_write (void *priv_data, int fd, void *user_data)
+{
+ ev_io *ev;
+
+ ev = (ev_io *)malloc (sizeof (ev_io));
+ if (ev != NULL) {
+ ev_io_init (ev, rdns_libev_write_event, fd, EV_WRITE);
+ ev->data = user_data;
+ ev_io_start ((struct ev_loop *)priv_data, ev);
+ }
+ return (void *)ev;
+}
+
+static void
+rdns_libev_del_write (void *priv_data, void *ev_data)
+{
+ ev_io *ev = (ev_io *)ev_data;
+ if (ev != NULL) {
+ ev_io_stop ((struct ev_loop *)priv_data, ev);
+ free ((void *)ev);
+ }
+}
+
+static void*
+rdns_libev_add_timer (void *priv_data, double after, void *user_data)
+{
+ ev_timer *ev;
+ ev = (ev_timer *)malloc (sizeof (ev_timer));
+ if (ev != NULL) {
+ ev_timer_init (ev, rdns_libev_timer_event, after, after);
+ ev->data = user_data;
+ ev_timer_start ((struct ev_loop *)priv_data, ev);
+ }
+ return (void *)ev;
+}
+
+static void*
+rdns_libev_add_periodic (void *priv_data, double after,
+ rdns_periodic_callback cb, void *user_data)
+{
+ ev_timer *ev;
+ struct rdns_ev_periodic_cbdata *cbdata = NULL;
+
+ ev = (ev_timer *)malloc (sizeof (ev_timer));
+ if (ev != NULL) {
+ cbdata = (struct rdns_ev_periodic_cbdata *)
+ malloc (sizeof (struct rdns_ev_periodic_cbdata));
+ if (cbdata != NULL) {
+ cbdata->cb = cb;
+ cbdata->cbdata = user_data;
+ cbdata->ev = ev;
+ ev_timer_init (ev, rdns_libev_periodic_event, after, after);
+ ev->data = cbdata;
+ ev_timer_start ((struct ev_loop *)priv_data, ev);
+ }
+ else {
+ free ((void *)ev);
+ return NULL;
+ }
+ }
+ return (void *)cbdata;
+}
+
+static void
+rdns_libev_del_periodic (void *priv_data, void *ev_data)
+{
+ struct rdns_ev_periodic_cbdata *cbdata = (struct rdns_ev_periodic_cbdata *)
+ ev_data;
+ if (cbdata != NULL) {
+ ev_timer_stop ((struct ev_loop *)priv_data, (ev_timer *)cbdata->ev);
+ free ((void *)cbdata->ev);
+ free ((void *)cbdata);
+ }
+}
+
+static void
+rdns_libev_repeat_timer (void *priv_data, void *ev_data)
+{
+ ev_timer *ev = (ev_timer *)ev_data;
+ if (ev != NULL) {
+ ev_timer_again ((struct ev_loop *)priv_data, ev);
+ }
+}
+
+static void
+rdns_libev_del_timer (void *priv_data, void *ev_data)
+{
+ ev_timer *ev = (ev_timer *)ev_data;
+ if (ev != NULL) {
+ ev_timer_stop ((struct ev_loop *)priv_data, ev);
+ free ((void *)ev);
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDNS_EV_H_ */
diff --git a/contrib/librdns/rdns_event.h b/contrib/librdns/rdns_event.h
new file mode 100644
index 000000000..b3fc64aed
--- /dev/null
+++ b/contrib/librdns/rdns_event.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef RDNS_EVENT_H_
+#define RDNS_EVENT_H_
+
+#include <event.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rdns.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void* rdns_libevent_add_read (void *priv_data, int fd, void *user_data);
+static void rdns_libevent_del_read(void *priv_data, void *ev_data);
+static void* rdns_libevent_add_write (void *priv_data, int fd, void *user_data);
+static void rdns_libevent_del_write (void *priv_data, void *ev_data);
+static void* rdns_libevent_add_timer (void *priv_data, double after, void *user_data);
+static void* rdns_libevent_add_periodic (void *priv_data, double after,
+ rdns_periodic_callback cb, void *user_data);
+static void rdns_libevent_del_periodic (void *priv_data, void *ev_data);
+static void rdns_libevent_repeat_timer (void *priv_data, void *ev_data);
+static void rdns_libevent_del_timer (void *priv_data, void *ev_data);
+
+struct rdns_event_periodic_cbdata {
+ struct event *ev;
+ rdns_periodic_callback cb;
+ void *cbdata;
+};
+
+static void
+rdns_bind_libevent (struct rdns_resolver *resolver, struct event_base *ev_base)
+{
+ struct rdns_async_context ev_ctx = {
+ .add_read = rdns_libevent_add_read,
+ .del_read = rdns_libevent_del_read,
+ .add_write = rdns_libevent_add_write,
+ .del_write = rdns_libevent_del_write,
+ .add_timer = rdns_libevent_add_timer,
+ .add_periodic = rdns_libevent_add_periodic,
+ .del_periodic = rdns_libevent_del_periodic,
+ .repeat_timer = rdns_libevent_repeat_timer,
+ .del_timer = rdns_libevent_del_timer,
+ .cleanup = NULL
+ }, *nctx;
+
+ /* XXX: never got freed */
+ nctx = malloc (sizeof (struct rdns_async_context));
+ if (nctx != NULL) {
+ memcpy (nctx, &ev_ctx, sizeof (struct rdns_async_context));
+ nctx->data = ev_base;
+ }
+ rdns_resolver_async_bind (resolver, nctx);
+}
+
+static void
+rdns_libevent_read_event (int fd, short what, void *ud)
+{
+ rdns_process_read (fd, ud);
+}
+
+static void
+rdns_libevent_write_event (int fd, short what, void *ud)
+{
+ rdns_process_retransmit (fd, ud);
+}
+
+static void
+rdns_libevent_timer_event (int fd, short what, void *ud)
+{
+ rdns_process_timer (ud);
+}
+
+static void
+rdns_libevent_periodic_event (int fd, short what, void *ud)
+{
+ struct rdns_event_periodic_cbdata *cbdata = ud;
+ cbdata->cb (cbdata->cbdata);
+}
+
+static void*
+rdns_libevent_add_read (void *priv_data, int fd, void *user_data)
+{
+ struct event *ev;
+ ev = malloc (sizeof (struct event));
+ if (ev != NULL) {
+ event_set (ev, fd, EV_READ | EV_PERSIST, rdns_libevent_read_event, user_data);
+ event_base_set (priv_data, ev);
+ event_add (ev, NULL);
+ }
+ return ev;
+}
+
+static void
+rdns_libevent_del_read(void *priv_data, void *ev_data)
+{
+ struct event *ev = ev_data;
+ if (ev != NULL) {
+ event_del (ev);
+ free (ev);
+ }
+}
+static void*
+rdns_libevent_add_write (void *priv_data, int fd, void *user_data)
+{
+ struct event *ev;
+ ev = malloc (sizeof (struct event));
+ if (ev != NULL) {
+ event_set (ev, fd, EV_WRITE | EV_PERSIST,
+ rdns_libevent_write_event, user_data);
+ event_base_set (priv_data, ev);
+ event_add (ev, NULL);
+ }
+ return ev;
+}
+
+static void
+rdns_libevent_del_write (void *priv_data, void *ev_data)
+{
+ struct event *ev = ev_data;
+ if (ev != NULL) {
+ event_del (ev);
+ free (ev);
+ }
+}
+
+#define rdns_event_double_to_tv(dbl, tv) do { \
+ (tv)->tv_sec = (int)(dbl); \
+ (tv)->tv_usec = ((dbl) - (int)(dbl))*1000*1000; \
+} while(0)
+
+static void*
+rdns_libevent_add_timer (void *priv_data, double after, void *user_data)
+{
+ struct event *ev;
+ struct timeval tv;
+ ev = malloc (sizeof (struct event));
+ if (ev != NULL) {
+ rdns_event_double_to_tv (after, &tv);
+ event_set (ev, -1, EV_TIMEOUT|EV_PERSIST, rdns_libevent_timer_event, user_data);
+ event_base_set (priv_data, ev);
+ event_add (ev, &tv);
+ }
+ return ev;
+}
+
+static void*
+rdns_libevent_add_periodic (void *priv_data, double after,
+ rdns_periodic_callback cb, void *user_data)
+{
+ struct event *ev;
+ struct timeval tv;
+ struct rdns_event_periodic_cbdata *cbdata = NULL;
+
+ ev = malloc (sizeof (struct event));
+ if (ev != NULL) {
+ cbdata = malloc (sizeof (struct rdns_event_periodic_cbdata));
+ if (cbdata != NULL) {
+ rdns_event_double_to_tv (after, &tv);
+ cbdata->cb = cb;
+ cbdata->cbdata = user_data;
+ cbdata->ev = ev;
+ event_set (ev, -1, EV_TIMEOUT|EV_PERSIST, rdns_libevent_periodic_event, cbdata);
+ event_base_set (priv_data, ev);
+ event_add (ev, &tv);
+ }
+ else {
+ free (ev);
+ return NULL;
+ }
+ }
+ return cbdata;
+}
+
+static void
+rdns_libevent_del_periodic (void *priv_data, void *ev_data)
+{
+ struct rdns_event_periodic_cbdata *cbdata = ev_data;
+ if (cbdata != NULL) {
+ event_del (cbdata->ev);
+ free (cbdata->ev);
+ free (cbdata);
+ }
+}
+
+static void
+rdns_libevent_repeat_timer (void *priv_data, void *ev_data)
+{
+ /* XXX: libevent hides timeval, so timeouts are persistent here */
+}
+
+#undef rdns_event_double_to_tv
+
+static void
+rdns_libevent_del_timer (void *priv_data, void *ev_data)
+{
+ struct event *ev = ev_data;
+ if (ev != NULL) {
+ event_del (ev);
+ free (ev);
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDNS_EV_H_ */
diff --git a/contrib/librdns/ref.h b/contrib/librdns/ref.h
new file mode 100644
index 000000000..a8016b16d
--- /dev/null
+++ b/contrib/librdns/ref.h
@@ -0,0 +1,71 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 REF_H_
+#define REF_H_
+
+/**
+ * @file ref.h
+ * A set of macros to handle refcounts
+ */
+
+typedef void (*ref_dtor_cb_t)(void *data);
+
+typedef struct ref_entry_s {
+ unsigned int refcount;
+ ref_dtor_cb_t dtor;
+} ref_entry_t;
+
+#define REF_INIT(obj, dtor_cb) do { \
+ (obj)->ref.refcount = 0; \
+ (obj)->ref.dtor = (ref_dtor_cb_t)(dtor_cb); \
+} while (0)
+
+#define REF_INIT_RETAIN(obj, dtor_cb) do { \
+ (obj)->ref.refcount = 1; \
+ (obj)->ref.dtor = (ref_dtor_cb_t)(dtor_cb); \
+} while (0)
+
+#ifdef HAVE_ATOMIC_BUILTINS
+#define REF_RETAIN(obj) do { \
+ __sync_add_and_fetch (&(obj)->ref.refcount, 1); \
+} while (0)
+
+#define REF_RELEASE(obj) do { \
+ unsigned int rc = __sync_sub_and_fetch (&(obj)->ref.refcount, 1); \
+ if (rc == 0 && (obj)->ref.dtor) { \
+ (obj)->ref.dtor (obj); \
+ } \
+} while (0)
+#else
+#define REF_RETAIN(obj) do { \
+ (obj)->ref.refcount ++; \
+} while (0)
+
+#define REF_RELEASE(obj) do { \
+ if (--(obj)->ref.refcount == 0 && (obj)->ref.dtor) { \
+ (obj)->ref.dtor (obj); \
+ } \
+} while (0)
+#endif
+
+#endif /* REF_H_ */
diff --git a/contrib/librdns/resolver.c b/contrib/librdns/resolver.c
new file mode 100644
index 000000000..9741f7bf9
--- /dev/null
+++ b/contrib/librdns/resolver.c
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include "rdns.h"
+#include "dns_private.h"
+#include "ottery.h"
+#include "util.h"
+#include "packet.h"
+#include "parse.h"
+#include "logger.h"
+#include "compression.h"
+
+static int
+rdns_send_request (struct rdns_request *req, int fd, bool new_req)
+{
+ int r;
+ struct rdns_server *serv = req->io->srv;
+ struct rdns_resolver *resolver = req->resolver;
+ struct rdns_request *tmp;
+ struct dns_header *header;
+ const int max_id_cycles = 32;
+
+ /* Find ID collision */
+ if (new_req) {
+ r = 0;
+ HASH_FIND_INT (req->io->requests, &req->id, tmp);
+ while (tmp != NULL) {
+ /* Check for unique id */
+ header = (struct dns_header *)req->packet;
+ header->qid = rdns_permutor_generate_id ();
+ req->id = header->qid;
+ if (++r > max_id_cycles) {
+ return -1;
+ }
+ HASH_FIND_INT (req->io->requests, &req->id, tmp);
+ }
+ }
+
+ if (resolver->curve_plugin == NULL) {
+ r = send (fd, req->packet, req->pos, 0);
+ }
+ else {
+ r = resolver->curve_plugin->cb.curve_plugin.send_cb (req,
+ resolver->curve_plugin->data);
+ }
+ if (r == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ if (new_req) {
+ /* Write when socket is ready */
+ HASH_ADD_INT (req->io->requests, id, req);
+ req->async_event = resolver->async->add_write (resolver->async->data,
+ fd, req);
+ }
+ /*
+ * If request is already processed then the calling function
+ * should take care about events processing
+ */
+ return 0;
+ }
+ else {
+ rdns_debug ("send failed: %s for server %s", strerror (errno), serv->name);
+ return -1;
+ }
+ }
+
+ if (new_req) {
+ /* Add request to hash table */
+ HASH_ADD_INT (req->io->requests, id, req);
+ /* Fill timeout */
+ req->async_event = resolver->async->add_timer (resolver->async->data,
+ req->timeout, req);
+ req->state = RDNS_REQUEST_SENT;
+ }
+
+ return 1;
+}
+
+
+static struct rdns_reply *
+rdns_make_reply (struct rdns_request *req, enum dns_rcode rcode)
+{
+ struct rdns_reply *rep;
+
+ rep = malloc (sizeof (struct rdns_reply));
+ if (rep != NULL) {
+ rep->request = req;
+ rep->resolver = req->resolver;
+ rep->entries = NULL;
+ rep->code = rcode;
+ req->reply = rep;
+ }
+
+ return rep;
+}
+
+static struct rdns_request *
+rdns_find_dns_request (uint8_t *in, struct rdns_io_channel *ioc)
+{
+ struct dns_header *header = (struct dns_header *)in;
+ struct rdns_request *req;
+ int id;
+ struct rdns_resolver *resolver = ioc->resolver;
+
+ id = header->qid;
+ HASH_FIND_INT (ioc->requests, &id, req);
+ if (req == NULL) {
+ /* No such requests found */
+ rdns_debug ("DNS request with id %d has not been found for IO channel", (int)id);
+ }
+
+ return req;
+}
+
+static bool
+rdns_parse_reply (uint8_t *in, int r, struct rdns_request *req,
+ struct rdns_reply **_rep)
+{
+ struct dns_header *header = (struct dns_header *)in;
+ struct rdns_reply *rep;
+ struct rdns_reply_entry *elt;
+ uint8_t *pos, *npos;
+ struct rdns_resolver *resolver = req->resolver;
+ uint16_t qdcount;
+ int type;
+ bool found = false;
+
+ int i, t;
+
+ /* First check header fields */
+ if (header->qr == 0) {
+ rdns_info ("got request while waiting for reply");
+ return false;
+ }
+
+ qdcount = ntohs (header->qdcount);
+
+ if (qdcount != req->qcount) {
+ rdns_info ("request has %d queries, reply has %d queries", (int)req->qcount, (int)header->qdcount);
+ return false;
+ }
+
+ /*
+ * Now we have request and query data is now at the end of header, so compare
+ * request QR section and reply QR section
+ */
+ req->pos = sizeof (struct dns_header);
+ pos = in + sizeof (struct dns_header);
+ t = r - sizeof (struct dns_header);
+ for (i = 0; i < (int)qdcount; i ++) {
+ if ((npos = rdns_request_reply_cmp (req, pos,t)) == NULL) {
+ rdns_info ("DNS request with id %d is for different query, ignoring", (int)req->id);
+ return false;
+ }
+ t -= npos - pos;
+ pos = npos;
+ }
+ /*
+ * Now pos is in answer section, so we should extract data and form reply
+ */
+ rep = rdns_make_reply (req, header->rcode);
+
+ if (rep == NULL) {
+ rdns_warn ("Cannot allocate memory for reply");
+ return false;
+ }
+
+ type = req->requested_names[0].type;
+
+ if (rep->code == RDNS_RC_NOERROR) {
+ r -= pos - in;
+ /* Extract RR records */
+ for (i = 0; i < ntohs (header->ancount); i ++) {
+ elt = malloc (sizeof (struct rdns_reply_entry));
+ t = rdns_parse_rr (resolver, in, elt, &pos, rep, &r);
+ if (t == -1) {
+ free (elt);
+ rdns_debug ("incomplete reply");
+ break;
+ }
+ else if (t == 1) {
+ DL_APPEND (rep->entries, elt);
+ if (elt->type == type) {
+ found = true;
+ }
+ }
+ else {
+ rdns_debug ("no matching reply for %s",
+ req->requested_names[0].name);
+ free (elt);
+ }
+ }
+ }
+
+ if (!found && type != RDNS_REQUEST_ANY) {
+ /* We have not found the requested RR type */
+ rep->code = RDNS_RC_NOREC;
+ }
+
+ *_rep = rep;
+ return true;
+}
+
+static void
+rdns_request_unschedule (struct rdns_request *req)
+{
+ req->async->del_timer (req->async->data,
+ req->async_event);
+ /* Remove from id hashes */
+ HASH_DEL (req->io->requests, req);
+}
+
+void
+rdns_process_read (int fd, void *arg)
+{
+ struct rdns_io_channel *ioc = arg;
+ struct rdns_resolver *resolver;
+ struct rdns_request *req = NULL;
+ ssize_t r;
+ struct rdns_reply *rep;
+ uint8_t in[UDP_PACKET_SIZE];
+
+ resolver = ioc->resolver;
+
+ /* First read packet from socket */
+ if (resolver->curve_plugin == NULL) {
+ r = read (fd, in, sizeof (in));
+ if (r > (int)(sizeof (struct dns_header) + sizeof (struct dns_query))) {
+ req = rdns_find_dns_request (in, ioc);
+ }
+ }
+ else {
+ r = resolver->curve_plugin->cb.curve_plugin.recv_cb (ioc, in,
+ sizeof (in), resolver->curve_plugin->data, &req);
+ if (req == NULL &&
+ r > (int)(sizeof (struct dns_header) + sizeof (struct dns_query))) {
+ req = rdns_find_dns_request (in, ioc);
+ }
+ }
+
+ if (req != NULL) {
+ if (rdns_parse_reply (in, r, req, &rep)) {
+ UPSTREAM_OK (req->io->srv);
+ req->state = RDNS_REQUEST_REPLIED;
+ rdns_request_unschedule (req);
+ req->func (rep, req->arg);
+ REF_RELEASE (req);
+ }
+ }
+ else {
+ /* Still want to increase uses */
+ ioc->uses ++;
+ }
+}
+
+void
+rdns_process_timer (void *arg)
+{
+ struct rdns_request *req = (struct rdns_request *)arg;
+ struct rdns_reply *rep;
+ int r;
+ bool renew = false;
+ struct rdns_resolver *resolver;
+ struct rdns_server *serv = NULL;
+
+ req->retransmits --;
+ resolver = req->resolver;
+
+ if (req->retransmits == 0) {
+ UPSTREAM_FAIL (req->io->srv, time (NULL));
+ rep = rdns_make_reply (req, RDNS_RC_TIMEOUT);
+ req->state = RDNS_REQUEST_REPLIED;
+ rdns_request_unschedule (req);
+ req->func (rep, req->arg);
+ REF_RELEASE (req);
+
+ return;
+ }
+
+ if (!req->io->active) {
+ /* Do not reschedule IO requests on inactive sockets */
+ rdns_debug ("reschedule request with id: %d", (int)req->id);
+ rdns_request_unschedule (req);
+ REF_RELEASE (req->io);
+
+ UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv);
+
+ if (serv == NULL) {
+ rdns_warn ("cannot find suitable server for request");
+ rep = rdns_make_reply (req, RDNS_RC_SERVFAIL);
+ req->state = RDNS_REQUEST_REPLIED;
+ req->func (rep, req->arg);
+ REF_RELEASE (req);
+ }
+
+ /* Select random IO channel */
+ req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt];
+ req->io->uses ++;
+ REF_RETAIN (req->io);
+ renew = true;
+ }
+
+ r = rdns_send_request (req, req->io->sock, renew);
+ if (r == 0) {
+ /* Retransmit one more time */
+ req->async->del_timer (req->async->data,
+ req->async_event);
+ req->async_event = req->async->add_write (req->async->data,
+ req->io->sock, req);
+ req->state = RDNS_REQUEST_REGISTERED;
+ }
+ else if (r == -1) {
+ UPSTREAM_FAIL (req->io->srv, time (NULL));
+ rep = rdns_make_reply (req, RDNS_RC_NETERR);
+ req->state = RDNS_REQUEST_REPLIED;
+ rdns_request_unschedule (req);
+ req->func (rep, req->arg);
+ REF_RELEASE (req);
+ }
+ else {
+ req->async->repeat_timer (req->async->data, req->async_event);
+ }
+}
+
+static void
+rdns_process_periodic (void *arg)
+{
+ struct rdns_resolver *resolver = (struct rdns_resolver*)arg;
+
+ UPSTREAM_RESCAN (resolver->servers, time (NULL));
+}
+
+static void
+rdns_process_ioc_refresh (void *arg)
+{
+ struct rdns_resolver *resolver = (struct rdns_resolver*)arg;
+ struct rdns_server *serv;
+ struct rdns_io_channel *ioc, *nioc;
+ unsigned int i;
+
+ if (resolver->max_ioc_uses > 0) {
+ UPSTREAM_FOREACH (resolver->servers, serv) {
+ for (i = 0; i < serv->io_cnt; i ++) {
+ ioc = serv->io_channels[i];
+ if (ioc->uses > resolver->max_ioc_uses) {
+ /* Schedule IOC removing */
+ nioc = calloc (1, sizeof (struct rdns_io_channel));
+ if (nioc == NULL) {
+ rdns_err ("calloc fails to allocate rdns_io_channel");
+ continue;
+ }
+ nioc->sock = rdns_make_client_socket (serv->name, serv->port,
+ SOCK_DGRAM);
+ if (nioc->sock == -1) {
+ rdns_err ("cannot open socket to %s: %s", serv->name,
+ strerror (errno));
+ free (nioc);
+ continue;
+ }
+ nioc->srv = serv;
+ nioc->active = true;
+ nioc->resolver = resolver;
+ nioc->async_io = resolver->async->add_read (resolver->async->data,
+ nioc->sock, nioc);
+ REF_INIT_RETAIN (nioc, rdns_ioc_free);
+ serv->io_channels[i] = nioc;
+ rdns_debug ("scheduled io channel for server %s to be refreshed after "
+ "%lu usages", serv->name, (unsigned long)ioc->uses);
+ ioc->active = false;
+ REF_RELEASE (ioc);
+ }
+ }
+ }
+ }
+}
+
+void
+rdns_process_retransmit (int fd, void *arg)
+{
+ struct rdns_request *req = (struct rdns_request *)arg;
+ struct rdns_resolver *resolver;
+ struct rdns_reply *rep;
+ int r;
+
+ resolver = req->resolver;
+
+ resolver->async->del_write (resolver->async->data,
+ req->async_event);
+
+ r = rdns_send_request (req, fd, false);
+
+ if (r == 0) {
+ /* Retransmit one more time */
+ req->async_event = req->async->add_write (req->async->data,
+ fd, req);
+ req->state = RDNS_REQUEST_REGISTERED;
+ }
+ else if (r == -1) {
+ UPSTREAM_FAIL (req->io->srv, time (NULL));
+ rep = rdns_make_reply (req, RDNS_RC_NETERR);
+ req->state = RDNS_REQUEST_REPLIED;
+ req->func (rep, req->arg);
+ REF_RELEASE (req);
+ }
+ else {
+ req->async_event = req->async->add_timer (req->async->data,
+ req->timeout, req);
+ req->state = RDNS_REQUEST_SENT;
+ }
+}
+
+struct rdns_request*
+rdns_make_request_full (
+ struct rdns_resolver *resolver,
+ dns_callback_type cb,
+ void *cbdata,
+ double timeout,
+ unsigned int repeats,
+ unsigned int queries,
+ ...
+ )
+{
+ va_list args;
+ struct rdns_request *req;
+ struct rdns_server *serv;
+ int r, type;
+ unsigned int i, tlen = 0, clen = 0, cur;
+ size_t olen;
+ const char *cur_name, *last_name = NULL;
+ struct rdns_compression_entry *comp = NULL;
+
+ if (!resolver->initialized) {
+ return NULL;
+ }
+
+ req = malloc (sizeof (struct rdns_request));
+ if (req == NULL) {
+ return NULL;
+ }
+
+ req->resolver = resolver;
+ req->func = cb;
+ req->arg = cbdata;
+ req->reply = NULL;
+ req->qcount = queries;
+ req->io = NULL;
+ req->state = RDNS_REQUEST_NEW;
+ req->packet = NULL;
+ req->requested_names = calloc (queries, sizeof (struct rdns_request_name));
+ if (req->requested_names == NULL) {
+ free (req);
+ return NULL;
+ }
+
+ req->type = 0;
+#ifdef TWEETNACL
+ req->curve_plugin_data = NULL;
+#endif
+ REF_INIT_RETAIN (req, rdns_request_free);
+
+ /* Calculate packet's total length based on records count */
+ va_start (args, queries);
+ for (i = 0; i < queries * 2; i += 2) {
+ cur = i / 2;
+ cur_name = va_arg (args, const char *);
+ if (cur_name != NULL) {
+ last_name = cur_name;
+ clen = strlen (cur_name);
+ if (clen == 0) {
+ rdns_info ("got empty name to resolve");
+ rdns_request_free (req);
+ return NULL;
+ }
+ tlen += clen;
+ }
+ else if (last_name == NULL) {
+ rdns_info ("got NULL as the first name to resolve");
+ rdns_request_free (req);
+ return NULL;
+ }
+
+ if (!rdns_format_dns_name (resolver, last_name, clen,
+ &req->requested_names[cur].name, &olen)) {
+ rdns_request_free (req);
+ return NULL;
+ }
+
+ type = va_arg (args, int);
+ req->requested_names[cur].type = type;
+ req->requested_names[cur].len = olen;
+ }
+ va_end (args);
+
+ rdns_allocate_packet (req, tlen);
+ rdns_make_dns_header (req, queries);
+
+ for (i = 0; i < queries; i ++) {
+ cur_name = req->requested_names[i].name;
+ clen = req->requested_names[i].len;
+ type = req->requested_names[i].type;
+ if (queries > 1) {
+ if (!rdns_add_rr (req, cur_name, clen, type, &comp)) {
+ REF_RELEASE (req);
+ rnds_compression_free (comp);
+ return NULL;
+ }
+ }
+ else {
+ if (!rdns_add_rr (req, cur_name, clen, type, NULL)) {
+ REF_RELEASE (req);
+ rnds_compression_free (comp);
+ return NULL;
+ }
+ }
+ }
+
+ rnds_compression_free (comp);
+
+ /* Add EDNS RR */
+ rdns_add_edns0 (req);
+
+ req->retransmits = repeats;
+ req->timeout = timeout;
+ req->state = RDNS_REQUEST_NEW;
+ req->async = resolver->async;
+
+ UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv);
+
+ if (serv == NULL) {
+ rdns_warn ("cannot find suitable server for request");
+ REF_RELEASE (req);
+ return NULL;
+ }
+
+ /* Select random IO channel */
+ req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt];
+ req->io->uses ++;
+
+ /* Now send request to server */
+ r = rdns_send_request (req, req->io->sock, true);
+
+ if (r == -1) {
+ REF_RELEASE (req);
+ return NULL;
+ }
+
+ REF_RETAIN (req->io);
+ REF_RETAIN (req->resolver);
+
+ return req;
+}
+
+bool
+rdns_resolver_init (struct rdns_resolver *resolver)
+{
+ unsigned int i;
+ struct rdns_server *serv;
+ struct rdns_io_channel *ioc;
+
+ if (!resolver->async_binded) {
+ return false;
+ }
+
+ if (resolver->servers == NULL) {
+ return false;
+ }
+
+ /* Now init io channels to all servers */
+ UPSTREAM_FOREACH (resolver->servers, serv) {
+ serv->io_channels = calloc (serv->io_cnt, sizeof (struct rdns_io_channel *));
+ for (i = 0; i < serv->io_cnt; i ++) {
+ ioc = calloc (1, sizeof (struct rdns_io_channel));
+ if (ioc == NULL) {
+ rdns_err ("cannot allocate memory for the resolver");
+ return false;
+ }
+ ioc->sock = rdns_make_client_socket (serv->name, serv->port, SOCK_DGRAM);
+ ioc->active = true;
+ if (ioc->sock == -1) {
+ rdns_err ("cannot open socket to %s:%d %s", serv->name, serv->port, strerror (errno));
+ free (ioc);
+ return false;
+ }
+ else {
+ ioc->srv = serv;
+ ioc->resolver = resolver;
+ ioc->async_io = resolver->async->add_read (resolver->async->data,
+ ioc->sock, ioc);
+ REF_INIT_RETAIN (ioc, rdns_ioc_free);
+ serv->io_channels[i] = ioc;
+ }
+ }
+ }
+
+ if (resolver->async->add_periodic) {
+ resolver->periodic = resolver->async->add_periodic (resolver->async->data,
+ UPSTREAM_REVIVE_TIME, rdns_process_periodic, resolver);
+ }
+
+ resolver->initialized = true;
+
+ return true;
+}
+
+void
+rdns_resolver_register_plugin (struct rdns_resolver *resolver,
+ struct rdns_plugin *plugin)
+{
+ if (resolver != NULL && plugin != NULL) {
+ /* XXX: support only network plugin now, and only a single one */
+ if (plugin->type == RDNS_PLUGIN_CURVE) {
+ resolver->curve_plugin = plugin;
+ }
+ }
+}
+
+bool
+rdns_resolver_add_server (struct rdns_resolver *resolver,
+ const char *name, unsigned int port,
+ int priority, unsigned int io_cnt)
+{
+ struct rdns_server *serv;
+ union {
+ struct in_addr v4;
+ struct in6_addr v6;
+ } addr;
+
+ if (inet_pton (AF_INET, name, &addr) == 0 &&
+ inet_pton (AF_INET6, name, &addr) == 0) {
+ /* Invalid IP */
+ return false;
+ }
+
+ if (io_cnt == 0) {
+ return false;
+ }
+ if (port == 0 || port > UINT16_MAX) {
+ return false;
+ }
+
+ serv = calloc (1, sizeof (struct rdns_server));
+ if (serv == NULL) {
+ return false;
+ }
+ serv->name = strdup (name);
+ if (serv->name == NULL) {
+ free (serv);
+ return false;
+ }
+
+ serv->io_cnt = io_cnt;
+ serv->port = port;
+
+ UPSTREAM_ADD (resolver->servers, serv, priority);
+
+ return true;
+}
+
+void
+rdns_resolver_set_logger (struct rdns_resolver *resolver,
+ rdns_log_function logger, void *log_data)
+{
+ resolver->logger = logger;
+ resolver->log_data = log_data;
+}
+
+void
+rdns_resolver_set_log_level (struct rdns_resolver *resolver,
+ enum rdns_log_level level)
+{
+ resolver->log_level = level;
+}
+
+
+void
+rdns_resolver_set_max_io_uses (struct rdns_resolver *resolver,
+ uint64_t max_ioc_uses, double check_time)
+{
+ if (resolver->refresh_ioc_periodic != NULL) {
+ resolver->async->del_periodic (resolver->async->data,
+ resolver->refresh_ioc_periodic);
+ resolver->refresh_ioc_periodic = NULL;
+ }
+
+ resolver->max_ioc_uses = max_ioc_uses;
+ if (check_time > 0.0 && resolver->async->add_periodic) {
+ resolver->refresh_ioc_periodic =
+ resolver->async->add_periodic (resolver->async->data,
+ check_time, rdns_process_ioc_refresh, resolver);
+ }
+}
+
+static void
+rdns_resolver_free (struct rdns_resolver *resolver)
+{
+ struct rdns_server *serv, *stmp;
+ struct rdns_io_channel *ioc;
+ unsigned int i;
+
+ if (resolver->initialized) {
+ if (resolver->periodic != NULL) {
+ resolver->async->del_periodic (resolver->async->data, resolver->periodic);
+ }
+ if (resolver->refresh_ioc_periodic != NULL) {
+ resolver->async->del_periodic (resolver->async->data,
+ resolver->refresh_ioc_periodic);
+ }
+ if (resolver->curve_plugin != NULL && resolver->curve_plugin->dtor != NULL) {
+ resolver->curve_plugin->dtor (resolver, resolver->curve_plugin->data);
+ }
+ /* Stop IO watch on all IO channels */
+ UPSTREAM_FOREACH_SAFE (resolver->servers, serv, stmp) {
+ for (i = 0; i < serv->io_cnt; i ++) {
+ ioc = serv->io_channels[i];
+ REF_RELEASE (ioc);
+ }
+ serv->io_cnt = 0;
+ UPSTREAM_DEL (resolver->servers, serv);
+ free (serv->io_channels);
+ free (serv->name);
+ free (serv);
+ }
+ }
+ free (resolver->async);
+ free (resolver);
+}
+
+
+struct rdns_resolver *
+rdns_resolver_new (void)
+{
+ struct rdns_resolver *new;
+
+ new = calloc (1, sizeof (struct rdns_resolver));
+
+ REF_INIT_RETAIN (new, rdns_resolver_free);
+
+ new->logger = rdns_logger_internal;
+ new->log_data = new;
+
+ return new;
+}
+
+void
+rdns_resolver_async_bind (struct rdns_resolver *resolver,
+ struct rdns_async_context *ctx)
+{
+ if (resolver != NULL && ctx != NULL) {
+ resolver->async = ctx;
+ resolver->async_binded = true;
+ }
+}
diff --git a/contrib/librdns/upstream.h b/contrib/librdns/upstream.h
new file mode 100644
index 000000000..9646f89aa
--- /dev/null
+++ b/contrib/librdns/upstream.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UPSTREAM_H_
+#define UPSTREAM_H_
+
+#include <time.h>
+#include <stdio.h>
+
+/**
+ * @file upstream.h
+ * The basic macros to define upstream objects
+ */
+
+#ifndef upstream_fatal
+#define upstream_fatal(msg) do { perror (msg); exit (-1); } while (0)
+#endif
+
+#ifndef upstream_malloc
+#define upstream_malloc(size) malloc (size)
+#endif
+
+#ifndef upstream_free
+#define upstream_free(size, ptr) free (ptr)
+#endif
+
+struct upstream_entry_s;
+struct upstream_common_data {
+ void **upstreams;
+ unsigned int allocated_nelts;
+ unsigned int nelts;
+ unsigned int alive;
+};
+
+typedef struct upstream_entry_s {
+ unsigned short errors; /**< errors for this upstream */
+ unsigned short dead;
+ unsigned short priority;
+ unsigned short weight;
+ time_t time; /**< time of marking */
+ void *parent; /**< parent object */
+ struct upstream_common_data *common; /**< common data */
+ void *next; /**< link to the next */
+} upstream_entry_t;
+
+/*
+ * Here we define some reasonable defaults:
+ * if an upstream has more than `UPSTREAM_MAX_ERRORS` in the period of time
+ * of `UPSTREAM_ERROR_TIME` then we shut it down for `UPSTREAM_REVIVE_TIME`.
+ * In this particular case times are 10 seconds for 10 errors and revive in
+ * 30 seconds.
+ */
+#ifndef UPSTREAM_REVIVE_TIME
+#define UPSTREAM_REVIVE_TIME 30
+#endif
+#ifndef UPSTREAM_ERROR_TIME
+#define UPSTREAM_ERROR_TIME 10
+#endif
+#ifndef UPSTREAM_MAX_ERRORS
+#define UPSTREAM_MAX_ERRORS 10
+#endif
+
+#define UPSTREAM_FAIL(u, now) do { \
+ if ((u)->up.time != 0) { \
+ if ((now) - (u)->up.time >= UPSTREAM_ERROR_TIME) { \
+ if ((u)->up.errors >= UPSTREAM_MAX_ERRORS) { \
+ (u)->up.dead = 1; \
+ (u)->up.time = now; \
+ (u)->up.common->alive --; \
+ } \
+ else { \
+ (u)->up.errors = 1; \
+ (u)->up.time = (now); \
+ } \
+ } \
+ else { \
+ (u)->up.errors ++; \
+ } \
+ } \
+ else { \
+ (u)->up.errors ++; \
+ (u)->up.time = (now); \
+ } \
+} while (0)
+
+#define UPSTREAM_OK(u) do { \
+ (u)->up.errors = 0; \
+ (u)->up.time = 0; \
+} while (0)
+
+#define UPSTREAM_ADD(head, u, priority) do { \
+ if (head == NULL) { \
+ struct upstream_common_data *cd; \
+ cd = upstream_malloc (sizeof (struct upstream_common_data)); \
+ if (cd == NULL) { \
+ upstream_fatal ("malloc failed"); \
+ } \
+ cd->upstreams = upstream_malloc (sizeof (void *) * 8); \
+ if (cd == NULL) { \
+ upstream_fatal ("malloc failed"); \
+ } \
+ cd->allocated_nelts = 8; \
+ cd->nelts = 1; \
+ cd->alive = 1; \
+ cd->upstreams[0] = (u); \
+ (u)->up.common = cd; \
+ } \
+ else { \
+ struct upstream_common_data *cd = (head)->up.common; \
+ (u)->up.common = cd; \
+ if (cd->nelts == cd->allocated_nelts) { \
+ void **nup; \
+ nup = upstream_malloc (sizeof (void *) * cd->nelts * 2); \
+ if (nup == NULL) { \
+ upstream_fatal ("malloc failed"); \
+ } \
+ memcpy (nup, cd->upstreams, cd->nelts * sizeof (void *)); \
+ upstream_free (cd->nelts * sizeof (void *), cd->upstreams); \
+ cd->upstreams = nup; \
+ cd->allocated_nelts *= 2; \
+ } \
+ cd->upstreams[cd->nelts++] = (u); \
+ cd->alive ++; \
+ } \
+ (u)->up.next = (head); \
+ (head) = (u); \
+ if (priority > 0) { \
+ (u)->up.priority = (u)->up.weight = (priority); \
+ } \
+ else { \
+ (u)->up.priority = (u)->up.weight = 65535; \
+ } \
+ (u)->up.time = 0; \
+ (u)->up.errors = 0; \
+ (u)->up.dead = 0; \
+ (u)->up.parent = (u); \
+} while (0)
+
+#define UPSTREAM_DEL(head, u) do { \
+ if (head != NULL) { \
+ struct upstream_common_data *cd = (head)->up.common; \
+ if ((u)->up.next != NULL) { \
+ (head) = (u)->up.next; \
+ cd->nelts --; \
+ cd->alive --; \
+ } \
+ else { \
+ upstream_free (cd->allocated_nelts * sizeof (void *), \
+ cd->upstreams); \
+ upstream_free (sizeof (struct upstream_common_data), cd); \
+ (head) = NULL; \
+ } \
+ } \
+} while (0)
+
+#define UPSTREAM_FOREACH(head, u) for ((u) = (head); (u) != NULL; (u) = (u)->up.next)
+#define UPSTREAM_FOREACH_SAFE(head, u, tmp) \
+ for ((u) = (head); \
+ (u) != NULL && ((tmp = (u)->up.next) || true); \
+ (u) = (tmp))
+
+#define UPSTREAM_REVIVE_ALL(head) do { \
+ __typeof(head) elt = (head); \
+ while (elt != NULL) { \
+ elt->up.dead = 0; \
+ elt->up.errors = 0; \
+ elt->up.time = 0; \
+ elt = elt->up.next; \
+ } \
+ (head)->up.common->alive = (head)->up.common->nelts; \
+} while (0)
+
+#define UPSTREAM_RESCAN(head, now) do { \
+ __typeof(head) elt = (head); \
+ if ((head)->up.common->alive == 0) { \
+ UPSTREAM_REVIVE_ALL((head)); \
+ } \
+ else { \
+ while (elt != NULL) { \
+ if (elt->up.dead) { \
+ if ((now) - elt->up.time >= UPSTREAM_REVIVE_TIME) { \
+ elt->up.dead = 0; \
+ elt->up.errors = 0; \
+ elt->up.weight = elt->up.priority; \
+ (head)->up.common->alive ++; \
+ } \
+ } \
+ else { \
+ if ((now) - elt->up.time >= UPSTREAM_ERROR_TIME && \
+ elt->up.errors >= UPSTREAM_MAX_ERRORS) { \
+ elt->up.dead = 1; \
+ elt->up.time = now; \
+ (head)->up.common->alive --; \
+ } \
+ } \
+ elt = elt->up.next; \
+ } \
+ } \
+} while (0)
+
+#define UPSTREAM_SELECT_ROUND_ROBIN(head, selected) do { \
+ __typeof(head) elt = (head); \
+ (selected) = NULL; \
+ int alive = 0; \
+ unsigned max_weight = 0; \
+ if ((head)->up.common->alive == 0){ \
+ UPSTREAM_REVIVE_ALL(head); \
+ } \
+ while (elt != NULL) { \
+ if (!elt->up.dead) { \
+ if (elt->up.weight > max_weight) { \
+ max_weight = elt->up.weight; \
+ (selected) = elt; \
+ } \
+ alive ++; \
+ } \
+ elt = elt->up.next; \
+ } \
+ if (max_weight == 0) { \
+ elt = (head); \
+ while (elt != NULL) { \
+ elt->up.weight = elt->up.priority; \
+ if (!elt->up.dead) { \
+ if (elt->up.priority > max_weight) { \
+ max_weight = elt->up.priority; \
+ (selected) = elt; \
+ } \
+ } \
+ elt = elt->up.next; \
+ } \
+ } \
+ (selected)->up.weight --; \
+} while (0)
+
+#endif /* UPSTREAM_H_ */
diff --git a/contrib/librdns/util.c b/contrib/librdns/util.c
new file mode 100644
index 000000000..7d9dc98d4
--- /dev/null
+++ b/contrib/librdns/util.c
@@ -0,0 +1,523 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "ottery.h"
+#include "util.h"
+#include "logger.h"
+
+static int
+rdns_make_socket_nonblocking (int fd)
+{
+ int ofl;
+
+ ofl = fcntl (fd, F_GETFL, 0);
+
+ if (fcntl (fd, F_SETFL, ofl | O_NONBLOCK) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+rdns_make_inet_socket (int type, struct addrinfo *addr)
+{
+ int fd, r, s_error;
+ socklen_t optlen;
+ struct addrinfo *cur;
+
+ cur = addr;
+ while (cur) {
+ /* Create socket */
+ fd = socket (cur->ai_family, type, 0);
+ if (fd == -1) {
+ goto out;
+ }
+
+ if (rdns_make_socket_nonblocking (fd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) {
+ goto out;
+ }
+
+ r = connect (fd, cur->ai_addr, cur->ai_addrlen);
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof (s_error);
+ getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen);
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+ break;
+out:
+ if (fd != -1) {
+ close (fd);
+ }
+ fd = -1;
+ cur = cur->ai_next;
+ }
+ return (fd);
+}
+
+static int
+rdns_make_unix_socket (const char *path, struct sockaddr_un *addr, int type)
+{
+ int fd = -1, s_error, r, serrno;
+ socklen_t optlen;
+
+ if (path == NULL) {
+ return -1;
+ }
+
+ addr->sun_family = AF_UNIX;
+
+ memset (addr->sun_path, 0, sizeof (addr->sun_path));
+ memccpy (addr->sun_path, path, 0, sizeof (addr->sun_path) - 1);
+#ifdef FREEBSD
+ addr->sun_len = SUN_LEN (addr);
+#endif
+
+ fd = socket (PF_LOCAL, type, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ if (rdns_make_socket_nonblocking (fd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) {
+ goto out;
+ }
+
+ r = connect (fd, (struct sockaddr *)addr, SUN_LEN (addr));
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof (s_error);
+ getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen);
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+
+ return (fd);
+
+ out:
+ serrno = errno;
+ if (fd != -1) {
+ close (fd);
+ }
+ errno = serrno;
+ return (-1);
+}
+
+/**
+ * Make a universal socket
+ * @param credits host, ip or path to unix socket
+ * @param port port (used for network sockets)
+ * @param async make this socket asynced
+ * @param is_server make this socket as server socket
+ * @param try_resolve try name resolution for a socket (BLOCKING)
+ */
+int
+rdns_make_client_socket (const char *credits, uint16_t port,
+ int type)
+{
+ struct sockaddr_un un;
+ struct stat st;
+ struct addrinfo hints, *res;
+ int r;
+ char portbuf[8];
+
+ if (*credits == '/') {
+ r = stat (credits, &st);
+ if (r == -1) {
+ /* Unix socket doesn't exists it must be created first */
+ errno = ENOENT;
+ return -1;
+ }
+ else {
+ if ((st.st_mode & S_IFSOCK) == 0) {
+ /* Path is not valid socket */
+ errno = EINVAL;
+ return -1;
+ }
+ else {
+ return rdns_make_unix_socket (credits, &un, type);
+ }
+ }
+ }
+ else {
+ /* TCP related part */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = type; /* Type of the socket */
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
+
+ snprintf (portbuf, sizeof (portbuf), "%d", (int)port);
+ if ((r = getaddrinfo (credits, portbuf, &hints, &res)) == 0) {
+ r = rdns_make_inet_socket (type, res);
+ freeaddrinfo (res);
+ return r;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ /* Not reached */
+ return -1;
+}
+
+const char *
+rdns_strerror (enum dns_rcode rcode)
+{
+ rcode &= 0xf;
+ static char numbuf[16];
+
+ if ('\0' == dns_rcodes[rcode][0]) {
+ snprintf (numbuf, sizeof (numbuf), "UNKNOWN: %d", (int)rcode);
+ return numbuf;
+ }
+ return dns_rcodes[rcode];
+}
+
+const char *
+rdns_strtype (enum rdns_request_type type)
+{
+ return dns_types[type];
+}
+
+uint16_t
+rdns_permutor_generate_id (void)
+{
+ uint16_t id;
+
+ id = ottery_rand_unsigned ();
+
+ return id;
+}
+
+
+void
+rdns_reply_free (struct rdns_reply *rep)
+{
+ struct rdns_reply_entry *entry, *tmp;
+
+ LL_FOREACH_SAFE (rep->entries, entry, tmp) {
+ switch (entry->type) {
+ case RDNS_REQUEST_PTR:
+ free (entry->content.ptr.name);
+ break;
+ case RDNS_REQUEST_NS:
+ free (entry->content.ns.name);
+ break;
+ case RDNS_REQUEST_MX:
+ free (entry->content.mx.name);
+ break;
+ case RDNS_REQUEST_TXT:
+ case RDNS_REQUEST_SPF:
+ free (entry->content.txt.data);
+ break;
+ case RDNS_REQUEST_SRV:
+ free (entry->content.srv.target);
+ break;
+ case RDNS_REQUEST_TLSA:
+ free (entry->content.tlsa.data);
+ break;
+ case RDNS_REQUEST_SOA:
+ free (entry->content.soa.mname);
+ free (entry->content.soa.admin);
+ break;
+ }
+ free (entry);
+ }
+ free (rep);
+}
+
+void
+rdns_request_free (struct rdns_request *req)
+{
+ unsigned int i;
+
+ if (req != NULL) {
+ if (req->packet != NULL) {
+ free (req->packet);
+ }
+ for (i = 0; i < req->qcount; i ++) {
+ free (req->requested_names[i].name);
+ }
+ if (req->requested_names != NULL) {
+ free (req->requested_names);
+ }
+ if (req->reply != NULL) {
+ rdns_reply_free (req->reply);
+ }
+ if (req->state >= RDNS_REQUEST_SENT &&
+ req->state < RDNS_REQUEST_REPLIED) {
+ /* Remove timer */
+ req->async->del_timer (req->async->data,
+ req->async_event);
+ /* Remove from id hashes */
+ HASH_DEL (req->io->requests, req);
+ }
+ else if (req->state == RDNS_REQUEST_REGISTERED) {
+ /* Remove retransmit event */
+ req->async->del_write (req->async->data,
+ req->async_event);
+ }
+#ifdef TWEETNACL
+ if (req->curve_plugin_data != NULL) {
+ req->resolver->curve_plugin->cb.curve_plugin.finish_cb (
+ req, req->resolver->curve_plugin->data);
+ }
+#endif
+ if (req->io != NULL && req->state > RDNS_REQUEST_NEW) {
+ REF_RELEASE (req->io);
+ REF_RELEASE (req->resolver);
+ }
+
+ free (req);
+ }
+}
+
+void
+rdns_ioc_free (struct rdns_io_channel *ioc)
+{
+ struct rdns_request *req, *rtmp;
+
+ HASH_ITER (hh, ioc->requests, req, rtmp) {
+ REF_RELEASE (req);
+ }
+ ioc->resolver->async->del_read (ioc->resolver->async->data,
+ ioc->async_io);
+ close (ioc->sock);
+ free (ioc);
+}
+
+void
+rdns_resolver_release (struct rdns_resolver *resolver)
+{
+ REF_RELEASE (resolver);
+}
+
+struct rdns_request*
+rdns_request_retain (struct rdns_request *req)
+{
+ REF_RETAIN (req);
+ return req;
+}
+
+void
+rdns_request_release (struct rdns_request *req)
+{
+ REF_RELEASE (req);
+}
+
+static bool
+rdns_resolver_conf_process_line (struct rdns_resolver *resolver, char *line)
+{
+ char *p, *c;
+ bool has_obrace = false;
+ unsigned int port = dns_port;
+
+ if (strncmp (line, "nameserver", sizeof ("nameserver") - 1) == 0) {
+ p = line + sizeof ("nameserver") - 1;
+ /* Skip spaces */
+ while (*p == ' ' || *p == '\t') {
+ p ++;
+ }
+ if (*p == '[') {
+ has_obrace = true;
+ p ++;
+ }
+ if (isxdigit (*p) || *p == ':') {
+ c = p;
+ while (isxdigit (*p) || *p == ':' || *p == '.') {
+ p ++;
+ }
+ if (has_obrace && *p != ']') {
+ return false;
+ }
+ else if (*p != '\0' && *p != '\n') {
+ return false;
+ }
+ *p = '\0';
+ if (has_obrace) {
+ p ++;
+ if (*p == ':') {
+ /* Maybe we have a port definition */
+ port = strtoul (p + 1, NULL, 10);
+ if (port == 0 || port > UINT16_MAX) {
+ return false;
+ }
+ }
+ }
+
+ return rdns_resolver_add_server (resolver, c, port, 0, default_io_cnt);
+ }
+ else {
+ return false;
+ }
+ }
+ /* XXX: skip unknown resolv.conf lines */
+
+ return true;
+}
+
+bool
+rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver, const char *path)
+{
+ FILE *in;
+ char buf[BUFSIZ];
+
+ in = fopen (path, "r");
+
+ if (in == NULL) {
+ return false;
+ }
+
+ while (!feof (in)) {
+ if (fgets (buf, sizeof (buf), in) == NULL) {
+ break;
+ }
+ if (!rdns_resolver_conf_process_line (resolver, buf)) {
+ rdns_warn ("rdns_resolver_parse_resolv_conf: cannot parse line: %s", buf);
+ fclose (in);
+ return false;
+ }
+ }
+
+ fclose (in);
+ return true;
+}
+
+bool
+rdns_request_has_type (struct rdns_request *req, enum rdns_request_type type)
+{
+ unsigned int i;
+
+ for (i = 0; i < req->qcount; i ++) {
+ if (req->requested_names[i].type == type) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+const struct rdns_request_name *
+rdns_request_get_name (struct rdns_request *req, unsigned int *count)
+{
+
+ if (count != NULL) {
+ *count = req->qcount;
+ }
+ return req->requested_names;
+}
+
+char *
+rdns_generate_ptr_from_str (const char *str)
+{
+ union {
+ struct in_addr v4;
+ struct in6_addr v6;
+ } addr;
+ char *res = NULL;
+ unsigned char *bytes;
+ size_t len;
+
+ if (inet_pton (AF_INET, str, &addr.v4) == 1) {
+ bytes = (unsigned char *)&addr.v4;
+
+ len = 4 * 4 + sizeof ("in-addr.arpa");
+ res = malloc (len);
+ if (res) {
+ snprintf (res, len, "%u.%u.%u.%u.in-addr.arpa",
+ (unsigned)bytes[3]&0xFF,
+ (unsigned)bytes[2]&0xFF,
+ (unsigned)bytes[1]&0xFF,
+ (unsigned)bytes[0]&0xFF);
+ }
+ }
+ else if (inet_pton (AF_INET6, str, &addr.v6) == 1) {
+ bytes = (unsigned char *)&addr.v6;
+
+ len = 2*32 + sizeof ("ip6.arpa");
+ res = malloc (len);
+ if (res) {
+ snprintf(res, len,
+ "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa",
+ bytes[15]&0xF, bytes[15] >> 4, bytes[14]&0xF, bytes[14] >> 4,
+ bytes[13]&0xF, bytes[13] >> 4, bytes[12]&0xF, bytes[12] >> 4,
+ bytes[11]&0xF, bytes[11] >> 4, bytes[10]&0xF, bytes[10] >> 4,
+ bytes[9]&0xF, bytes[9] >> 4, bytes[8]&0xF, bytes[8] >> 4,
+ bytes[7]&0xF, bytes[7] >> 4, bytes[6]&0xF, bytes[6] >> 4,
+ bytes[5]&0xF, bytes[5] >> 4, bytes[4]&0xF, bytes[4] >> 4,
+ bytes[3]&0xF, bytes[3] >> 4, bytes[2]&0xF, bytes[2] >> 4,
+ bytes[1]&0xF, bytes[1] >> 4, bytes[0]&0xF, bytes[0] >> 4);
+ }
+ }
+
+ return res;
+}
diff --git a/contrib/librdns/util.h b/contrib/librdns/util.h
new file mode 100644
index 000000000..035f49a19
--- /dev/null
+++ b/contrib/librdns/util.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 UTIL_H_
+#define UTIL_H_
+
+#include "dns_private.h"
+
+/**
+ * Make a universal socket
+ * @param credits host, ip or path to unix socket
+ * @param port port (used for network sockets)
+ * @param type of socket (SOCK_STREAM or SOCK_DGRAM)
+ */
+int
+rdns_make_client_socket (const char *credits, uint16_t port,
+ int type);
+
+/**
+ * Generate new random DNS id
+ * @return dns id
+ */
+uint16_t rdns_permutor_generate_id (void);
+
+
+/**
+ * Free IO channel
+ */
+void rdns_ioc_free (struct rdns_io_channel *ioc);
+
+/**
+ * Free request
+ * @param req
+ */
+void rdns_request_free (struct rdns_request *req);
+
+/**
+ * Free reply
+ * @param rep
+ */
+void rdns_reply_free (struct rdns_reply *rep);
+
+#endif /* UTIL_H_ */
diff --git a/contrib/libucl/CMakeLists.txt b/contrib/libucl/CMakeLists.txt
new file mode 100644
index 000000000..80f50155e
--- /dev/null
+++ b/contrib/libucl/CMakeLists.txt
@@ -0,0 +1,18 @@
+SET(UCLSRC ucl_util.c
+ ucl_parser.c
+ ucl_emitter.c
+ ucl_emitter_streamline.c
+ ucl_emitter_utils.c
+ ucl_hash.c
+ ucl_schema.c
+ lua_ucl.c)
+
+
+SET (LIB_TYPE STATIC)
+ADD_LIBRARY(ucl ${LIB_TYPE} ${UCLSRC})
+
+IF(ENABLE_URL_SIGN MATCHES "ON")
+ IF(OPENSSL_FOUND)
+ TARGET_LINK_LIBRARIES(ucl ${OPENSSL_LIBRARIES})
+ ENDIF(OPENSSL_FOUND)
+ENDIF(ENABLE_URL_SIGN MATCHES "ON")
diff --git a/contrib/libucl/khash.h b/contrib/libucl/khash.h
new file mode 100644
index 000000000..afc3ce3ef
--- /dev/null
+++ b/contrib/libucl/khash.h
@@ -0,0 +1,627 @@
+/* The MIT License
+
+ Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ An example:
+
+#include "khash.h"
+KHASH_MAP_INIT_INT(32, char)
+int main() {
+ int ret, is_missing;
+ khiter_t k;
+ khash_t(32) *h = kh_init(32);
+ k = kh_put(32, h, 5, &ret);
+ kh_value(h, k) = 10;
+ k = kh_get(32, h, 10);
+ is_missing = (k == kh_end(h));
+ k = kh_get(32, h, 5);
+ kh_del(32, h, k);
+ for (k = kh_begin(h); k != kh_end(h); ++k)
+ if (kh_exist(h, k)) kh_value(h, k) = 1;
+ kh_destroy(32, h);
+ return 0;
+}
+*/
+
+/*
+ 2013-05-02 (0.2.8):
+
+ * Use quadratic probing. When the capacity is power of 2, stepping function
+ i*(i+1)/2 guarantees to traverse each bucket. It is better than double
+ hashing on cache performance and is more robust than linear probing.
+
+ In theory, double hashing should be more robust than quadratic probing.
+ However, my implementation is probably not for large hash tables, because
+ the second hash function is closely tied to the first hash function,
+ which reduce the effectiveness of double hashing.
+
+ Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
+
+ 2011-12-29 (0.2.7):
+
+ * Minor code clean up; no actual effect.
+
+ 2011-09-16 (0.2.6):
+
+ * The capacity is a power of 2. This seems to dramatically improve the
+ speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
+
+ - http://code.google.com/p/ulib/
+ - http://nothings.org/computer/judy/
+
+ * Allow to optionally use linear probing which usually has better
+ performance for random input. Double hashing is still the default as it
+ is more robust to certain non-random input.
+
+ * Added Wang's integer hash function (not used by default). This hash
+ function is more robust to certain non-random input.
+
+ 2011-02-14 (0.2.5):
+
+ * Allow to declare global functions.
+
+ 2009-09-26 (0.2.4):
+
+ * Improve portability
+
+ 2008-09-19 (0.2.3):
+
+ * Corrected the example
+ * Improved interfaces
+
+ 2008-09-11 (0.2.2):
+
+ * Improved speed a little in kh_put()
+
+ 2008-09-10 (0.2.1):
+
+ * Added kh_clear()
+ * Fixed a compiling error
+
+ 2008-09-02 (0.2.0):
+
+ * Changed to token concatenation which increases flexibility.
+
+ 2008-08-31 (0.1.2):
+
+ * Fixed a bug in kh_get(), which has not been tested previously.
+
+ 2008-08-31 (0.1.1):
+
+ * Added destructor
+*/
+
+
+#ifndef __AC_KHASH_H
+#define __AC_KHASH_H
+
+/*!
+ @header
+
+ Generic hash table library.
+ */
+
+#define AC_VERSION_KHASH_H "0.2.8"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* compiler specific configuration */
+
+#if UINT_MAX == 0xffffffffu
+typedef unsigned int khint32_t;
+#elif ULONG_MAX == 0xffffffffu
+typedef unsigned long khint32_t;
+#endif
+
+#if ULONG_MAX == ULLONG_MAX
+typedef unsigned long khint64_t;
+#else
+typedef unsigned long long khint64_t;
+#endif
+
+#ifndef kh_inline
+#ifdef _MSC_VER
+#define kh_inline __inline
+#else
+#define kh_inline inline
+#endif
+#endif /* kh_inline */
+
+#ifndef kh_unused
+# ifdef __GNUC__
+# define kh_unused(x) __attribute__((__unused__)) x
+# else
+# define kh_unused(x) x
+# endif
+#endif
+
+typedef khint32_t khint_t;
+typedef khint_t khiter_t;
+
+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
+
+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+
+#ifndef kroundup32
+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+#endif
+
+#ifndef kcalloc
+#define kcalloc(N,Z) calloc(N,Z)
+#endif
+#ifndef kmalloc
+#define kmalloc(Z) malloc(Z)
+#endif
+#ifndef krealloc
+#define krealloc(P,Z) realloc(P,Z)
+#endif
+#ifndef kfree
+#define kfree(P) free(P)
+#endif
+
+static const double __ac_HASH_UPPER = 0.77;
+
+#define __KHASH_TYPE(name, khkey_t, khval_t) \
+ typedef struct kh_##name##_s { \
+ khint_t n_buckets, size, n_occupied, upper_bound; \
+ khint32_t *flags; \
+ khkey_t *keys; \
+ khval_t *vals; \
+ } kh_##name##_t;
+
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t * kh_init_##name(void); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kfree((void *)h->keys); kfree(h->flags); \
+ kfree((void *)h->vals); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_unused(kh_clear_##name)(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t k, i, last, mask, step = 0; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ i = (i + (++step)) & mask; \
+ if (i == last) return h->n_buckets; \
+ } \
+ return __ac_iseither(h->flags, i)? h->n_buckets : i; \
+ } else return 0; \
+ } \
+ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) new_n_buckets = 4; \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
+ else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (!new_flags) return -1; \
+ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ if (!new_keys) { kfree(new_flags); return -1; } \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ if (!new_vals) { kfree(new_flags); return -1; } \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) val = h->vals[j]; \
+ __ac_set_isdel_true(h->flags, j); \
+ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
+ khint_t k, i, step = 0; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
+ __ac_set_isempty_false(new_flags, i); \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
+ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
+ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
+ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) h->vals[i] = val; \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ return 0; \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size<<1)) { \
+ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
+ { \
+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
+ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
+ else { \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) site = i; \
+ i = (i + (++step)) & mask; \
+ if (i == last) { x = site; break; } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
+ else x = i; \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; ++h->n_occupied; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; \
+ *ret = 2; \
+ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
+
+#define KHASH_DECLARE(name, khkey_t, khval_t) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_PROTOTYPES(name, khkey_t, khval_t)
+
+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+/* --- BEGIN OF HASH FUNCTIONS --- */
+
+/*! @function
+ @abstract Integer hash function
+ @param key The integer [khint32_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int_hash_func(key) (khint32_t)(key)
+/*! @function
+ @abstract Integer comparison function
+ */
+#define kh_int_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract 64-bit integer hash function
+ @param key The integer [khint64_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
+/*! @function
+ @abstract 64-bit integer comparison function
+ */
+#define kh_int64_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract const char* hash function
+ @param s Pointer to a null terminated string
+ @return The hash value
+ */
+static kh_inline khint_t __ac_X31_hash_string(const char *s)
+{
+ khint_t h = (khint_t)*s;
+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
+ return h;
+}
+/*! @function
+ @abstract Another interface to const char* hash function
+ @param key Pointer to a null terminated string [const char*]
+ @return The hash value [khint_t]
+ */
+#define kh_str_hash_func(key) __ac_X31_hash_string(key)
+/*! @function
+ @abstract Const char* comparison function
+ */
+#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
+
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
+{
+ key += ~(key << 15);
+ key ^= (key >> 10);
+ key += (key << 3);
+ key ^= (key >> 6);
+ key += ~(key << 11);
+ key ^= (key >> 16);
+ return key;
+}
+#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
+
+/* --- END OF HASH FUNCTIONS --- */
+
+/* Other convenient macros... */
+
+/*!
+ @abstract Type of the hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define khash_t(name) kh_##name##_t
+
+/*! @function
+ @abstract Initiate a hash table.
+ @param name Name of the hash table [symbol]
+ @return Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_init(name) kh_init_##name()
+
+/*! @function
+ @abstract Destroy a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_destroy(name, h) kh_destroy_##name(h)
+
+/*! @function
+ @abstract Reset a hash table without deallocating memory.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_clear(name, h) kh_clear_##name(h)
+
+/*! @function
+ @abstract Resize a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param s New size [khint_t]
+ */
+#define kh_resize(name, h, s) kh_resize_##name(h, s)
+
+/*! @function
+ @abstract Insert a key to the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @param r Extra return code: -1 if the operation failed;
+ 0 if the key is present in the hash table;
+ 1 if the bucket is empty (never used); 2 if the element in
+ the bucket has been deleted [int*]
+ @return Iterator to the inserted element [khint_t]
+ */
+#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
+
+/*! @function
+ @abstract Retrieve a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
+ */
+#define kh_get(name, h, k) kh_get_##name(h, k)
+
+/*! @function
+ @abstract Remove a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Iterator to the element to be deleted [khint_t]
+ */
+#define kh_del(name, h, k) kh_del_##name(h, k)
+
+/*! @function
+ @abstract Test whether a bucket contains data.
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return 1 if containing data; 0 otherwise [int]
+ */
+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
+
+/*! @function
+ @abstract Get key given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Key [type of keys]
+ */
+#define kh_key(h, x) ((h)->keys[x])
+
+/*! @function
+ @abstract Get value given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Value [type of values]
+ @discussion For hash sets, calling this results in segfault.
+ */
+#define kh_val(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Alias of kh_val()
+ */
+#define kh_value(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Get the start iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The start iterator [khint_t]
+ */
+#define kh_begin(h) (khint_t)(0)
+
+/*! @function
+ @abstract Get the end iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The end iterator [khint_t]
+ */
+#define kh_end(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Get the number of elements in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of elements in the hash table [khint_t]
+ */
+#define kh_size(h) ((h)->size)
+
+/*! @function
+ @abstract Get the number of buckets in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of buckets in the hash table [khint_t]
+ */
+#define kh_n_buckets(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Iterate over the entries in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which key will be assigned
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (kvar) = kh_key(h,__i); \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/*! @function
+ @abstract Iterate over the values in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_value(h, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/* More conenient interfaces */
+
+/*! @function
+ @abstract Instantiate a hash set containing integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT(name) \
+ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT(name, khval_t) \
+ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT64(name) \
+ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT64(name, khval_t) \
+ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+typedef const char *kh_cstr_t;
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_STR(name) \
+ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#endif /* __AC_KHASH_H */
diff --git a/contrib/libucl/kvec.h b/contrib/libucl/kvec.h
new file mode 100644
index 000000000..b5cce8508
--- /dev/null
+++ b/contrib/libucl/kvec.h
@@ -0,0 +1,103 @@
+/* The MIT License
+
+ Copyright (c) 2008, by Attractive Chaos <attractor@live.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ An example:
+
+#include "kvec.h"
+int main() {
+ kvec_t(int) array;
+ kv_init(array);
+ kv_push(int, array, 10); // append
+ kv_a(int, array, 20) = 5; // dynamic
+ kv_A(array, 20) = 4; // static
+ kv_destroy(array);
+ return 0;
+}
+*/
+
+/*
+ 2008-09-22 (0.1.0):
+
+ * The initial version.
+
+*/
+
+#ifndef AC_KVEC_H
+#define AC_KVEC_H
+
+#include <stdlib.h>
+
+#define kv_roundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+
+#define kvec_t(type) struct { size_t n, m; type *a; }
+#define kv_init(v) ((v).n = (v).m = 0, (v).a = 0)
+#define kv_destroy(v) free((v).a)
+#define kv_A(v, i) ((v).a[(i)])
+#define kv_pop(v) ((v).a[--(v).n])
+#define kv_size(v) ((v).n)
+#define kv_max(v) ((v).m)
+
+#define kv_resize(type, v, s) ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m))
+#define kv_grow_factor 1.5
+#define kv_grow(type, v) ((v).m = ((v).m > 1 ? (v).m * kv_grow_factor : 2), \
+ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m))
+
+#define kv_copy(type, v1, v0) do { \
+ if ((v1).m < (v0).n) kv_resize(type, v1, (v0).n); \
+ (v1).n = (v0).n; \
+ memcpy((v1).a, (v0).a, sizeof(type) * (v0).n); \
+ } while (0) \
+
+#define kv_push(type, v, x) do { \
+ if ((v).n == (v).m) { \
+ kv_grow(type, v); \
+ } \
+ (v).a[(v).n++] = (x); \
+ } while (0)
+
+#define kv_prepend(type, v, x) do { \
+ if ((v).n == (v).m) { \
+ kv_grow(type, v); \
+ } \
+ memmove((v).a + 1, (v).a, sizeof(type) * (v).n); \
+ (v).a[0] = (x); \
+ (v).n ++; \
+} while (0)
+
+#define kv_concat(type, v1, v0) do { \
+ if ((v1).m < (v0).n + (v1).n) kv_resize(type, v1, (v0).n + (v1).n); \
+ memcpy((v1).a + (v1).n, (v0).a, sizeof(type) * ((v0).n + (v1).n)); \
+ (v1).n = (v0).n + (v1).n; \
+ } while (0)
+
+#define kv_del(type, v, i) do { \
+ if ((i) < (v).n) { \
+ memmove((v).a + (i), (v).a + ((i) + 1), sizeof(type) * ((v).n - (i) - 1)); \
+ (v).n --; \
+ } \
+} while (0)
+
+#endif
diff --git a/contrib/libucl/lua_ucl.c b/contrib/libucl/lua_ucl.c
new file mode 100644
index 000000000..682b0b559
--- /dev/null
+++ b/contrib/libucl/lua_ucl.c
@@ -0,0 +1,820 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 lua ucl bindings
+ */
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "lua_ucl.h"
+#include <strings.h>
+
+/***
+ * @module ucl
+ * This lua module allows to parse objects from strings and to store data into
+ * ucl objects. It uses `libucl` C library to parse and manipulate with ucl objects.
+ * @example
+local ucl = require("ucl")
+
+local parser = ucl.parser()
+local res,err = parser:parse_string('{key=value}')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ local obj = parser:get_object()
+ local got = ucl.to_format(obj, 'json')
+endif
+
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+}
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+ */
+
+#define PARSER_META "ucl.parser.meta"
+#define EMITTER_META "ucl.emitter.meta"
+#define NULL_META "null.emitter.meta"
+
+static int ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj);
+static int ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj, bool allow_array);
+static ucl_object_t* ucl_object_lua_fromtable (lua_State *L, int idx);
+static ucl_object_t* ucl_object_lua_fromelt (lua_State *L, int idx);
+
+static void *ucl_null;
+
+/**
+ * Push a single element of an object to lua
+ * @param L
+ * @param key
+ * @param obj
+ */
+static void
+ucl_object_lua_push_element (lua_State *L, const char *key,
+ const ucl_object_t *obj)
+{
+ lua_pushstring (L, key);
+ ucl_object_push_lua (L, obj, true);
+ lua_settable (L, -3);
+}
+
+static void
+lua_ucl_userdata_dtor (void *ud)
+{
+ struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
+
+ luaL_unref (fd->L, LUA_REGISTRYINDEX, fd->idx);
+ if (fd->ret != NULL) {
+ free (fd->ret);
+ }
+ free (fd);
+}
+
+static const char *
+lua_ucl_userdata_emitter (void *ud)
+{
+ struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
+ const char *out = "";
+
+ lua_rawgeti (fd->L, LUA_REGISTRYINDEX, fd->idx);
+
+ lua_pcall (fd->L, 0, 1, 0);
+ out = lua_tostring (fd->L, -1);
+
+ if (out != NULL) {
+ /* We need to store temporary string in a more appropriate place */
+ if (fd->ret) {
+ free (fd->ret);
+ }
+ fd->ret = strdup (out);
+ }
+
+ lua_settop (fd->L, 0);
+
+ return fd->ret;
+}
+
+/**
+ * Push a single object to lua
+ * @param L
+ * @param obj
+ * @return
+ */
+static int
+ucl_object_lua_push_object (lua_State *L, const ucl_object_t *obj,
+ bool allow_array)
+{
+ const ucl_object_t *cur;
+ ucl_object_iter_t it = NULL;
+ int nelt = 0;
+
+ if (allow_array && obj->next != NULL) {
+ /* Actually we need to push this as an array */
+ return ucl_object_lua_push_array (L, obj);
+ }
+
+ /* Optimize allocation by preallocation of table */
+ while (ucl_iterate_object (obj, &it, true) != NULL) {
+ nelt ++;
+ }
+
+ lua_createtable (L, 0, nelt);
+ it = NULL;
+
+ while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
+ ucl_object_lua_push_element (L, ucl_object_key (cur), cur);
+ }
+
+ return 1;
+}
+
+/**
+ * Push an array to lua as table indexed by integers
+ * @param L
+ * @param obj
+ * @return
+ */
+static int
+ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj)
+{
+ const ucl_object_t *cur;
+ int i = 1, nelt = 0;
+
+ /* Optimize allocation by preallocation of table */
+ LL_FOREACH (obj, cur) {
+ nelt ++;
+ }
+
+ lua_createtable (L, nelt, 0);
+
+ LL_FOREACH (obj, cur) {
+ ucl_object_push_lua (L, cur, false);
+ lua_rawseti (L, -2, i);
+ i ++;
+ }
+
+ return 1;
+}
+
+/**
+ * Push a simple object to lua depending on its actual type
+ */
+static int
+ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj,
+ bool allow_array)
+{
+ struct ucl_lua_funcdata *fd;
+
+ if (allow_array && obj->next != NULL) {
+ /* Actually we need to push this as an array */
+ return ucl_object_lua_push_array (L, obj);
+ }
+
+ switch (obj->type) {
+ case UCL_BOOLEAN:
+ lua_pushboolean (L, ucl_obj_toboolean (obj));
+ break;
+ case UCL_STRING:
+ lua_pushstring (L, ucl_obj_tostring (obj));
+ break;
+ case UCL_INT:
+#if LUA_VERSION_NUM >= 501
+ lua_pushinteger (L, ucl_obj_toint (obj));
+#else
+ lua_pushnumber (L, ucl_obj_toint (obj));
+#endif
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ lua_pushnumber (L, ucl_obj_todouble (obj));
+ break;
+ case UCL_NULL:
+ lua_getfield (L, LUA_REGISTRYINDEX, "ucl.null");
+ break;
+ case UCL_USERDATA:
+ fd = (struct ucl_lua_funcdata *)obj->value.ud;
+ lua_rawgeti (L, LUA_REGISTRYINDEX, fd->idx);
+ break;
+ default:
+ lua_pushnil (L);
+ break;
+ }
+
+ return 1;
+}
+
+/***
+ * @function ucl_object_push_lua(L, obj, allow_array)
+ * This is a `C` function to push `UCL` object as lua variable. This function
+ * converts `obj` to lua representation using the following conversions:
+ *
+ * - *scalar* values are directly presented by lua objects
+ * - *userdata* values are converted to lua function objects using `LUA_REGISTRYINDEX`,
+ * this can be used to pass functions from lua to c and vice-versa
+ * - *arrays* are converted to lua tables with numeric indicies suitable for `ipairs` iterations
+ * - *objects* are converted to lua tables with string indicies
+ * @param {lua_State} L lua state pointer
+ * @param {ucl_object_t} obj object to push
+ * @param {bool} allow_array expand implicit arrays (should be true for all but partial arrays)
+ * @return {int} `1` if an object is pushed to lua
+ */
+int
+ucl_object_push_lua (lua_State *L, const ucl_object_t *obj, bool allow_array)
+{
+ switch (obj->type) {
+ case UCL_OBJECT:
+ return ucl_object_lua_push_object (L, obj, allow_array);
+ case UCL_ARRAY:
+ return ucl_object_lua_push_array (L, obj->value.av);
+ default:
+ return ucl_object_lua_push_scalar (L, obj, allow_array);
+ }
+}
+
+/**
+ * Parse lua table into object top
+ * @param L
+ * @param top
+ * @param idx
+ */
+static ucl_object_t *
+ucl_object_lua_fromtable (lua_State *L, int idx)
+{
+ ucl_object_t *obj, *top = NULL;
+ size_t keylen;
+ const char *k;
+ bool is_array = true;
+ int max = INT_MIN;
+
+ if (idx < 0) {
+ /* For negative indicies we want to invert them */
+ idx = lua_gettop (L) + idx + 1;
+ }
+ /* Check for array */
+ lua_pushnil (L);
+ while (lua_next (L, idx) != 0) {
+ if (lua_type (L, -2) == LUA_TNUMBER) {
+ double num = lua_tonumber (L, -2);
+ if (num == (int)num) {
+ if (num > max) {
+ max = num;
+ }
+ }
+ else {
+ /* Keys are not integer */
+ lua_pop (L, 2);
+ is_array = false;
+ break;
+ }
+ }
+ else {
+ /* Keys are not numeric */
+ lua_pop (L, 2);
+ is_array = false;
+ break;
+ }
+ lua_pop (L, 1);
+ }
+
+ /* Table iterate */
+ if (is_array) {
+ int i;
+
+ top = ucl_object_typed_new (UCL_ARRAY);
+ for (i = 1; i <= max; i ++) {
+ lua_pushinteger (L, i);
+ lua_gettable (L, idx);
+ obj = ucl_object_lua_fromelt (L, lua_gettop (L));
+ if (obj != NULL) {
+ ucl_array_append (top, obj);
+ }
+ }
+ }
+ else {
+ lua_pushnil (L);
+ top = ucl_object_typed_new (UCL_OBJECT);
+ while (lua_next (L, idx) != 0) {
+ /* copy key to avoid modifications */
+ k = lua_tolstring (L, -2, &keylen);
+ obj = ucl_object_lua_fromelt (L, lua_gettop (L));
+
+ if (obj != NULL) {
+ ucl_object_insert_key (top, obj, k, keylen, true);
+ }
+ lua_pop (L, 1);
+ }
+ }
+
+ return top;
+}
+
+/**
+ * Get a single element from lua to object obj
+ * @param L
+ * @param obj
+ * @param idx
+ */
+static ucl_object_t *
+ucl_object_lua_fromelt (lua_State *L, int idx)
+{
+ int type;
+ double num;
+ ucl_object_t *obj = NULL;
+ struct ucl_lua_funcdata *fd;
+
+ type = lua_type (L, idx);
+
+ switch (type) {
+ case LUA_TSTRING:
+ obj = ucl_object_fromstring_common (lua_tostring (L, idx), 0, 0);
+ break;
+ case LUA_TNUMBER:
+ num = lua_tonumber (L, idx);
+ if (num == (int64_t)num) {
+ obj = ucl_object_fromint (num);
+ }
+ else {
+ obj = ucl_object_fromdouble (num);
+ }
+ break;
+ case LUA_TBOOLEAN:
+ obj = ucl_object_frombool (lua_toboolean (L, idx));
+ break;
+ case LUA_TUSERDATA:
+ if (lua_topointer (L, idx) == ucl_null) {
+ obj = ucl_object_typed_new (UCL_NULL);
+ }
+ break;
+ case LUA_TTABLE:
+ case LUA_TFUNCTION:
+ case LUA_TTHREAD:
+ if (luaL_getmetafield (L, idx, "__gen_ucl")) {
+ if (lua_isfunction (L, -1)) {
+ lua_settop (L, 3); /* gen, obj, func */
+ lua_insert (L, 1); /* func, gen, obj */
+ lua_insert (L, 2); /* func, obj, gen */
+ lua_call(L, 2, 1);
+ obj = ucl_object_lua_fromelt (L, 1);
+ }
+ lua_pop (L, 2);
+ }
+ else {
+ if (type == LUA_TTABLE) {
+ obj = ucl_object_lua_fromtable (L, idx);
+ }
+ else if (type == LUA_TFUNCTION) {
+ fd = malloc (sizeof (*fd));
+ if (fd != NULL) {
+ lua_pushvalue (L, idx);
+ fd->L = L;
+ fd->ret = NULL;
+ fd->idx = luaL_ref (L, LUA_REGISTRYINDEX);
+
+ obj = ucl_object_new_userdata (lua_ucl_userdata_dtor,
+ lua_ucl_userdata_emitter);
+ obj->type = UCL_USERDATA;
+ obj->value.ud = (void *)fd;
+ }
+ }
+ }
+ break;
+ }
+
+ return obj;
+}
+
+/**
+ * @function ucl_object_lua_import(L, idx)
+ * Extracts ucl object from lua variable at `idx` position,
+ * @see ucl_object_push_lua for conversion definitions
+ * @param {lua_state} L lua state machine pointer
+ * @param {int} idx index where the source variable is placed
+ * @return {ucl_object_t} new ucl object extracted from lua variable. Reference count of this object is 1,
+ * this object thus needs to be unref'ed after usage.
+ */
+ucl_object_t *
+ucl_object_lua_import (lua_State *L, int idx)
+{
+ ucl_object_t *obj;
+ int t;
+
+ t = lua_type (L, idx);
+ switch (t) {
+ case LUA_TTABLE:
+ obj = ucl_object_lua_fromtable (L, idx);
+ break;
+ default:
+ obj = ucl_object_lua_fromelt (L, idx);
+ break;
+ }
+
+ return obj;
+}
+
+static int
+lua_ucl_parser_init (lua_State *L)
+{
+ struct ucl_parser *parser, **pparser;
+ int flags = 0;
+
+ if (lua_gettop (L) >= 1) {
+ flags = lua_tonumber (L, 1);
+ }
+
+ parser = ucl_parser_new (flags);
+ if (parser == NULL) {
+ lua_pushnil (L);
+ }
+
+ pparser = lua_newuserdata (L, sizeof (parser));
+ *pparser = parser;
+ luaL_getmetatable (L, PARSER_META);
+ lua_setmetatable (L, -2);
+
+ return 1;
+}
+
+static struct ucl_parser *
+lua_ucl_parser_get (lua_State *L, int index)
+{
+ return *((struct ucl_parser **) luaL_checkudata(L, index, PARSER_META));
+}
+
+/***
+ * @method parser:parse_file(name)
+ * Parse UCL object from file.
+ * @param {string} name filename to parse
+ * @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+@example
+local parser = ucl.parser()
+local res,err = parser:parse_file('/some/file.conf')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ -- Do something with object
+end
+ */
+static int
+lua_ucl_parser_parse_file (lua_State *L)
+{
+ struct ucl_parser *parser;
+ const char *file;
+ int ret = 2;
+
+ parser = lua_ucl_parser_get (L, 1);
+ file = luaL_checkstring (L, 2);
+
+ if (parser != NULL && file != NULL) {
+ if (ucl_parser_add_file (parser, file)) {
+ lua_pushboolean (L, true);
+ ret = 1;
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, ucl_parser_get_error (parser));
+ }
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, "invalid arguments");
+ }
+
+ return ret;
+}
+
+/***
+ * @method parser:parse_string(input)
+ * Parse UCL object from file.
+ * @param {string} input string to parse
+ * @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+ */
+static int
+lua_ucl_parser_parse_string (lua_State *L)
+{
+ struct ucl_parser *parser;
+ const char *string;
+ size_t llen;
+ int ret = 2;
+
+ parser = lua_ucl_parser_get (L, 1);
+ string = luaL_checklstring (L, 2, &llen);
+
+ if (parser != NULL && string != NULL) {
+ if (ucl_parser_add_chunk (parser, (const unsigned char *)string, llen)) {
+ lua_pushboolean (L, true);
+ ret = 1;
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, ucl_parser_get_error (parser));
+ }
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, "invalid arguments");
+ }
+
+ return ret;
+}
+
+/***
+ * @method parser:get_object()
+ * Get top object from parser and export it to lua representation.
+ * @return {variant or nil} ucl object as lua native variable
+ */
+static int
+lua_ucl_parser_get_object (lua_State *L)
+{
+ struct ucl_parser *parser;
+ ucl_object_t *obj;
+ int ret = 1;
+
+ parser = lua_ucl_parser_get (L, 1);
+ obj = ucl_parser_get_object (parser);
+
+ if (obj != NULL) {
+ ret = ucl_object_push_lua (L, obj, false);
+ /* no need to keep reference */
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return ret;
+}
+
+static int
+lua_ucl_parser_gc (lua_State *L)
+{
+ struct ucl_parser *parser;
+
+ parser = lua_ucl_parser_get (L, 1);
+ ucl_parser_free (parser);
+
+ return 0;
+}
+
+static void
+lua_ucl_parser_mt (lua_State *L)
+{
+ luaL_newmetatable (L, PARSER_META);
+
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction (L, lua_ucl_parser_gc);
+ lua_setfield (L, -2, "__gc");
+
+ lua_pushcfunction (L, lua_ucl_parser_parse_file);
+ lua_setfield (L, -2, "parse_file");
+
+ lua_pushcfunction (L, lua_ucl_parser_parse_string);
+ lua_setfield (L, -2, "parse_string");
+
+ lua_pushcfunction (L, lua_ucl_parser_get_object);
+ lua_setfield (L, -2, "get_object");
+
+ lua_pop (L, 1);
+}
+
+static int
+lua_ucl_to_string (lua_State *L, const ucl_object_t *obj, enum ucl_emitter type)
+{
+ unsigned char *result;
+
+ result = ucl_object_emit (obj, type);
+
+ if (result != NULL) {
+ lua_pushstring (L, (const char *)result);
+ free (result);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_to_json (lua_State *L)
+{
+ ucl_object_t *obj;
+ int format = UCL_EMIT_JSON;
+
+ if (lua_gettop (L) > 1) {
+ if (lua_toboolean (L, 2)) {
+ format = UCL_EMIT_JSON_COMPACT;
+ }
+ }
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, format);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_to_config (lua_State *L)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, UCL_EMIT_CONFIG);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+/***
+ * @function ucl.to_format(var, format)
+ * Converts lua variable `var` to the specified `format`. Formats supported are:
+ *
+ * - `json` - fine printed json
+ * - `json-compact` - compacted json
+ * - `config` - fine printed configuration
+ * - `ucl` - same as `config`
+ * - `yaml` - embedded yaml
+ *
+ * If `var` contains function, they are called during output formatting and if
+ * they return string value, then this value is used for ouptut.
+ * @param {variant} var any sort of lua variable (if userdata then metafield `__to_ucl` is searched for output)
+ * @param {string} format any available format
+ * @return {string} string representation of `var` in the specific `format`.
+ * @example
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+}
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+ */
+static int
+lua_ucl_to_format (lua_State *L)
+{
+ ucl_object_t *obj;
+ int format = UCL_EMIT_JSON;
+
+ if (lua_gettop (L) > 1) {
+ if (lua_type (L, 2) == LUA_TNUMBER) {
+ format = lua_tonumber (L, 2);
+ if (format < 0 || format >= UCL_EMIT_YAML) {
+ lua_pushnil (L);
+ return 1;
+ }
+ }
+ else if (lua_type (L, 2) == LUA_TSTRING) {
+ const char *strtype = lua_tostring (L, 2);
+
+ if (strcasecmp (strtype, "json") == 0) {
+ format = UCL_EMIT_JSON;
+ }
+ else if (strcasecmp (strtype, "json-compact") == 0) {
+ format = UCL_EMIT_JSON_COMPACT;
+ }
+ else if (strcasecmp (strtype, "yaml") == 0) {
+ format = UCL_EMIT_YAML;
+ }
+ else if (strcasecmp (strtype, "config") == 0 ||
+ strcasecmp (strtype, "ucl") == 0) {
+ format = UCL_EMIT_CONFIG;
+ }
+ }
+ }
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, format);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_null_tostring (lua_State* L)
+{
+ lua_pushstring (L, "null");
+ return 1;
+}
+
+static void
+lua_ucl_null_mt (lua_State *L)
+{
+ luaL_newmetatable (L, NULL_META);
+
+ lua_pushcfunction (L, lua_ucl_null_tostring);
+ lua_setfield (L, -2, "__tostring");
+
+ lua_pop (L, 1);
+}
+
+int
+luaopen_ucl (lua_State *L)
+{
+ lua_ucl_parser_mt (L);
+ lua_ucl_null_mt (L);
+
+ /* Create the refs weak table: */
+ lua_createtable (L, 0, 2);
+ lua_pushliteral (L, "v"); /* tbl, "v" */
+ lua_setfield (L, -2, "__mode");
+ lua_pushvalue (L, -1); /* tbl, tbl */
+ lua_setmetatable (L, -2); /* tbl */
+ lua_setfield (L, LUA_REGISTRYINDEX, "ucl.refs");
+
+ lua_newtable (L);
+
+ lua_pushcfunction (L, lua_ucl_parser_init);
+ lua_setfield (L, -2, "parser");
+
+ lua_pushcfunction (L, lua_ucl_to_json);
+ lua_setfield (L, -2, "to_json");
+
+ lua_pushcfunction (L, lua_ucl_to_config);
+ lua_setfield (L, -2, "to_config");
+
+ lua_pushcfunction (L, lua_ucl_to_format);
+ lua_setfield (L, -2, "to_format");
+
+ ucl_null = lua_newuserdata (L, 0);
+ luaL_getmetatable (L, NULL_META);
+ lua_setmetatable (L, -2);
+
+ lua_pushvalue (L, -1);
+ lua_setfield (L, LUA_REGISTRYINDEX, "ucl.null");
+
+ lua_setfield (L, -2, "null");
+
+ return 1;
+}
+
+struct ucl_lua_funcdata*
+ucl_object_toclosure (const ucl_object_t *obj)
+{
+ if (obj == NULL || obj->type != UCL_USERDATA) {
+ return NULL;
+ }
+
+ return (struct ucl_lua_funcdata*)obj->value.ud;
+}
diff --git a/contrib/libucl/lua_ucl.h b/contrib/libucl/lua_ucl.h
new file mode 100644
index 000000000..38e74d3f6
--- /dev/null
+++ b/contrib/libucl/lua_ucl.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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 LUA_UCL_H_
+#define LUA_UCL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include "ucl.h"
+
+/**
+ * Closure structure for lua function storing inside UCL
+ */
+struct ucl_lua_funcdata {
+ lua_State *L;
+ int idx;
+ char *ret;
+};
+
+/**
+ * Initialize lua UCL API
+ */
+UCL_EXTERN int luaopen_ucl (lua_State *L);
+
+/**
+ * Import UCL object from lua state
+ * @param L lua state
+ * @param idx index of object at the lua stack to convert to UCL
+ * @return new UCL object or NULL, the caller should unref object after using
+ */
+UCL_EXTERN ucl_object_t* ucl_object_lua_import (lua_State *L, int idx);
+
+/**
+ * Push an object to lua
+ * @param L lua state
+ * @param obj object to push
+ * @param allow_array traverse over implicit arrays
+ */
+UCL_EXTERN int ucl_object_push_lua (lua_State *L,
+ const ucl_object_t *obj, bool allow_array);
+
+UCL_EXTERN struct ucl_lua_funcdata* ucl_object_toclosure (
+ const ucl_object_t *obj);
+
+#endif /* LUA_UCL_H_ */
diff --git a/contrib/libucl/tree.h b/contrib/libucl/tree.h
new file mode 100644
index 000000000..cee937369
--- /dev/null
+++ b/contrib/libucl/tree.h
@@ -0,0 +1,212 @@
+/* tree.h -- AVL trees (in the spirit of BSD's 'queue.h') -*- C -*- */
+
+/* Copyright (c) 2005 Ian Piumarta
+ *
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the 'Software'), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * provided that the above copyright notice(s) and this permission notice appear
+ * in all copies of the Software and that both the above copyright notice(s) and
+ * this permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
+ */
+
+/* This file defines an AVL balanced binary tree [Georgii M. Adelson-Velskii and
+ * Evgenii M. Landis, 'An algorithm for the organization of information',
+ * Doklady Akademii Nauk SSSR, 146:263-266, 1962 (Russian). Also in Myron
+ * J. Ricci (trans.), Soviet Math, 3:1259-1263, 1962 (English)].
+ *
+ * An AVL tree is headed by pointers to the root node and to a function defining
+ * the ordering relation between nodes. Each node contains an arbitrary payload
+ * plus three fields per tree entry: the depth of the subtree for which it forms
+ * the root and two pointers to child nodes (singly-linked for minimum space, at
+ * the expense of direct access to the parent node given a pointer to one of the
+ * children). The tree is rebalanced after every insertion or removal. The
+ * tree may be traversed in two directions: forward (in-order left-to-right) and
+ * reverse (in-order, right-to-left).
+ *
+ * Because of the recursive nature of many of the operations on trees it is
+ * necessary to define a number of helper functions for each type of tree node.
+ * The macro TREE_DEFINE(node_tag, entry_name) defines these functions with
+ * unique names according to the node_tag. This macro should be invoked,
+ * thereby defining the necessary functions, once per node tag in the program.
+ *
+ * For details on the use of these macros, see the tree(3) manual page.
+ */
+
+#ifndef __tree_h
+#define __tree_h
+
+
+#define TREE_DELTA_MAX 1
+
+#define TREE_ENTRY(type) \
+ struct { \
+ struct type *avl_left; \
+ struct type *avl_right; \
+ int avl_height; \
+ }
+
+#define TREE_HEAD(name, type) \
+ struct name { \
+ struct type *th_root; \
+ int (*th_cmp)(struct type *lhs, struct type *rhs); \
+ }
+
+#define TREE_INITIALIZER(cmp) { 0, cmp }
+
+#define TREE_DELTA(self, field) \
+ (( (((self)->field.avl_left) ? (self)->field.avl_left->field.avl_height : 0)) \
+ - (((self)->field.avl_right) ? (self)->field.avl_right->field.avl_height : 0))
+
+/* Recursion prevents the following from being defined as macros. */
+
+#define TREE_DEFINE(node, field) \
+ \
+ struct node *TREE_BALANCE_##node##_##field(struct node *); \
+ \
+ struct node *TREE_ROTL_##node##_##field(struct node *self) \
+ { \
+ struct node *r= self->field.avl_right; \
+ self->field.avl_right= r->field.avl_left; \
+ r->field.avl_left= TREE_BALANCE_##node##_##field(self); \
+ return TREE_BALANCE_##node##_##field(r); \
+ } \
+ \
+ struct node *TREE_ROTR_##node##_##field(struct node *self) \
+ { \
+ struct node *l= self->field.avl_left; \
+ self->field.avl_left= l->field.avl_right; \
+ l->field.avl_right= TREE_BALANCE_##node##_##field(self); \
+ return TREE_BALANCE_##node##_##field(l); \
+ } \
+ \
+ struct node *TREE_BALANCE_##node##_##field(struct node *self) \
+ { \
+ int delta= TREE_DELTA(self, field); \
+ \
+ if (delta < -TREE_DELTA_MAX) \
+ { \
+ if (TREE_DELTA(self->field.avl_right, field) > 0) \
+ self->field.avl_right= TREE_ROTR_##node##_##field(self->field.avl_right); \
+ return TREE_ROTL_##node##_##field(self); \
+ } \
+ else if (delta > TREE_DELTA_MAX) \
+ { \
+ if (TREE_DELTA(self->field.avl_left, field) < 0) \
+ self->field.avl_left= TREE_ROTL_##node##_##field(self->field.avl_left); \
+ return TREE_ROTR_##node##_##field(self); \
+ } \
+ self->field.avl_height= 0; \
+ if (self->field.avl_left && (self->field.avl_left->field.avl_height > self->field.avl_height)) \
+ self->field.avl_height= self->field.avl_left->field.avl_height; \
+ if (self->field.avl_right && (self->field.avl_right->field.avl_height > self->field.avl_height)) \
+ self->field.avl_height= self->field.avl_right->field.avl_height; \
+ self->field.avl_height += 1; \
+ return self; \
+ } \
+ \
+ struct node *TREE_INSERT_##node##_##field \
+ (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \
+ { \
+ if (!self) \
+ return elm; \
+ if (compare(elm, self) < 0) \
+ self->field.avl_left= TREE_INSERT_##node##_##field(self->field.avl_left, elm, compare); \
+ else \
+ self->field.avl_right= TREE_INSERT_##node##_##field(self->field.avl_right, elm, compare); \
+ return TREE_BALANCE_##node##_##field(self); \
+ } \
+ \
+ struct node *TREE_FIND_##node##_##field \
+ (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \
+ { \
+ if (!self) \
+ return 0; \
+ if (compare(elm, self) == 0) \
+ return self; \
+ if (compare(elm, self) < 0) \
+ return TREE_FIND_##node##_##field(self->field.avl_left, elm, compare); \
+ else \
+ return TREE_FIND_##node##_##field(self->field.avl_right, elm, compare); \
+ } \
+ \
+ struct node *TREE_MOVE_RIGHT(struct node *self, struct node *rhs) \
+ { \
+ if (!self) \
+ return rhs; \
+ self->field.avl_right= TREE_MOVE_RIGHT(self->field.avl_right, rhs); \
+ return TREE_BALANCE_##node##_##field(self); \
+ } \
+ \
+ struct node *TREE_REMOVE_##node##_##field \
+ (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \
+ { \
+ if (!self) return 0; \
+ \
+ if (compare(elm, self) == 0) \
+ { \
+ struct node *tmp= TREE_MOVE_RIGHT(self->field.avl_left, self->field.avl_right); \
+ self->field.avl_left= 0; \
+ self->field.avl_right= 0; \
+ return tmp; \
+ } \
+ if (compare(elm, self) < 0) \
+ self->field.avl_left= TREE_REMOVE_##node##_##field(self->field.avl_left, elm, compare); \
+ else \
+ self->field.avl_right= TREE_REMOVE_##node##_##field(self->field.avl_right, elm, compare); \
+ return TREE_BALANCE_##node##_##field(self); \
+ } \
+ \
+ void TREE_FORWARD_APPLY_ALL_##node##_##field \
+ (struct node *self, void (*function)(struct node *node, void *data), void *data) \
+ { \
+ if (self) \
+ { \
+ TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \
+ function(self, data); \
+ TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \
+ } \
+ } \
+ \
+ void TREE_REVERSE_APPLY_ALL_##node##_##field \
+ (struct node *self, void (*function)(struct node *node, void *data), void *data) \
+ { \
+ if (self) \
+ { \
+ TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \
+ function(self, data); \
+ TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \
+ } \
+ }
+
+#define TREE_INSERT(head, node, field, elm) \
+ ((head)->th_root= TREE_INSERT_##node##_##field((head)->th_root, (elm), (head)->th_cmp))
+
+#define TREE_FIND(head, node, field, elm) \
+ (TREE_FIND_##node##_##field((head)->th_root, (elm), (head)->th_cmp))
+
+#define TREE_REMOVE(head, node, field, elm) \
+ ((head)->th_root= TREE_REMOVE_##node##_##field((head)->th_root, (elm), (head)->th_cmp))
+
+#define TREE_DEPTH(head, field) \
+ ((head)->th_root->field.avl_height)
+
+#define TREE_FORWARD_APPLY(head, node, field, function, data) \
+ TREE_FORWARD_APPLY_ALL_##node##_##field((head)->th_root, function, data)
+
+#define TREE_REVERSE_APPLY(head, node, field, function, data) \
+ TREE_REVERSE_APPLY_ALL_##node##_##field((head)->th_root, function, data)
+
+#define TREE_INIT(head, cmp) do { \
+ (head)->th_root= 0; \
+ (head)->th_cmp= (cmp); \
+ } while (0)
+
+
+#endif /* __tree_h */
diff --git a/contrib/libucl/ucl.h b/contrib/libucl/ucl.h
new file mode 100644
index 000000000..823ac8d3b
--- /dev/null
+++ b/contrib/libucl/ucl.h
@@ -0,0 +1,1144 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCL_H_
+#define UCL_H_
+
+#include <string.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef _WIN32
+# define UCL_EXTERN __declspec(dllexport)
+#else
+# define UCL_EXTERN
+#endif
+
+/**
+ * @mainpage
+ * This is a reference manual for UCL API. You may find the description of UCL format by following this
+ * [github repository](https://github.com/vstakhov/libucl).
+ *
+ * This manual has several main sections:
+ * - @ref structures
+ * - @ref utils
+ * - @ref parser
+ * - @ref emitter
+ */
+
+/**
+ * @file ucl.h
+ * @brief UCL parsing and emitting functions
+ *
+ * UCL is universal configuration language, which is a form of
+ * JSON with less strict rules that make it more comfortable for
+ * using as a configuration language
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Memory allocation utilities
+ * UCL_ALLOC(size) - allocate memory for UCL
+ * UCL_FREE(size, ptr) - free memory of specified size at ptr
+ * Default: malloc and free
+ */
+#ifndef UCL_ALLOC
+#define UCL_ALLOC(size) malloc(size)
+#endif
+#ifndef UCL_FREE
+#define UCL_FREE(size, ptr) free(ptr)
+#endif
+
+#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#define UCL_WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+#else
+#define UCL_WARN_UNUSED_RESULT
+#endif
+
+#ifdef __GNUC__
+#define UCL_DEPRECATED(func) func __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define UCL_DEPRECATED(func) __declspec(deprecated) func
+#else
+#define UCL_DEPRECATED(func) func
+#endif
+
+/**
+ * @defgroup structures Structures and types
+ * UCL defines several enumeration types used for error reporting or specifying flags and attributes.
+ *
+ * @{
+ */
+
+/**
+ * The common error codes returned by ucl parser
+ */
+typedef enum ucl_error {
+ UCL_EOK = 0, /**< No error */
+ UCL_ESYNTAX, /**< Syntax error occurred during parsing */
+ UCL_EIO, /**< IO error occurred during parsing */
+ UCL_ESTATE, /**< Invalid state machine state */
+ UCL_ENESTED, /**< Input has too many recursion levels */
+ UCL_EMACRO, /**< Error processing a macro */
+ UCL_EINTERNAL, /**< Internal unclassified error */
+ UCL_ESSL /**< SSL error */
+} ucl_error_t;
+
+/**
+ * #ucl_object_t may have one of specified types, some types are compatible with each other and some are not.
+ * For example, you can always convert #UCL_TIME to #UCL_FLOAT. Also you can convert #UCL_FLOAT to #UCL_INTEGER
+ * by loosing floating point. Every object may be converted to a string by #ucl_object_tostring_forced() function.
+ *
+ */
+typedef enum ucl_type {
+ UCL_OBJECT = 0, /**< UCL object - key/value pairs */
+ UCL_ARRAY, /**< UCL array */
+ UCL_INT, /**< Integer number */
+ UCL_FLOAT, /**< Floating point number */
+ UCL_STRING, /**< Null terminated string */
+ UCL_BOOLEAN, /**< Boolean value */
+ UCL_TIME, /**< Time value (floating point number of seconds) */
+ UCL_USERDATA, /**< Opaque userdata pointer (may be used in macros) */
+ UCL_NULL /**< Null value */
+} ucl_type_t;
+
+/**
+ * You can use one of these types to serialise #ucl_object_t by using ucl_object_emit().
+ */
+typedef enum ucl_emitter {
+ UCL_EMIT_JSON = 0, /**< Emit fine formatted JSON */
+ UCL_EMIT_JSON_COMPACT, /**< Emit compacted JSON */
+ UCL_EMIT_CONFIG, /**< Emit human readable config format */
+ UCL_EMIT_YAML /**< Emit embedded YAML format */
+} ucl_emitter_t;
+
+/**
+ * These flags defines parser behaviour. If you specify #UCL_PARSER_ZEROCOPY you must ensure
+ * that the input memory is not freed if an object is in use. Moreover, if you want to use
+ * zero-terminated keys and string values then you should not use zero-copy mode, as in this case
+ * UCL still has to perform copying implicitly.
+ */
+typedef enum ucl_parser_flags {
+ UCL_PARSER_KEY_LOWERCASE = 0x1, /**< Convert all keys to lower case */
+ UCL_PARSER_ZEROCOPY = 0x2, /**< Parse input in zero-copy mode if possible */
+ UCL_PARSER_NO_TIME = 0x4, /**< Do not parse time and treat time values as strings */
+ UCL_PARSER_NO_IMPLICIT_ARRAYS = 0x8 /** Create explicit arrays instead of implicit ones */
+} ucl_parser_flags_t;
+
+/**
+ * String conversion flags, that are used in #ucl_object_fromstring_common function.
+ */
+typedef enum ucl_string_flags {
+ UCL_STRING_ESCAPE = 0x1, /**< Perform JSON escape */
+ UCL_STRING_TRIM = 0x2, /**< Trim leading and trailing whitespaces */
+ UCL_STRING_PARSE_BOOLEAN = 0x4, /**< Parse passed string and detect boolean */
+ UCL_STRING_PARSE_INT = 0x8, /**< Parse passed string and detect integer number */
+ UCL_STRING_PARSE_DOUBLE = 0x10, /**< Parse passed string and detect integer or float number */
+ UCL_STRING_PARSE_TIME = 0x20, /**< Parse time strings */
+ UCL_STRING_PARSE_NUMBER = UCL_STRING_PARSE_INT|UCL_STRING_PARSE_DOUBLE|UCL_STRING_PARSE_TIME, /**<
+ Parse passed string and detect number */
+ UCL_STRING_PARSE = UCL_STRING_PARSE_BOOLEAN|UCL_STRING_PARSE_NUMBER, /**<
+ Parse passed string (and detect booleans and numbers) */
+ UCL_STRING_PARSE_BYTES = 0x40 /**< Treat numbers as bytes */
+} ucl_string_flags_t;
+
+/**
+ * Basic flags for an object
+ */
+typedef enum ucl_object_flags {
+ UCL_OBJECT_ALLOCATED_KEY = 0x1, /**< An object has key allocated internally */
+ UCL_OBJECT_ALLOCATED_VALUE = 0x2, /**< An object has a string value allocated internally */
+ UCL_OBJECT_NEED_KEY_ESCAPE = 0x4, /**< The key of an object need to be escaped on output */
+ UCL_OBJECT_EPHEMERAL = 0x8, /**< Temporary object that does not need to be freed really */
+ UCL_OBJECT_MULTILINE = 0x10, /**< String should be displayed as multiline string */
+ UCL_OBJECT_MULTIVALUE = 0x20 /**< Object is a key with multiple values */
+} ucl_object_flags_t;
+
+/**
+ * UCL object structure. Please mention that the most of fields should not be touched by
+ * UCL users. In future, this structure may be converted to private one.
+ */
+typedef struct ucl_object_s {
+ /**
+ * Variant value type
+ */
+ union {
+ int64_t iv; /**< Int value of an object */
+ const char *sv; /**< String value of an object */
+ double dv; /**< Double value of an object */
+ void *av; /**< Array */
+ void *ov; /**< Object */
+ void* ud; /**< Opaque user data */
+ } value;
+ const char *key; /**< Key of an object */
+ struct ucl_object_s *next; /**< Array handle */
+ struct ucl_object_s *prev; /**< Array handle */
+ uint32_t keylen; /**< Lenght of a key */
+ uint32_t len; /**< Size of an object */
+ uint32_t ref; /**< Reference count */
+ uint16_t flags; /**< Object flags */
+ uint16_t type; /**< Real type */
+ unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
+} ucl_object_t;
+
+/**
+ * Destructor type for userdata objects
+ * @param ud user specified data pointer
+ */
+typedef void (*ucl_userdata_dtor)(void *ud);
+typedef const char* (*ucl_userdata_emitter)(void *ud);
+
+/** @} */
+
+/**
+ * @defgroup utils Utility functions
+ * A number of utility functions simplify handling of UCL objects
+ *
+ * @{
+ */
+/**
+ * Copy and return a key of an object, returned key is zero-terminated
+ * @param obj CL object
+ * @return zero terminated key
+ */
+UCL_EXTERN char* ucl_copy_key_trash (const ucl_object_t *obj);
+
+/**
+ * Copy and return a string value of an object, returned key is zero-terminated
+ * @param obj CL object
+ * @return zero terminated string representation of object value
+ */
+UCL_EXTERN char* ucl_copy_value_trash (const ucl_object_t *obj);
+
+/**
+ * Creates a new object
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_new (void) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create new object with type specified
+ * @param type type of a new object
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_typed_new (ucl_type_t type) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create new object with type and priority specified
+ * @param type type of a new object
+ * @param priority priority of an object
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_new_full (ucl_type_t type, unsigned priority)
+ UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create new object with userdata dtor
+ * @param dtor destructor function
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_new_userdata (ucl_userdata_dtor dtor,
+ ucl_userdata_emitter emitter) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Perform deep copy of an object copying everything
+ * @param other object to copy
+ * @return new object with refcount equal to 1
+ */
+UCL_EXTERN ucl_object_t * ucl_object_copy (const ucl_object_t *other)
+ UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Return the type of an object
+ * @return the object type
+ */
+UCL_EXTERN ucl_type_t ucl_object_type (const ucl_object_t *obj);
+
+/**
+ * Convert any string to an ucl object making the specified transformations
+ * @param str fixed size or NULL terminated string
+ * @param len length (if len is zero, than str is treated as NULL terminated)
+ * @param flags conversion flags
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t * ucl_object_fromstring_common (const char *str, size_t len,
+ enum ucl_string_flags flags) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create a UCL object from the specified string
+ * @param str NULL terminated string, will be json escaped
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t *ucl_object_fromstring (const char *str) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create a UCL object from the specified string
+ * @param str fixed size string, will be json escaped
+ * @param len length of a string
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t *ucl_object_fromlstring (const char *str,
+ size_t len) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create an object from an integer number
+ * @param iv number
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_fromint (int64_t iv) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create an object from a float number
+ * @param dv number
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_fromdouble (double dv) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create an object from a boolean
+ * @param bv bool value
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_frombool (bool bv) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Insert a object 'elt' to the hash 'top' and associate it with key 'key'
+ * @param top destination object (must be of type UCL_OBJECT)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return true if key has been inserted
+ */
+UCL_EXTERN bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key);
+
+/**
+ * Replace a object 'elt' to the hash 'top' and associate it with key 'key', old object will be unrefed,
+ * if no object has been found this function works like ucl_object_insert_key()
+ * @param top destination object (must be of type UCL_OBJECT)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return true if key has been inserted
+ */
+UCL_EXTERN bool ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key);
+
+/**
+ * Merge the keys from one object to another object. Overwrite on conflict
+ * @param top destination object (must be of type UCL_OBJECT)
+ * @param elt element to insert (must be of type UCL_OBJECT)
+ * @param copy copy rather than reference the elements
+ * @return true if all keys have been merged
+ */
+UCL_EXTERN bool ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy);
+
+/**
+ * Delete a object associated with key 'key', old object will be unrefered,
+ * @param top object
+ * @param key key associated to the object to remove
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ */
+UCL_EXTERN bool ucl_object_delete_keyl (ucl_object_t *top,
+ const char *key, size_t keylen);
+
+/**
+ * Delete a object associated with key 'key', old object will be unrefered,
+ * @param top object
+ * @param key key associated to the object to remove
+ */
+UCL_EXTERN bool ucl_object_delete_key (ucl_object_t *top,
+ const char *key);
+
+
+/**
+ * Removes `key` from `top` object, returning the object that was removed. This
+ * object is not released, caller must unref the returned object when it is no
+ * longer needed.
+ * @param top object
+ * @param key key to remove
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @return removed object or NULL if object has not been found
+ */
+UCL_EXTERN ucl_object_t* ucl_object_pop_keyl (ucl_object_t *top, const char *key,
+ size_t keylen) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Removes `key` from `top` object returning the object that was removed. This
+ * object is not released, caller must unref the returned object when it is no
+ * longer needed.
+ * @param top object
+ * @param key key to remove
+ * @return removed object or NULL if object has not been found
+ */
+UCL_EXTERN ucl_object_t* ucl_object_pop_key (ucl_object_t *top, const char *key)
+ UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Insert a object 'elt' to the hash 'top' and associate it with key 'key', if
+ * the specified key exist, try to merge its content
+ * @param top destination object (must be of type UCL_OBJECT)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return true if key has been inserted
+ */
+UCL_EXTERN bool ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key);
+
+/**
+ * Append an element to the end of array object
+ * @param top destination object (must NOT be NULL)
+ * @param elt element to append (must NOT be NULL)
+ * @return true if value has been inserted
+ */
+UCL_EXTERN bool ucl_array_append (ucl_object_t *top,
+ ucl_object_t *elt);
+
+/**
+ * Append an element to the start of array object
+ * @param top destination object (must NOT be NULL)
+ * @param elt element to append (must NOT be NULL)
+ * @return true if value has been inserted
+ */
+UCL_EXTERN bool ucl_array_prepend (ucl_object_t *top,
+ ucl_object_t *elt);
+
+/**
+ * Merge all elements of second array into the first array
+ * @param top destination array (must be of type UCL_ARRAY)
+ * @param elt array to copy elements from (must be of type UCL_ARRAY)
+ * @param copy copy elements instead of referencing them
+ * @return true if arrays were merged
+ */
+UCL_EXTERN bool ucl_array_merge (ucl_object_t *top, ucl_object_t *elt,
+ bool copy);
+
+/**
+ * Removes an element `elt` from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
+ * @param top array ucl object
+ * @param elt element to remove
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN ucl_object_t* ucl_array_delete (ucl_object_t *top,
+ ucl_object_t *elt);
+
+/**
+ * Returns the first element of the array `top`
+ * @param top array ucl object
+ * @return element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN const ucl_object_t* ucl_array_head (const ucl_object_t *top);
+
+/**
+ * Returns the last element of the array `top`
+ * @param top array ucl object
+ * @return element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN const ucl_object_t* ucl_array_tail (const ucl_object_t *top);
+
+/**
+ * Removes the last element from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
+ * @param top array ucl object
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN ucl_object_t* ucl_array_pop_last (ucl_object_t *top);
+
+/**
+ * Removes the first element from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
+ * @param top array ucl object
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN ucl_object_t* ucl_array_pop_first (ucl_object_t *top);
+
+/**
+ * Return object identified by index of the array `top`
+ * @param top object to get a key from (must be of type UCL_ARRAY)
+ * @param index array index to return
+ * @return object at the specified index or NULL if index is not found
+ */
+UCL_EXTERN const ucl_object_t* ucl_array_find_index (const ucl_object_t *top,
+ unsigned int index);
+
+/**
+ * Replace an element in an array with a different element, returning the object
+ * that was replaced. This object is not released, caller must unref the
+ * returned object when it is no longer needed.
+ * @param top destination object (must be of type UCL_ARRAY)
+ * @param elt element to append (must NOT be NULL)
+ * @param index array index in destination to overwrite with elt
+ * @return object that was replaced or NULL if index is not found
+ */
+ucl_object_t *
+ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
+ unsigned int index);
+
+/**
+ * Append a element to another element forming an implicit array
+ * @param head head to append (may be NULL)
+ * @param elt new element
+ * @return the new implicit array
+ */
+UCL_EXTERN ucl_object_t * ucl_elt_append (ucl_object_t *head,
+ ucl_object_t *elt);
+
+/**
+ * Converts an object to double value
+ * @param obj CL object
+ * @param target target double variable
+ * @return true if conversion was successful
+ */
+UCL_EXTERN bool ucl_object_todouble_safe (const ucl_object_t *obj, double *target);
+
+/**
+ * Unsafe version of \ref ucl_obj_todouble_safe
+ * @param obj CL object
+ * @return double value
+ */
+UCL_EXTERN double ucl_object_todouble (const ucl_object_t *obj);
+
+/**
+ * Converts an object to integer value
+ * @param obj CL object
+ * @param target target integer variable
+ * @return true if conversion was successful
+ */
+UCL_EXTERN bool ucl_object_toint_safe (const ucl_object_t *obj, int64_t *target);
+
+/**
+ * Unsafe version of \ref ucl_obj_toint_safe
+ * @param obj CL object
+ * @return int value
+ */
+UCL_EXTERN int64_t ucl_object_toint (const ucl_object_t *obj);
+
+/**
+ * Converts an object to boolean value
+ * @param obj CL object
+ * @param target target boolean variable
+ * @return true if conversion was successful
+ */
+UCL_EXTERN bool ucl_object_toboolean_safe (const ucl_object_t *obj, bool *target);
+
+/**
+ * Unsafe version of \ref ucl_obj_toboolean_safe
+ * @param obj CL object
+ * @return boolean value
+ */
+UCL_EXTERN bool ucl_object_toboolean (const ucl_object_t *obj);
+
+/**
+ * Converts an object to string value
+ * @param obj CL object
+ * @param target target string variable, no need to free value
+ * @return true if conversion was successful
+ */
+UCL_EXTERN bool ucl_object_tostring_safe (const ucl_object_t *obj, const char **target);
+
+/**
+ * Unsafe version of \ref ucl_obj_tostring_safe
+ * @param obj CL object
+ * @return string value
+ */
+UCL_EXTERN const char* ucl_object_tostring (const ucl_object_t *obj);
+
+/**
+ * Convert any object to a string in JSON notation if needed
+ * @param obj CL object
+ * @return string value
+ */
+UCL_EXTERN const char* ucl_object_tostring_forced (const ucl_object_t *obj);
+
+/**
+ * Return string as char * and len, string may be not zero terminated, more efficient that \ref ucl_obj_tostring as it
+ * allows zero-copy (if #UCL_PARSER_ZEROCOPY has been used during parsing)
+ * @param obj CL object
+ * @param target target string variable, no need to free value
+ * @param tlen target length
+ * @return true if conversion was successful
+ */
+UCL_EXTERN bool ucl_object_tolstring_safe (const ucl_object_t *obj,
+ const char **target, size_t *tlen);
+
+/**
+ * Unsafe version of \ref ucl_obj_tolstring_safe
+ * @param obj CL object
+ * @return string value
+ */
+UCL_EXTERN const char* ucl_object_tolstring (const ucl_object_t *obj, size_t *tlen);
+
+/**
+ * Return object identified by a key in the specified object
+ * @param obj object to get a key from (must be of type UCL_OBJECT)
+ * @param key key to search
+ * @return object matching the specified key or NULL if key was not found
+ */
+UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj,
+ const char *key);
+
+/**
+ * Return object identified by a fixed size key in the specified object
+ * @param obj object to get a key from (must be of type UCL_OBJECT)
+ * @param key key to search
+ * @param klen length of a key
+ * @return object matching the specified key or NULL if key was not found
+ */
+UCL_EXTERN const ucl_object_t* ucl_object_find_keyl (const ucl_object_t *obj,
+ const char *key, size_t klen);
+
+/**
+ * Return object identified by dot notation string
+ * @param obj object to search in
+ * @param path dot.notation.path to the path to lookup. May use numeric .index on arrays
+ * @return object matched the specified path or NULL if path is not found
+ */
+UCL_EXTERN const ucl_object_t *ucl_lookup_path (const ucl_object_t *obj,
+ const char *path);
+
+/**
+ * Returns a key of an object as a NULL terminated string
+ * @param obj CL object
+ * @return key or NULL if there is no key
+ */
+UCL_EXTERN const char* ucl_object_key (const ucl_object_t *obj);
+
+/**
+ * Returns a key of an object as a fixed size string (may be more efficient)
+ * @param obj CL object
+ * @param len target key length
+ * @return key pointer
+ */
+UCL_EXTERN const char* ucl_object_keyl (const ucl_object_t *obj, size_t *len);
+
+/**
+ * Increase reference count for an object
+ * @param obj object to ref
+ * @return the referenced object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_ref (const ucl_object_t *obj);
+
+/**
+ * Free ucl object
+ * @param obj ucl object to free
+ */
+UCL_DEPRECATED(UCL_EXTERN void ucl_object_free (ucl_object_t *obj));
+
+/**
+ * Decrease reference count for an object
+ * @param obj object to unref
+ */
+UCL_EXTERN void ucl_object_unref (ucl_object_t *obj);
+
+/**
+ * Compare objects `o1` and `o2`
+ * @param o1 the first object
+ * @param o2 the second object
+ * @return values >0, 0 and <0 if `o1` is more than, equal and less than `o2`.
+ * The order of comparison:
+ * 1) Type of objects
+ * 2) Size of objects
+ * 3) Content of objects
+ */
+UCL_EXTERN int ucl_object_compare (const ucl_object_t *o1,
+ const ucl_object_t *o2);
+
+/**
+ * Sort UCL array using `cmp` compare function
+ * @param ar
+ * @param cmp
+ */
+UCL_EXTERN void ucl_object_array_sort (ucl_object_t *ar,
+ int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2));
+
+/**
+ * Get the priority for specific UCL object
+ * @param obj any ucl object
+ * @return priority of an object
+ */
+UCL_EXTERN unsigned int ucl_object_get_priority (const ucl_object_t *obj);
+
+/**
+ * Set explicit priority of an object.
+ * @param obj any ucl object
+ * @param priority new priroity value (only 4 least significant bits are considred)
+ */
+UCL_EXTERN void ucl_object_set_priority (ucl_object_t *obj,
+ unsigned int priority);
+
+/**
+ * Opaque iterator object
+ */
+typedef void* ucl_object_iter_t;
+
+/**
+ * Get next key from an object
+ * @param obj object to iterate
+ * @param iter opaque iterator, must be set to NULL on the first call:
+ * ucl_object_iter_t it = NULL;
+ * while ((cur = ucl_iterate_object (obj, &it)) != NULL) ...
+ * @return the next object or NULL
+ */
+UCL_EXTERN const ucl_object_t* ucl_iterate_object (const ucl_object_t *obj,
+ ucl_object_iter_t *iter, bool expand_values);
+
+/**
+ * Create new safe iterator for the specified object
+ * @param obj object to iterate
+ * @return new iterator object that should be used with safe iterators API only
+ */
+UCL_EXTERN ucl_object_iter_t ucl_object_iterate_new (const ucl_object_t *obj)
+ UCL_WARN_UNUSED_RESULT;
+/**
+ * Reset initialized iterator to a new object
+ * @param obj new object to iterate
+ * @return modified iterator object
+ */
+UCL_EXTERN ucl_object_iter_t ucl_object_iterate_reset (ucl_object_iter_t it,
+ const ucl_object_t *obj);
+
+/**
+ * Get the next object from the `obj`. This fucntion iterates over arrays, objects
+ * and implicit arrays
+ * @param iter safe iterator
+ * @return the next object in sequence
+ */
+UCL_EXTERN const ucl_object_t* ucl_object_iterate_safe (ucl_object_iter_t iter,
+ bool expand_values);
+
+/**
+ * Free memory associated with the safe iterator
+ * @param it safe iterator object
+ */
+UCL_EXTERN void ucl_object_iterate_free (ucl_object_iter_t it);
+
+/** @} */
+
+
+/**
+ * @defgroup parser Parsing functions
+ * These functions are used to parse UCL objects
+ *
+ * @{
+ */
+
+/**
+ * Macro handler for a parser
+ * @param data the content of macro
+ * @param len the length of content
+ * @param arguments arguments object
+ * @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,
+ const ucl_object_t *arguments,
+ void* ud);
+
+/* Opaque parser */
+struct ucl_parser;
+
+/**
+ * Creates new parser object
+ * @param pool pool to allocate memory from
+ * @return new parser object
+ */
+UCL_EXTERN struct ucl_parser* ucl_parser_new (int flags);
+
+/**
+ * Register new handler for a macro
+ * @param parser parser object
+ * @param macro macro name (without leading dot)
+ * @param handler handler (it is called immediately after macro is parsed)
+ * @param ud opaque user data for a handler
+ */
+UCL_EXTERN void ucl_parser_register_macro (struct ucl_parser *parser, const char *macro,
+ ucl_macro_handler handler, void* ud);
+
+/**
+ * Handler to detect unregistered variables
+ * @param data variable data
+ * @param len length of variable
+ * @param replace (out) replace value for variable
+ * @param replace_len (out) replace length for variable
+ * @param need_free (out) UCL will free `dest` after usage
+ * @param ud opaque userdata
+ * @return true if variable
+ */
+typedef bool (*ucl_variable_handler) (const unsigned char *data, size_t len,
+ unsigned char **replace, size_t *replace_len, bool *need_free, void* ud);
+
+/**
+ * Register new parser variable
+ * @param parser parser object
+ * @param var variable name
+ * @param value variable value
+ */
+UCL_EXTERN void ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
+ const char *value);
+
+/**
+ * Set handler for unknown variables
+ * @param parser parser structure
+ * @param handler desired handler
+ * @param ud opaque data for the handler
+ */
+UCL_EXTERN void ucl_parser_set_variables_handler (struct ucl_parser *parser,
+ ucl_variable_handler handler, void *ud);
+
+/**
+ * Load new chunk to a parser
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @return true if chunk has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_chunk (struct ucl_parser *parser,
+ const unsigned char *data, size_t len);
+
+/**
+ * Load new chunk to a parser with the specified priority
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @param priority the desired priority of a chunk (only 4 least significant bits
+ * are considered for this parameter)
+ * @return true if chunk has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_chunk_priority (struct ucl_parser *parser,
+ const unsigned char *data, size_t len, unsigned priority);
+
+/**
+ * Load ucl object from a string
+ * @param parser parser structure
+ * @param data the pointer to the string
+ * @param len the length of the string, if `len` is 0 then `data` must be zero-terminated string
+ * @return true if string has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_string (struct ucl_parser *parser,
+ const char *data,size_t len);
+
+/**
+ * Load and add data from a file
+ * @param parser parser structure
+ * @param filename the name of file
+ * @param err if *err is NULL it is set to parser error
+ * @return true if chunk has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_file (struct ucl_parser *parser,
+ const char *filename);
+
+/**
+ * Load and add data from a file descriptor
+ * @param parser parser structure
+ * @param filename the name of file
+ * @param err if *err is NULL it is set to parser error
+ * @return true if chunk has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_fd (struct ucl_parser *parser,
+ int fd);
+
+/**
+ * Get a top object for a parser (refcount is increased)
+ * @param parser parser structure
+ * @param err if *err is NULL it is set to parser error
+ * @return top parser object or NULL
+ */
+UCL_EXTERN ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser);
+
+/**
+ * Get the error string if failing
+ * @param parser parser object
+ */
+UCL_EXTERN const char *ucl_parser_get_error(struct ucl_parser *parser);
+
+/**
+ * Clear the error in the parser
+ * @param parser parser object
+ */
+UCL_EXTERN void ucl_parser_clear_error(struct ucl_parser *parser);
+
+/**
+ * Free ucl parser object
+ * @param parser parser object
+ */
+UCL_EXTERN void ucl_parser_free (struct ucl_parser *parser);
+
+/**
+ * Add new public key to parser for signatures check
+ * @param parser parser object
+ * @param key PEM representation of a key
+ * @param len length of the key
+ * @param err if *err is NULL it is set to parser error
+ * @return true if a key has been successfully added
+ */
+UCL_EXTERN bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len);
+
+/**
+ * Set FILENAME and CURDIR variables in parser
+ * @param parser parser object
+ * @param filename filename to set or NULL to set FILENAME to "undef" and CURDIR to getcwd()
+ * @param need_expand perform realpath() if this variable is true and filename is not NULL
+ * @return true if variables has been set
+ */
+UCL_EXTERN bool ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename,
+ bool need_expand);
+
+/** @} */
+
+/**
+ * @defgroup emitter Emitting functions
+ * These functions are used to serialise UCL objects to some string representation.
+ *
+ * @{
+ */
+
+struct ucl_emitter_context;
+/**
+ * Structure using for emitter callbacks
+ */
+struct ucl_emitter_functions {
+ /** Append a single character */
+ int (*ucl_emitter_append_character) (unsigned char c, size_t nchars, void *ud);
+ /** Append a string of a specified length */
+ int (*ucl_emitter_append_len) (unsigned const char *str, size_t len, void *ud);
+ /** Append a 64 bit integer */
+ int (*ucl_emitter_append_int) (int64_t elt, void *ud);
+ /** Append floating point element */
+ int (*ucl_emitter_append_double) (double elt, void *ud);
+ /** Free userdata */
+ void (*ucl_emitter_free_func)(void *ud);
+ /** Opaque userdata pointer */
+ void *ud;
+};
+
+struct ucl_emitter_operations {
+ /** Write a primitive element */
+ void (*ucl_emitter_write_elt) (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool first, bool print_key);
+ /** Start ucl object */
+ void (*ucl_emitter_start_object) (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool print_key);
+ /** End ucl object */
+ void (*ucl_emitter_end_object) (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj);
+ /** Start ucl array */
+ void (*ucl_emitter_start_array) (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool print_key);
+ void (*ucl_emitter_end_array) (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj);
+};
+
+/**
+ * Structure that defines emitter functions
+ */
+struct ucl_emitter_context {
+ /** Name of emitter (e.g. json, compact_json) */
+ const char *name;
+ /** Unique id (e.g. UCL_EMIT_JSON for standard emitters */
+ int id;
+ /** A set of output functions */
+ const struct ucl_emitter_functions *func;
+ /** A set of output operations */
+ const struct ucl_emitter_operations *ops;
+ /** Current amount of indent tabs */
+ unsigned int indent;
+ /** Top level object */
+ const ucl_object_t *top;
+ /** The rest of context */
+ unsigned char data[1];
+};
+
+/**
+ * Emit object to a string
+ * @param obj object
+ * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is
+ * #UCL_EMIT_CONFIG then emit config like object
+ * @return dump of an object (must be freed after using) or NULL in case of error
+ */
+UCL_EXTERN unsigned char *ucl_object_emit (const ucl_object_t *obj,
+ enum ucl_emitter emit_type);
+
+/**
+ * Emit object to a string
+ * @param obj object
+ * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is
+ * #UCL_EMIT_CONFIG then emit config like object
+ * @param emitter a set of emitter functions
+ * @return dump of an object (must be freed after using) or NULL in case of error
+ */
+UCL_EXTERN bool ucl_object_emit_full (const ucl_object_t *obj,
+ enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter);
+
+/**
+ * Start streamlined UCL object emitter
+ * @param obj top UCL object
+ * @param emit_type emit type
+ * @param emitter a set of emitter functions
+ * @return new streamlined context that should be freed by
+ * `ucl_object_emit_streamline_finish`
+ */
+UCL_EXTERN struct ucl_emitter_context* ucl_object_emit_streamline_new (
+ const ucl_object_t *obj, enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter);
+
+/**
+ * Start object or array container for the streamlined output
+ * @param ctx streamlined context
+ * @param obj container object
+ */
+UCL_EXTERN void ucl_object_emit_streamline_start_container (
+ struct ucl_emitter_context *ctx, const ucl_object_t *obj);
+/**
+ * Add a complete UCL object to streamlined output
+ * @param ctx streamlined context
+ * @param obj object to output
+ */
+UCL_EXTERN void ucl_object_emit_streamline_add_object (
+ struct ucl_emitter_context *ctx, const ucl_object_t *obj);
+/**
+ * End previously added container
+ * @param ctx streamlined context
+ */
+UCL_EXTERN void ucl_object_emit_streamline_end_container (
+ struct ucl_emitter_context *ctx);
+/**
+ * Terminate streamlined container finishing all containers in it
+ * @param ctx streamlined context
+ */
+UCL_EXTERN void ucl_object_emit_streamline_finish (
+ struct ucl_emitter_context *ctx);
+
+/**
+ * Returns functions to emit object to memory
+ * @param pmem target pointer (should be freed by caller)
+ * @return emitter functions structure
+ */
+UCL_EXTERN struct ucl_emitter_functions* ucl_object_emit_memory_funcs (
+ void **pmem);
+
+/**
+ * Returns functions to emit object to FILE *
+ * @param fp FILE * object
+ * @return emitter functions structure
+ */
+UCL_EXTERN struct ucl_emitter_functions* ucl_object_emit_file_funcs (
+ FILE *fp);
+/**
+ * Returns functions to emit object to a file descriptor
+ * @param fd file descriptor
+ * @return emitter functions structure
+ */
+UCL_EXTERN struct ucl_emitter_functions* ucl_object_emit_fd_funcs (
+ int fd);
+
+/**
+ * Free emitter functions
+ * @param f pointer to functions
+ */
+UCL_EXTERN void ucl_object_emit_funcs_free (struct ucl_emitter_functions *f);
+
+/** @} */
+
+/**
+ * @defgroup schema Schema functions
+ * These functions are used to validate UCL objects using json schema format
+ *
+ * @{
+ */
+
+/**
+ * Used to define UCL schema error
+ */
+enum ucl_schema_error_code {
+ UCL_SCHEMA_OK = 0, /**< no error */
+ UCL_SCHEMA_TYPE_MISMATCH, /**< type of object is incorrect */
+ UCL_SCHEMA_INVALID_SCHEMA, /**< schema is invalid */
+ UCL_SCHEMA_MISSING_PROPERTY,/**< one or more missing properties */
+ UCL_SCHEMA_CONSTRAINT, /**< constraint found */
+ UCL_SCHEMA_MISSING_DEPENDENCY, /**< missing dependency */
+ UCL_SCHEMA_UNKNOWN /**< generic error */
+};
+
+/**
+ * Generic ucl schema error
+ */
+struct ucl_schema_error {
+ enum ucl_schema_error_code code; /**< error code */
+ char msg[128]; /**< error message */
+ const ucl_object_t *obj; /**< object where error occured */
+};
+
+/**
+ * Validate object `obj` using schema object `schema`.
+ * @param schema schema object
+ * @param obj object to validate
+ * @param err error pointer, if this parameter is not NULL and error has been
+ * occured, then `err` is filled with the exact error definition.
+ * @return true if `obj` is valid using `schema`
+ */
+UCL_EXTERN bool ucl_object_validate (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+/*
+ * XXX: Poorly named API functions, need to replace them with the appropriate
+ * named function. All API functions *must* use naming ucl_object_*. Usage of
+ * ucl_obj* should be avoided.
+ */
+#define ucl_obj_todouble_safe ucl_object_todouble_safe
+#define ucl_obj_todouble ucl_object_todouble
+#define ucl_obj_tostring ucl_object_tostring
+#define ucl_obj_tostring_safe ucl_object_tostring_safe
+#define ucl_obj_tolstring ucl_object_tolstring
+#define ucl_obj_tolstring_safe ucl_object_tolstring_safe
+#define ucl_obj_toint ucl_object_toint
+#define ucl_obj_toint_safe ucl_object_toint_safe
+#define ucl_obj_toboolean ucl_object_toboolean
+#define ucl_obj_toboolean_safe ucl_object_toboolean_safe
+#define ucl_obj_get_key ucl_object_find_key
+#define ucl_obj_get_keyl ucl_object_find_keyl
+#define ucl_obj_unref ucl_object_unref
+#define ucl_obj_ref ucl_object_ref
+#define ucl_obj_free ucl_object_free
+
+#endif /* UCL_H_ */
diff --git a/contrib/libucl/ucl_chartable.h b/contrib/libucl/ucl_chartable.h
new file mode 100644
index 000000000..5248e117c
--- /dev/null
+++ b/contrib/libucl/ucl_chartable.h
@@ -0,0 +1,267 @@
+/* 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_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_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_UCL_UNSAFE /* */,
+UCL_CHARACTER_VALUE_STR /* ! */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_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_UCL_UNSAFE /* : */,
+UCL_CHARACTER_VALUE_END /* ; */, UCL_CHARACTER_VALUE_STR /* < */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_UCL_UNSAFE /* = */,
+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_UCL_UNSAFE /* [ */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_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_UCL_UNSAFE /* { */,
+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/contrib/libucl/ucl_emitter.c b/contrib/libucl/ucl_emitter.c
new file mode 100644
index 000000000..9ddf3584a
--- /dev/null
+++ b/contrib/libucl/ucl_emitter.c
@@ -0,0 +1,511 @@
+/* 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+#ifdef HAVE_FLOAT_H
+#include <float.h>
+#endif
+#ifdef HAVE_MATH_H
+#include <math.h>
+#endif
+
+/**
+ * @file ucl_emitter.c
+ * Serialise UCL object to various of output formats
+ */
+
+static void ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool first, bool print_key, bool compact);
+
+#define UCL_EMIT_TYPE_OPS(type) \
+ static void ucl_emit_ ## type ## _elt (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool first, bool print_key); \
+ static void ucl_emit_ ## type ## _start_obj (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool print_key); \
+ static void ucl_emit_ ## type## _start_array (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool print_key); \
+ static void ucl_emit_ ##type## _end_object (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj); \
+ static void ucl_emit_ ##type## _end_array (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj)
+
+/*
+ * JSON format operations
+ */
+UCL_EMIT_TYPE_OPS(json);
+UCL_EMIT_TYPE_OPS(json_compact);
+UCL_EMIT_TYPE_OPS(config);
+UCL_EMIT_TYPE_OPS(yaml);
+
+#define UCL_EMIT_TYPE_CONTENT(type) { \
+ .ucl_emitter_write_elt = ucl_emit_ ## type ## _elt, \
+ .ucl_emitter_start_object = ucl_emit_ ## type ##_start_obj, \
+ .ucl_emitter_start_array = ucl_emit_ ## type ##_start_array, \
+ .ucl_emitter_end_object = ucl_emit_ ## type ##_end_object, \
+ .ucl_emitter_end_array = ucl_emit_ ## type ##_end_array \
+}
+
+
+const struct ucl_emitter_operations ucl_standartd_emitter_ops[] = {
+ [UCL_EMIT_JSON] = UCL_EMIT_TYPE_CONTENT(json),
+ [UCL_EMIT_JSON_COMPACT] = UCL_EMIT_TYPE_CONTENT(json_compact),
+ [UCL_EMIT_CONFIG] = UCL_EMIT_TYPE_CONTENT(config),
+ [UCL_EMIT_YAML] = UCL_EMIT_TYPE_CONTENT(yaml)
+};
+
+/*
+ * Utility to check whether we need a top object
+ */
+#define UCL_EMIT_IDENT_TOP_OBJ(ctx, obj) ((ctx)->top != (obj) || \
+ ((ctx)->id == UCL_EMIT_JSON_COMPACT || (ctx)->id == UCL_EMIT_JSON))
+
+
+/**
+ * Add tabulation to the output buffer
+ * @param buf target buffer
+ * @param tabs number of tabs to add
+ */
+static inline void
+ucl_add_tabs (const struct ucl_emitter_functions *func, unsigned int tabs,
+ bool compact)
+{
+ if (!compact && tabs > 0) {
+ func->ucl_emitter_append_character (' ', tabs * 4, func->ud);
+ }
+}
+
+/**
+ * Print key for the element
+ * @param ctx
+ * @param obj
+ */
+static void
+ucl_emitter_print_key (bool print_key, struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool compact)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ if (!print_key) {
+ return;
+ }
+
+ if (ctx->id == UCL_EMIT_CONFIG) {
+ if (obj->flags & UCL_OBJECT_NEED_KEY_ESCAPE) {
+ ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
+ }
+ else {
+ func->ucl_emitter_append_len (obj->key, obj->keylen, func->ud);
+ }
+
+ if (obj->type != UCL_OBJECT && obj->type != UCL_ARRAY) {
+ func->ucl_emitter_append_len (" = ", 3, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_character (' ', 1, func->ud);
+ }
+ }
+ else if (ctx->id == UCL_EMIT_YAML) {
+ if (obj->keylen > 0 && (obj->flags & UCL_OBJECT_NEED_KEY_ESCAPE)) {
+ ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
+ }
+ else if (obj->keylen > 0) {
+ func->ucl_emitter_append_len (obj->key, obj->keylen, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ }
+
+ func->ucl_emitter_append_len (": ", 2, func->ud);
+ }
+ else {
+ if (obj->keylen > 0) {
+ ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
+ }
+ else {
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ }
+
+ if (compact) {
+ func->ucl_emitter_append_character (':', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len (": ", 2, func->ud);
+ }
+ }
+}
+
+static void
+ucl_emitter_finish_object (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool compact, bool is_array)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ if (ctx->id == UCL_EMIT_CONFIG && obj != ctx->top) {
+ if (obj->type != UCL_OBJECT && obj->type != UCL_ARRAY) {
+ if (!is_array) {
+ /* Objects are split by ';' */
+ func->ucl_emitter_append_len (";\n", 2, func->ud);
+ }
+ else {
+ /* Use commas for arrays */
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ }
+ else {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ }
+}
+
+/**
+ * End standard ucl object
+ * @param ctx emitter context
+ * @param compact compact flag
+ */
+static void
+ucl_emitter_common_end_object (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool compact)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ if (UCL_EMIT_IDENT_TOP_OBJ(ctx, obj)) {
+ ctx->indent --;
+ if (compact) {
+ func->ucl_emitter_append_character ('}', 1, func->ud);
+ }
+ else {
+ if (ctx->id != UCL_EMIT_CONFIG) {
+ /* newline is already added for this format */
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ ucl_add_tabs (func, ctx->indent, compact);
+ func->ucl_emitter_append_character ('}', 1, func->ud);
+ }
+ }
+
+ ucl_emitter_finish_object (ctx, obj, compact, false);
+}
+
+/**
+ * End standard ucl array
+ * @param ctx emitter context
+ * @param compact compact flag
+ */
+static void
+ucl_emitter_common_end_array (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool compact)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ ctx->indent --;
+ if (compact) {
+ func->ucl_emitter_append_character (']', 1, func->ud);
+ }
+ else {
+ if (ctx->id != UCL_EMIT_CONFIG) {
+ /* newline is already added for this format */
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ ucl_add_tabs (func, ctx->indent, compact);
+ func->ucl_emitter_append_character (']', 1, func->ud);
+ }
+
+ ucl_emitter_finish_object (ctx, obj, compact, true);
+}
+
+/**
+ * Start emit standard UCL array
+ * @param ctx emitter context
+ * @param obj object to write
+ * @param compact compact flag
+ */
+static void
+ucl_emitter_common_start_array (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool print_key, bool compact)
+{
+ const ucl_object_t *cur;
+ ucl_object_iter_t iter = NULL;
+ const struct ucl_emitter_functions *func = ctx->func;
+ bool first = true;
+
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+
+ if (compact) {
+ func->ucl_emitter_append_character ('[', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("[\n", 2, func->ud);
+ }
+
+ ctx->indent ++;
+
+ if (obj->type == UCL_ARRAY) {
+ /* explicit array */
+ while ((cur = ucl_iterate_object (obj, &iter, true)) != NULL) {
+ ucl_emitter_common_elt (ctx, cur, first, false, compact);
+ first = false;
+ }
+ }
+ else {
+ /* implicit array */
+ cur = obj;
+ while (cur) {
+ ucl_emitter_common_elt (ctx, cur, first, false, compact);
+ first = false;
+ cur = cur->next;
+ }
+ }
+
+
+}
+
+/**
+ * Start emit standard UCL object
+ * @param ctx emitter context
+ * @param obj object to write
+ * @param compact compact flag
+ */
+static void
+ucl_emitter_common_start_object (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool print_key, bool compact)
+{
+ ucl_hash_iter_t it = NULL;
+ const ucl_object_t *cur, *elt;
+ const struct ucl_emitter_functions *func = ctx->func;
+ bool first = true;
+
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ /*
+ * Print <ident_level>{
+ * <ident_level + 1><object content>
+ */
+ if (UCL_EMIT_IDENT_TOP_OBJ(ctx, obj)) {
+ if (compact) {
+ func->ucl_emitter_append_character ('{', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("{\n", 2, func->ud);
+ }
+ ctx->indent ++;
+ }
+
+ while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
+
+ if (ctx->id == UCL_EMIT_CONFIG) {
+ LL_FOREACH (cur, elt) {
+ ucl_emitter_common_elt (ctx, elt, first, true, compact);
+ }
+ }
+ else {
+ /* Expand implicit arrays */
+ if (cur->next != NULL) {
+ if (!first) {
+ if (compact) {
+ func->ucl_emitter_append_character (',', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ }
+ ucl_add_tabs (func, ctx->indent, compact);
+ ucl_emitter_common_start_array (ctx, cur, true, compact);
+ ucl_emitter_common_end_array (ctx, cur, compact);
+ }
+ else {
+ ucl_emitter_common_elt (ctx, cur, first, true, compact);
+ }
+ }
+
+ first = false;
+ }
+}
+
+/**
+ * Common choice of object emitting
+ * @param ctx emitter context
+ * @param obj object to print
+ * @param first flag to mark the first element
+ * @param print_key print key of an object
+ * @param compact compact output
+ */
+static void
+ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj, bool first, bool print_key, bool compact)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+ bool flag;
+ struct ucl_object_userdata *ud;
+ const char *ud_out = "";
+
+ if (ctx->id != UCL_EMIT_CONFIG && !first) {
+ if (compact) {
+ func->ucl_emitter_append_character (',', 1, func->ud);
+ }
+ else {
+ if (ctx->id == UCL_EMIT_YAML && ctx->indent == 0) {
+ func->ucl_emitter_append_len ("\n", 1, func->ud);
+ } else {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ }
+ }
+
+ ucl_add_tabs (func, ctx->indent, compact);
+
+ switch (obj->type) {
+ case UCL_INT:
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ func->ucl_emitter_append_int (ucl_object_toint (obj), func->ud);
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ func->ucl_emitter_append_double (ucl_object_todouble (obj), func->ud);
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ case UCL_BOOLEAN:
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ flag = ucl_object_toboolean (obj);
+ if (flag) {
+ func->ucl_emitter_append_len ("true", 4, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("false", 5, func->ud);
+ }
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ case UCL_STRING:
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ if (ctx->id == UCL_EMIT_CONFIG && ucl_maybe_long_string (obj)) {
+ ucl_elt_string_write_multiline (obj->value.sv, obj->len, ctx);
+ }
+ else {
+ ucl_elt_string_write_json (obj->value.sv, obj->len, ctx);
+ }
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ case UCL_NULL:
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ case UCL_OBJECT:
+ ucl_emitter_common_start_object (ctx, obj, print_key, compact);
+ ucl_emitter_common_end_object (ctx, obj, compact);
+ break;
+ case UCL_ARRAY:
+ ucl_emitter_common_start_array (ctx, obj, print_key, compact);
+ ucl_emitter_common_end_array (ctx, obj, compact);
+ break;
+ case UCL_USERDATA:
+ ud = (struct ucl_object_userdata *)obj;
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ if (ud->emitter) {
+ ud_out = ud->emitter (obj->value.ud);
+ if (ud_out == NULL) {
+ ud_out = "null";
+ }
+ }
+ ucl_elt_string_write_json (ud_out, strlen (ud_out), ctx);
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
+ break;
+ }
+}
+
+/*
+ * Specific standard implementations of the emitter functions
+ */
+#define UCL_EMIT_TYPE_IMPL(type, compact) \
+ static void ucl_emit_ ## type ## _elt (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool first, bool print_key) { \
+ ucl_emitter_common_elt (ctx, obj, first, print_key, (compact)); \
+ } \
+ static void ucl_emit_ ## type ## _start_obj (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool print_key) { \
+ ucl_emitter_common_start_object (ctx, obj, print_key, (compact)); \
+ } \
+ static void ucl_emit_ ## type## _start_array (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj, bool print_key) { \
+ ucl_emitter_common_start_array (ctx, obj, print_key, (compact)); \
+ } \
+ static void ucl_emit_ ##type## _end_object (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj) { \
+ ucl_emitter_common_end_object (ctx, obj, (compact)); \
+ } \
+ static void ucl_emit_ ##type## _end_array (struct ucl_emitter_context *ctx, \
+ const ucl_object_t *obj) { \
+ ucl_emitter_common_end_array (ctx, obj, (compact)); \
+ }
+
+UCL_EMIT_TYPE_IMPL(json, false)
+UCL_EMIT_TYPE_IMPL(json_compact, true)
+UCL_EMIT_TYPE_IMPL(config, false)
+UCL_EMIT_TYPE_IMPL(yaml, false)
+
+unsigned char *
+ucl_object_emit (const ucl_object_t *obj, enum ucl_emitter emit_type)
+{
+ unsigned char *res = NULL;
+ struct ucl_emitter_functions *func;
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ func = ucl_object_emit_memory_funcs ((void **)&res);
+
+ if (func != NULL) {
+ ucl_object_emit_full (obj, emit_type, func);
+ ucl_object_emit_funcs_free (func);
+ }
+
+ return res;
+}
+
+bool
+ucl_object_emit_full (const ucl_object_t *obj, enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter)
+{
+ const struct ucl_emitter_context *ctx;
+ struct ucl_emitter_context my_ctx;
+ bool res = false;
+
+ ctx = ucl_emit_get_standard_context (emit_type);
+ if (ctx != NULL) {
+ memcpy (&my_ctx, ctx, sizeof (my_ctx));
+ my_ctx.func = emitter;
+ my_ctx.indent = 0;
+ my_ctx.top = obj;
+
+ my_ctx.ops->ucl_emitter_write_elt (&my_ctx, obj, true, false);
+ res = true;
+ }
+
+ return res;
+}
diff --git a/contrib/libucl/ucl_emitter_streamline.c b/contrib/libucl/ucl_emitter_streamline.c
new file mode 100644
index 000000000..ff27c8824
--- /dev/null
+++ b/contrib/libucl/ucl_emitter_streamline.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+
+struct ucl_emitter_streamline_stack {
+ bool is_array;
+ bool empty;
+ const ucl_object_t *obj;
+ struct ucl_emitter_streamline_stack *next;
+};
+
+struct ucl_emitter_context_streamline {
+ /* Inherited from the main context */
+ const char *name;
+ int id;
+ const struct ucl_emitter_functions *func;
+ const struct ucl_emitter_operations *ops;
+ unsigned int ident;
+ const ucl_object_t *top;
+
+ /* Streamline specific fields */
+ struct ucl_emitter_streamline_stack *containers;
+};
+
+#define TO_STREAMLINE(ctx) (struct ucl_emitter_context_streamline *)(ctx)
+
+struct ucl_emitter_context*
+ucl_object_emit_streamline_new (const ucl_object_t *obj,
+ enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter)
+{
+ const struct ucl_emitter_context *ctx;
+ struct ucl_emitter_context_streamline *sctx;
+
+ ctx = ucl_emit_get_standard_context (emit_type);
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ sctx = calloc (1, sizeof (*sctx));
+ if (sctx == NULL) {
+ return NULL;
+ }
+
+ memcpy (sctx, ctx, sizeof (*ctx));
+ sctx->func = emitter;
+ sctx->top = obj;
+
+ ucl_object_emit_streamline_start_container ((struct ucl_emitter_context *)sctx,
+ obj);
+
+ return (struct ucl_emitter_context *)sctx;
+}
+
+void
+ucl_object_emit_streamline_start_container (struct ucl_emitter_context *ctx,
+ const ucl_object_t *obj)
+{
+ struct ucl_emitter_context_streamline *sctx = TO_STREAMLINE(ctx);
+ struct ucl_emitter_streamline_stack *st, *top;
+ bool print_key = false;
+
+ /* Check top object presence */
+ if (sctx->top == NULL) {
+ sctx->top = obj;
+ }
+
+ top = sctx->containers;
+ st = malloc (sizeof (*st));
+ if (st != NULL) {
+ if (top != NULL && !top->is_array) {
+ print_key = true;
+ }
+ st->empty = true;
+ st->obj = obj;
+ if (obj != NULL && obj->type == UCL_ARRAY) {
+ st->is_array = true;
+ sctx->ops->ucl_emitter_start_array (ctx, obj, print_key);
+ }
+ else {
+ st->is_array = false;
+ sctx->ops->ucl_emitter_start_object (ctx, obj, print_key);
+ }
+ LL_PREPEND (sctx->containers, st);
+ }
+}
+
+void
+ucl_object_emit_streamline_add_object (
+ struct ucl_emitter_context *ctx, const ucl_object_t *obj)
+{
+ struct ucl_emitter_context_streamline *sctx = TO_STREAMLINE(ctx);
+ bool is_array = false, is_first = false;
+
+ if (sctx->containers != NULL) {
+ if (sctx->containers->is_array) {
+ is_array = true;
+ }
+ if (sctx->containers->empty) {
+ is_first = true;
+ sctx->containers->empty = false;
+ }
+ }
+
+ sctx->ops->ucl_emitter_write_elt (ctx, obj, is_first, !is_array);
+}
+
+void
+ucl_object_emit_streamline_end_container (struct ucl_emitter_context *ctx)
+{
+ struct ucl_emitter_context_streamline *sctx = TO_STREAMLINE(ctx);
+ struct ucl_emitter_streamline_stack *st;
+
+ if (sctx->containers != NULL) {
+ st = sctx->containers;
+
+ if (st->is_array) {
+ sctx->ops->ucl_emitter_end_array (ctx, st->obj);
+ }
+ else {
+ sctx->ops->ucl_emitter_end_object (ctx, st->obj);
+ }
+ sctx->containers = st->next;
+ free (st);
+ }
+}
+
+void
+ucl_object_emit_streamline_finish (struct ucl_emitter_context *ctx)
+{
+ struct ucl_emitter_context_streamline *sctx = TO_STREAMLINE(ctx);
+
+ while (sctx->containers != NULL) {
+ ucl_object_emit_streamline_end_container (ctx);
+ }
+
+ free (sctx);
+}
diff --git a/contrib/libucl/ucl_emitter_utils.c b/contrib/libucl/ucl_emitter_utils.c
new file mode 100644
index 000000000..91cad78bc
--- /dev/null
+++ b/contrib/libucl/ucl_emitter_utils.c
@@ -0,0 +1,487 @@
+/* Copyright (c) 2014, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+
+#ifdef HAVE_FLOAT_H
+#include <float.h>
+#endif
+#ifdef HAVE_MATH_H
+#include <math.h>
+#endif
+
+extern const struct ucl_emitter_operations ucl_standartd_emitter_ops[];
+
+static const struct ucl_emitter_context ucl_standard_emitters[] = {
+ [UCL_EMIT_JSON] = {
+ .name = "json",
+ .id = UCL_EMIT_JSON,
+ .func = NULL,
+ .ops = &ucl_standartd_emitter_ops[UCL_EMIT_JSON]
+ },
+ [UCL_EMIT_JSON_COMPACT] = {
+ .name = "json_compact",
+ .id = UCL_EMIT_JSON_COMPACT,
+ .func = NULL,
+ .ops = &ucl_standartd_emitter_ops[UCL_EMIT_JSON_COMPACT]
+ },
+ [UCL_EMIT_CONFIG] = {
+ .name = "config",
+ .id = UCL_EMIT_CONFIG,
+ .func = NULL,
+ .ops = &ucl_standartd_emitter_ops[UCL_EMIT_CONFIG]
+ },
+ [UCL_EMIT_YAML] = {
+ .name = "yaml",
+ .id = UCL_EMIT_YAML,
+ .func = NULL,
+ .ops = &ucl_standartd_emitter_ops[UCL_EMIT_YAML]
+ }
+};
+
+/**
+ * Get standard emitter context for a specified emit_type
+ * @param emit_type type of emitter
+ * @return context or NULL if input is invalid
+ */
+const struct ucl_emitter_context *
+ucl_emit_get_standard_context (enum ucl_emitter emit_type)
+{
+ if (emit_type >= UCL_EMIT_JSON && emit_type <= UCL_EMIT_YAML) {
+ return &ucl_standard_emitters[emit_type];
+ }
+
+ return NULL;
+}
+
+/**
+ * Serialise string
+ * @param str string to emit
+ * @param buf target buffer
+ */
+void
+ucl_elt_string_write_json (const char *str, size_t size,
+ struct ucl_emitter_context *ctx)
+{
+ const char *p = str, *c = str;
+ size_t len = 0;
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ func->ucl_emitter_append_character ('"', 1, func->ud);
+
+ while (size) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ if (len > 0) {
+ func->ucl_emitter_append_len (c, len, func->ud);
+ }
+ switch (*p) {
+ case '\n':
+ func->ucl_emitter_append_len ("\\n", 2, func->ud);
+ break;
+ case '\r':
+ func->ucl_emitter_append_len ("\\r", 2, func->ud);
+ break;
+ case '\b':
+ func->ucl_emitter_append_len ("\\b", 2, func->ud);
+ break;
+ case '\t':
+ func->ucl_emitter_append_len ("\\t", 2, func->ud);
+ break;
+ case '\f':
+ func->ucl_emitter_append_len ("\\f", 2, func->ud);
+ break;
+ case '\\':
+ func->ucl_emitter_append_len ("\\\\", 2, func->ud);
+ break;
+ case '"':
+ func->ucl_emitter_append_len ("\\\"", 2, func->ud);
+ break;
+ }
+ len = 0;
+ c = ++p;
+ }
+ else {
+ p ++;
+ len ++;
+ }
+ size --;
+ }
+ if (len > 0) {
+ func->ucl_emitter_append_len (c, len, func->ud);
+ }
+ func->ucl_emitter_append_character ('"', 1, func->ud);
+}
+
+void
+ucl_elt_string_write_multiline (const char *str, size_t size,
+ struct ucl_emitter_context *ctx)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ func->ucl_emitter_append_len ("<<EOD\n", sizeof ("<<EOD\n") - 1, func->ud);
+ func->ucl_emitter_append_len (str, size, func->ud);
+ func->ucl_emitter_append_len ("\nEOD", sizeof ("\nEOD") - 1, func->ud);
+}
+
+/*
+ * Generic utstring output
+ */
+static int
+ucl_utstring_append_character (unsigned char c, size_t len, void *ud)
+{
+ UT_string *buf = ud;
+
+ if (len == 1) {
+ utstring_append_c (buf, c);
+ }
+ else {
+ utstring_reserve (buf, len + 1);
+ memset (&buf->d[buf->i], c, len);
+ buf->i += len;
+ buf->d[buf->i] = '\0';
+ }
+
+ return 0;
+}
+
+static int
+ucl_utstring_append_len (const unsigned char *str, size_t len, void *ud)
+{
+ UT_string *buf = ud;
+
+ utstring_append_len (buf, str, len);
+
+ return 0;
+}
+
+static int
+ucl_utstring_append_int (int64_t val, void *ud)
+{
+ UT_string *buf = ud;
+
+ utstring_printf (buf, "%jd", (intmax_t)val);
+ return 0;
+}
+
+static int
+ucl_utstring_append_double (double val, void *ud)
+{
+ UT_string *buf = ud;
+ const double delta = 0.0000001;
+
+ if (val == (double)(int)val) {
+ utstring_printf (buf, "%.1lf", val);
+ }
+ else if (fabs (val - (double)(int)val) < delta) {
+ /* Write at maximum precision */
+ utstring_printf (buf, "%.*lg", DBL_DIG, val);
+ }
+ else {
+ utstring_printf (buf, "%lf", val);
+ }
+
+ return 0;
+}
+
+/*
+ * Generic file output
+ */
+static int
+ucl_file_append_character (unsigned char c, size_t len, void *ud)
+{
+ FILE *fp = ud;
+
+ while (len --) {
+ fputc (c, fp);
+ }
+
+ return 0;
+}
+
+static int
+ucl_file_append_len (const unsigned char *str, size_t len, void *ud)
+{
+ FILE *fp = ud;
+
+ fwrite (str, len, 1, fp);
+
+ return 0;
+}
+
+static int
+ucl_file_append_int (int64_t val, void *ud)
+{
+ FILE *fp = ud;
+
+ fprintf (fp, "%jd", (intmax_t)val);
+
+ return 0;
+}
+
+static int
+ucl_file_append_double (double val, void *ud)
+{
+ FILE *fp = ud;
+ const double delta = 0.0000001;
+
+ if (val == (double)(int)val) {
+ fprintf (fp, "%.1lf", val);
+ }
+ else if (fabs (val - (double)(int)val) < delta) {
+ /* Write at maximum precision */
+ fprintf (fp, "%.*lg", DBL_DIG, val);
+ }
+ else {
+ fprintf (fp, "%lf", val);
+ }
+
+ return 0;
+}
+
+/*
+ * Generic file descriptor writing functions
+ */
+static int
+ucl_fd_append_character (unsigned char c, size_t len, void *ud)
+{
+ int fd = *(int *)ud;
+ unsigned char *buf;
+
+ if (len == 1) {
+ return write (fd, &c, 1);
+ }
+ else {
+ buf = malloc (len);
+ if (buf == NULL) {
+ /* Fallback */
+ while (len --) {
+ if (write (fd, &c, 1) == -1) {
+ return -1;
+ }
+ }
+ }
+ else {
+ memset (buf, c, len);
+ if (write (fd, buf, len) == -1) {
+ free(buf);
+ return -1;
+ }
+ free (buf);
+ }
+ }
+
+ return 0;
+}
+
+static int
+ucl_fd_append_len (const unsigned char *str, size_t len, void *ud)
+{
+ int fd = *(int *)ud;
+
+ return write (fd, str, len);
+}
+
+static int
+ucl_fd_append_int (int64_t val, void *ud)
+{
+ int fd = *(int *)ud;
+ char intbuf[64];
+
+ snprintf (intbuf, sizeof (intbuf), "%jd", (intmax_t)val);
+ return write (fd, intbuf, strlen (intbuf));
+}
+
+static int
+ucl_fd_append_double (double val, void *ud)
+{
+ int fd = *(int *)ud;
+ const double delta = 0.0000001;
+ char nbuf[64];
+
+ if (val == (double)(int)val) {
+ snprintf (nbuf, sizeof (nbuf), "%.1lf", val);
+ }
+ else if (fabs (val - (double)(int)val) < delta) {
+ /* Write at maximum precision */
+ snprintf (nbuf, sizeof (nbuf), "%.*lg", DBL_DIG, val);
+ }
+ else {
+ snprintf (nbuf, sizeof (nbuf), "%lf", val);
+ }
+
+ return write (fd, nbuf, strlen (nbuf));
+}
+
+struct ucl_emitter_functions*
+ucl_object_emit_memory_funcs (void **pmem)
+{
+ struct ucl_emitter_functions *f;
+ UT_string *s;
+
+ f = calloc (1, sizeof (*f));
+
+ if (f != NULL) {
+ f->ucl_emitter_append_character = ucl_utstring_append_character;
+ f->ucl_emitter_append_double = ucl_utstring_append_double;
+ f->ucl_emitter_append_int = ucl_utstring_append_int;
+ f->ucl_emitter_append_len = ucl_utstring_append_len;
+ f->ucl_emitter_free_func = free;
+ utstring_new (s);
+ f->ud = s;
+ *pmem = s->d;
+ s->pd = pmem;
+ }
+
+ return f;
+}
+
+struct ucl_emitter_functions*
+ucl_object_emit_file_funcs (FILE *fp)
+{
+ struct ucl_emitter_functions *f;
+
+ f = calloc (1, sizeof (*f));
+
+ if (f != NULL) {
+ f->ucl_emitter_append_character = ucl_file_append_character;
+ f->ucl_emitter_append_double = ucl_file_append_double;
+ f->ucl_emitter_append_int = ucl_file_append_int;
+ f->ucl_emitter_append_len = ucl_file_append_len;
+ f->ucl_emitter_free_func = NULL;
+ f->ud = fp;
+ }
+
+ return f;
+}
+
+struct ucl_emitter_functions*
+ucl_object_emit_fd_funcs (int fd)
+{
+ struct ucl_emitter_functions *f;
+ int *ip;
+
+ f = calloc (1, sizeof (*f));
+
+ if (f != NULL) {
+ ip = malloc (sizeof (fd));
+ if (ip == NULL) {
+ free (f);
+ return NULL;
+ }
+
+ memcpy (ip, &fd, sizeof (fd));
+ f->ucl_emitter_append_character = ucl_fd_append_character;
+ f->ucl_emitter_append_double = ucl_fd_append_double;
+ f->ucl_emitter_append_int = ucl_fd_append_int;
+ f->ucl_emitter_append_len = ucl_fd_append_len;
+ f->ucl_emitter_free_func = free;
+ f->ud = ip;
+ }
+
+ return f;
+}
+
+void
+ucl_object_emit_funcs_free (struct ucl_emitter_functions *f)
+{
+ if (f != NULL) {
+ if (f->ucl_emitter_free_func != NULL) {
+ f->ucl_emitter_free_func (f->ud);
+ }
+ free (f);
+ }
+}
+
+
+unsigned char *
+ucl_object_emit_single_json (const ucl_object_t *obj)
+{
+ UT_string *buf = NULL;
+ unsigned char *res = NULL;
+
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ utstring_new (buf);
+
+ if (buf != NULL) {
+ switch (obj->type) {
+ case UCL_OBJECT:
+ ucl_utstring_append_len ("object", 6, buf);
+ break;
+ case UCL_ARRAY:
+ ucl_utstring_append_len ("array", 5, buf);
+ break;
+ case UCL_INT:
+ ucl_utstring_append_int (obj->value.iv, buf);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ ucl_utstring_append_double (obj->value.dv, buf);
+ break;
+ case UCL_NULL:
+ ucl_utstring_append_len ("null", 4, buf);
+ break;
+ case UCL_BOOLEAN:
+ if (obj->value.iv) {
+ ucl_utstring_append_len ("true", 4, buf);
+ }
+ else {
+ ucl_utstring_append_len ("false", 5, buf);
+ }
+ break;
+ case UCL_STRING:
+ ucl_utstring_append_len (obj->value.sv, obj->len, buf);
+ break;
+ case UCL_USERDATA:
+ ucl_utstring_append_len ("userdata", 8, buf);
+ break;
+ }
+ res = utstring_body (buf);
+ free (buf);
+ }
+
+ return res;
+}
+
+#define LONG_STRING_LIMIT 80
+
+bool
+ucl_maybe_long_string (const ucl_object_t *obj)
+{
+ if (obj->len > LONG_STRING_LIMIT || (obj->flags & UCL_OBJECT_MULTILINE)) {
+ /* String is long enough, so search for newline characters in it */
+ if (memchr (obj->value.sv, '\n', obj->len) != NULL) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/contrib/libucl/ucl_hash.c b/contrib/libucl/ucl_hash.c
new file mode 100644
index 000000000..275e84d47
--- /dev/null
+++ b/contrib/libucl/ucl_hash.c
@@ -0,0 +1,353 @@
+/* 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_internal.h"
+#include "ucl_hash.h"
+#include "khash.h"
+#include "kvec.h"
+
+struct ucl_hash_elt {
+ const ucl_object_t *obj;
+ size_t ar_idx;
+};
+
+struct ucl_hash_struct {
+ void *hash;
+ kvec_t(const ucl_object_t *) ar;
+ bool caseless;
+};
+
+static inline uint32_t
+ucl_hash_func (const ucl_object_t *o)
+{
+ return XXH32 (o->key, o->keylen, 0xdeadbeef);
+}
+
+static inline int
+ucl_hash_equal (const ucl_object_t *k1, const ucl_object_t *k2)
+{
+ if (k1->keylen == k2->keylen) {
+ return strncmp (k1->key, k2->key, k1->keylen) == 0;
+ }
+
+ return 0;
+}
+
+KHASH_INIT (ucl_hash_node, const ucl_object_t *, struct ucl_hash_elt, 1,
+ ucl_hash_func, ucl_hash_equal)
+
+static inline uint32_t
+ucl_hash_caseless_func (const ucl_object_t *o)
+{
+ void *xxh = XXH32_init (0xdeadbeef);
+ char hash_buf[64], *c;
+ const char *p;
+ ssize_t remain = o->keylen;
+
+ p = o->key;
+ c = &hash_buf[0];
+
+ while (remain > 0) {
+ *c++ = tolower (*p++);
+
+ if (c - &hash_buf[0] == sizeof (hash_buf)) {
+ XXH32_update (xxh, hash_buf, sizeof (hash_buf));
+ c = &hash_buf[0];
+ }
+ remain --;
+ }
+
+ if (c - &hash_buf[0] != 0) {
+ XXH32_update (xxh, hash_buf, c - &hash_buf[0]);
+ }
+
+ return XXH32_digest (xxh);
+}
+
+static inline int
+ucl_hash_caseless_equal (const ucl_object_t *k1, const ucl_object_t *k2)
+{
+ if (k1->keylen == k2->keylen) {
+ return strncasecmp (k1->key, k2->key, k1->keylen) == 0;
+ }
+
+ return 0;
+}
+
+KHASH_INIT (ucl_hash_caseless_node, const ucl_object_t *, struct ucl_hash_elt, 1,
+ ucl_hash_caseless_func, ucl_hash_caseless_equal)
+
+ucl_hash_t*
+ucl_hash_create (bool ignore_case)
+{
+ ucl_hash_t *new;
+
+ new = UCL_ALLOC (sizeof (ucl_hash_t));
+ if (new != NULL) {
+ kv_init (new->ar);
+
+ new->caseless = ignore_case;
+ if (ignore_case) {
+ khash_t(ucl_hash_caseless_node) *h = kh_init (ucl_hash_caseless_node);
+ new->hash = (void *)h;
+ }
+ else {
+ khash_t(ucl_hash_node) *h = kh_init (ucl_hash_node);
+ new->hash = (void *)h;
+ }
+ }
+ return new;
+}
+
+void ucl_hash_destroy (ucl_hash_t* hashlin, ucl_hash_free_func *func)
+{
+ const ucl_object_t *cur, *tmp;
+
+ if (hashlin == NULL) {
+ return;
+ }
+
+ if (func != NULL) {
+ /* Iterate over the hash first */
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ khiter_t k;
+
+ for (k = kh_begin (h); k != kh_end (h); ++k) {
+ if (kh_exist (h, k)) {
+ cur = (kh_value (h, k)).obj;
+ while (cur != NULL) {
+ tmp = cur->next;
+ func (__DECONST (ucl_object_t *, cur));
+ cur = tmp;
+ }
+ }
+ }
+ }
+
+ if (hashlin->caseless) {
+ khash_t(ucl_hash_caseless_node) *h = (khash_t(ucl_hash_caseless_node) *)
+ hashlin->hash;
+ kh_destroy (ucl_hash_caseless_node, h);
+ }
+ else {
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ kh_destroy (ucl_hash_node, h);
+ }
+
+ kv_destroy (hashlin->ar);
+ UCL_FREE (sizeof (*hashlin), hashlin);
+}
+
+void
+ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj,
+ const char *key, unsigned keylen)
+{
+ khiter_t k;
+ int ret;
+ struct ucl_hash_elt *elt;
+
+ if (hashlin == NULL) {
+ return;
+ }
+
+ if (hashlin->caseless) {
+ khash_t(ucl_hash_caseless_node) *h = (khash_t(ucl_hash_caseless_node) *)
+ hashlin->hash;
+ k = kh_put (ucl_hash_caseless_node, h, obj, &ret);
+ if (ret > 0) {
+ elt = &kh_value (h, k);
+ kv_push (const ucl_object_t *, hashlin->ar, obj);
+ elt->obj = obj;
+ elt->ar_idx = kv_size (hashlin->ar) - 1;
+ }
+ }
+ else {
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ k = kh_put (ucl_hash_node, h, obj, &ret);
+ if (ret > 0) {
+ elt = &kh_value (h, k);
+ kv_push (const ucl_object_t *, hashlin->ar, obj);
+ elt->obj = obj;
+ elt->ar_idx = kv_size (hashlin->ar) - 1;
+ }
+ }
+}
+
+void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
+ const ucl_object_t *new)
+{
+ khiter_t k;
+ int ret;
+ struct ucl_hash_elt elt, *pelt;
+
+ if (hashlin == NULL) {
+ return;
+ }
+
+ if (hashlin->caseless) {
+ khash_t(ucl_hash_caseless_node) *h = (khash_t(ucl_hash_caseless_node) *)
+ hashlin->hash;
+ k = kh_put (ucl_hash_caseless_node, h, old, &ret);
+ if (ret == 0) {
+ elt = kh_value (h, k);
+ kh_del (ucl_hash_caseless_node, h, k);
+ k = kh_put (ucl_hash_caseless_node, h, new, &ret);
+ pelt = &kh_value (h, k);
+ pelt->obj = new;
+ pelt->ar_idx = elt.ar_idx;
+ kv_A (hashlin->ar, elt.ar_idx) = new;
+ }
+ }
+ else {
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ k = kh_put (ucl_hash_node, h, old, &ret);
+ if (ret == 0) {
+ elt = kh_value (h, k);
+ kh_del (ucl_hash_node, h, k);
+ k = kh_put (ucl_hash_node, h, new, &ret);
+ pelt = &kh_value (h, k);
+ pelt->obj = new;
+ pelt->ar_idx = elt.ar_idx;
+ kv_A (hashlin->ar, elt.ar_idx) = new;
+ }
+ }
+}
+
+struct ucl_hash_real_iter {
+ const ucl_object_t **cur;
+ const ucl_object_t **end;
+};
+
+const void*
+ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter)
+{
+ struct ucl_hash_real_iter *it = (struct ucl_hash_real_iter *)(*iter);
+ const ucl_object_t *ret = NULL;
+
+ if (hashlin == NULL) {
+ return NULL;
+ }
+
+ if (it == NULL) {
+ it = UCL_ALLOC (sizeof (*it));
+ it->cur = &hashlin->ar.a[0];
+ it->end = it->cur + hashlin->ar.n;
+ }
+
+ if (it->cur < it->end) {
+ ret = *it->cur++;
+ }
+ else {
+ UCL_FREE (sizeof (*it), it);
+ *iter = NULL;
+ return NULL;
+ }
+
+ *iter = it;
+
+ return ret;
+}
+
+bool
+ucl_hash_iter_has_next (ucl_hash_t *hashlin, ucl_hash_iter_t iter)
+{
+ struct ucl_hash_real_iter *it = (struct ucl_hash_real_iter *)(iter);
+
+ return it->cur < it->end - 1;
+}
+
+
+const ucl_object_t*
+ucl_hash_search (ucl_hash_t* hashlin, const char *key, unsigned keylen)
+{
+ khiter_t k;
+ const ucl_object_t *ret = NULL;
+ ucl_object_t search;
+ struct ucl_hash_elt *elt;
+
+ search.key = key;
+ search.keylen = keylen;
+
+ if (hashlin == NULL) {
+ return NULL;
+ }
+
+ if (hashlin->caseless) {
+ khash_t(ucl_hash_caseless_node) *h = (khash_t(ucl_hash_caseless_node) *)
+ hashlin->hash;
+
+ k = kh_get (ucl_hash_caseless_node, h, &search);
+ if (k != kh_end (h)) {
+ elt = &kh_value (h, k);
+ ret = elt->obj;
+ }
+ }
+ else {
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ k = kh_get (ucl_hash_node, h, &search);
+ if (k != kh_end (h)) {
+ elt = &kh_value (h, k);
+ ret = elt->obj;
+ }
+ }
+
+ return ret;
+}
+
+void
+ucl_hash_delete (ucl_hash_t* hashlin, const ucl_object_t *obj)
+{
+ khiter_t k;
+ struct ucl_hash_elt *elt;
+
+ if (hashlin == NULL) {
+ return;
+ }
+
+ if (hashlin->caseless) {
+ khash_t(ucl_hash_caseless_node) *h = (khash_t(ucl_hash_caseless_node) *)
+ hashlin->hash;
+
+ k = kh_get (ucl_hash_caseless_node, h, obj);
+ if (k != kh_end (h)) {
+ elt = &kh_value (h, k);
+ kv_A (hashlin->ar, elt->ar_idx) = NULL;
+ kh_del (ucl_hash_caseless_node, h, k);
+ }
+ }
+ else {
+ khash_t(ucl_hash_node) *h = (khash_t(ucl_hash_node) *)
+ hashlin->hash;
+ k = kh_get (ucl_hash_node, h, obj);
+ if (k != kh_end (h)) {
+ elt = &kh_value (h, k);
+ kv_A (hashlin->ar, elt->ar_idx) = NULL;
+ kh_del (ucl_hash_node, h, k);
+ }
+ }
+}
diff --git a/contrib/libucl/ucl_hash.h b/contrib/libucl/ucl_hash.h
new file mode 100644
index 000000000..64c83eac8
--- /dev/null
+++ b/contrib/libucl/ucl_hash.h
@@ -0,0 +1,93 @@
+/* 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_HASH_H
+#define __UCL_HASH_H
+
+#include "ucl.h"
+
+/******************************************************************************/
+
+struct ucl_hash_node_s;
+typedef struct ucl_hash_node_s ucl_hash_node_t;
+
+typedef int ucl_hash_cmp_func (const void* void_a, const void* void_b);
+typedef void ucl_hash_free_func (void *ptr);
+typedef void* ucl_hash_iter_t;
+
+
+/**
+ * Linear chained hashtable.
+ */
+struct ucl_hash_struct;
+typedef struct ucl_hash_struct ucl_hash_t;
+
+
+/**
+ * Initializes the hashtable.
+ */
+ucl_hash_t* ucl_hash_create (bool ignore_case);
+
+/**
+ * Deinitializes the hashtable.
+ */
+void ucl_hash_destroy (ucl_hash_t* hashlin, ucl_hash_free_func *func);
+
+/**
+ * Inserts an element in the the hashtable.
+ */
+void ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj, const char *key,
+ unsigned keylen);
+
+/**
+ * Replace element in the hash
+ */
+void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
+ const ucl_object_t *new);
+
+/**
+ * Delete an element from the the hashtable.
+ */
+void ucl_hash_delete (ucl_hash_t* hashlin, const ucl_object_t *obj);
+
+/**
+ * Searches an element in the hashtable.
+ */
+const ucl_object_t* ucl_hash_search (ucl_hash_t* hashlin, const char *key,
+ unsigned keylen);
+
+
+/**
+ * Iterate over hash table
+ * @param hashlin hash
+ * @param iter iterator (must be NULL on first iteration)
+ * @return the next object
+ */
+const void* ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter);
+
+/**
+ * Check whether an iterator has next element
+ */
+bool ucl_hash_iter_has_next (ucl_hash_t *hashlin, ucl_hash_iter_t iter);
+
+#endif
diff --git a/contrib/libucl/ucl_internal.h b/contrib/libucl/ucl_internal.h
new file mode 100644
index 000000000..bdbe691d0
--- /dev/null
+++ b/contrib/libucl/ucl_internal.h
@@ -0,0 +1,399 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCL_INTERNAL_H_
+#define UCL_INTERNAL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+/* Help embedded builds */
+#define HAVE_SYS_TYPES_H
+#define HAVE_SYS_MMAN_H
+#define HAVE_SYS_STAT_H
+#define HAVE_SYS_PARAM_H
+#define HAVE_LIMITS_H
+#define HAVE_FCNTL_H
+#define HAVE_ERRNO_H
+#define HAVE_UNISTD_H
+#define HAVE_CTYPE_H
+#define HAVE_STDIO_H
+#define HAVE_STRING_H
+#define HAVE_FLOAT_H
+#define HAVE_LIBGEN_H
+#define HAVE_MATH_H
+#define HAVE_STDBOOL_H
+#define HAVE_STDINT_H
+#define HAVE_STDARG_H
+#ifndef _WIN32
+# define HAVE_REGEX_H
+#endif
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_MMAN_H
+# ifndef _WIN32
+# include <sys/mman.h>
+# endif
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_CTYPE_H
+#include <ctype.h>
+#endif
+#ifdef HAVE_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "utlist.h"
+#include "utstring.h"
+#include "uthash.h"
+#include "ucl.h"
+#include "ucl_hash.h"
+#include "xxhash.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
+#ifndef __DECONST
+#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var))
+#endif
+
+/**
+ * @file rcl_internal.h
+ * Internal structures and functions of UCL library
+ */
+
+#define UCL_MAX_RECURSION 16
+#define UCL_TRASH_KEY 0
+#define UCL_TRASH_VALUE 1
+
+enum ucl_parser_state {
+ UCL_STATE_INIT = 0,
+ UCL_STATE_OBJECT,
+ UCL_STATE_ARRAY,
+ UCL_STATE_KEY,
+ UCL_STATE_VALUE,
+ UCL_STATE_AFTER_VALUE,
+ UCL_STATE_ARRAY_VALUE,
+ UCL_STATE_SCOMMENT,
+ UCL_STATE_MCOMMENT,
+ UCL_STATE_MACRO_NAME,
+ UCL_STATE_MACRO,
+ UCL_STATE_ERROR
+};
+
+enum ucl_character_type {
+ UCL_CHARACTER_DENIED = 0,
+ UCL_CHARACTER_KEY = 1,
+ UCL_CHARACTER_KEY_START = 1 << 1,
+ UCL_CHARACTER_WHITESPACE = 1 << 2,
+ UCL_CHARACTER_WHITESPACE_UNSAFE = 1 << 3,
+ UCL_CHARACTER_VALUE_END = 1 << 4,
+ UCL_CHARACTER_VALUE_STR = 1 << 5,
+ UCL_CHARACTER_VALUE_DIGIT = 1 << 6,
+ UCL_CHARACTER_VALUE_DIGIT_START = 1 << 7,
+ UCL_CHARACTER_ESCAPE = 1 << 8,
+ UCL_CHARACTER_KEY_SEP = 1 << 9,
+ UCL_CHARACTER_JSON_UNSAFE = 1 << 10,
+ UCL_CHARACTER_UCL_UNSAFE = 1 << 11
+};
+
+struct ucl_macro {
+ char *name;
+ ucl_macro_handler handler;
+ void* ud;
+ UT_hash_handle hh;
+};
+
+struct ucl_stack {
+ ucl_object_t *obj;
+ struct ucl_stack *next;
+ int level;
+};
+
+struct ucl_chunk {
+ const unsigned char *begin;
+ const unsigned char *end;
+ const unsigned char *pos;
+ size_t remain;
+ unsigned int line;
+ unsigned int column;
+ unsigned priority;
+ struct ucl_chunk *next;
+};
+
+#ifdef HAVE_OPENSSL
+struct ucl_pubkey {
+ EVP_PKEY *key;
+ struct ucl_pubkey *next;
+};
+#else
+struct ucl_pubkey {
+ struct ucl_pubkey *next;
+};
+#endif
+
+struct ucl_variable {
+ char *var;
+ char *value;
+ size_t var_len;
+ size_t value_len;
+ struct ucl_variable *prev, *next;
+};
+
+struct ucl_parser {
+ enum ucl_parser_state state;
+ enum ucl_parser_state prev_state;
+ unsigned int recursion;
+ int flags;
+ ucl_object_t *top_obj;
+ ucl_object_t *cur_obj;
+ char *cur_file;
+ struct ucl_macro *macroes;
+ struct ucl_stack *stack;
+ struct ucl_chunk *chunks;
+ struct ucl_pubkey *keys;
+ struct ucl_variable *variables;
+ ucl_variable_handler var_handler;
+ void *var_data;
+ UT_string *err;
+};
+
+struct ucl_object_userdata {
+ ucl_object_t obj;
+ ucl_userdata_dtor dtor;
+ ucl_userdata_emitter emitter;
+};
+
+/**
+ * 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,
+ const ucl_object_t *args, void* ud);
+
+bool ucl_try_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud);
+
+/**
+ * Handle includes macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+bool ucl_includes_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud);
+
+size_t ucl_strlcpy (char *dst, const char *src, size_t siz);
+size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz);
+size_t ucl_strlcpy_tolower (char *dst, const char *src, size_t siz);
+
+
+#ifdef __GNUC__
+static inline void
+ucl_create_err (UT_string **err, const char *fmt, ...)
+__attribute__ (( format( printf, 2, 3) ));
+#endif
+
+static inline void
+ucl_create_err (UT_string **err, const char *fmt, ...)
+
+{
+ if (*err == NULL) {
+ utstring_new (*err);
+ va_list ap;
+ va_start (ap, fmt);
+ utstring_printf_va (*err, fmt, ap);
+ va_end (ap);
+ }
+}
+
+/**
+ * Check whether a given string contains a boolean value
+ * @param obj object to set
+ * @param start start of a string
+ * @param len length of a string
+ * @return true if a string is a boolean value
+ */
+static inline bool
+ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t len)
+{
+ const char *p = (const char *)start;
+ bool ret = false, val = false;
+
+ if (len == 5) {
+ if ((p[0] == 'f' || p[0] == 'F') && strncasecmp (p, "false", 5) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 4) {
+ if ((p[0] == 't' || p[0] == 'T') && strncasecmp (p, "true", 4) == 0) {
+ ret = true;
+ val = true;
+ }
+ }
+ else if (len == 3) {
+ if ((p[0] == 'y' || p[0] == 'Y') && strncasecmp (p, "yes", 3) == 0) {
+ ret = true;
+ val = true;
+ }
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "off", 3) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 2) {
+ if ((p[0] == 'n' || p[0] == 'N') && strncasecmp (p, "no", 2) == 0) {
+ ret = true;
+ val = false;
+ }
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "on", 2) == 0) {
+ ret = true;
+ val = true;
+ }
+ }
+
+ if (ret) {
+ obj->type = UCL_BOOLEAN;
+ obj->value.iv = val;
+ }
+
+ return ret;
+}
+
+/**
+ * Check numeric string
+ * @param obj object to set if a string is numeric
+ * @param start start of string
+ * @param end end of string
+ * @param pos position where parsing has stopped
+ * @param allow_double allow parsing of floating point values
+ * @return 0 if string is numeric and error code (EINVAL or ERANGE) in case of conversion error
+ */
+int ucl_maybe_parse_number (ucl_object_t *obj,
+ const char *start, const char *end, const char **pos,
+ bool allow_double, bool number_bytes, bool allow_time);
+
+
+static inline const ucl_object_t *
+ucl_hash_search_obj (ucl_hash_t* hashlin, ucl_object_t *obj)
+{
+ return (const ucl_object_t *)ucl_hash_search (hashlin, obj->key, obj->keylen);
+}
+
+static inline ucl_hash_t * ucl_hash_insert_object (ucl_hash_t *hashlin,
+ const ucl_object_t *obj,
+ bool ignore_case) UCL_WARN_UNUSED_RESULT;
+
+static inline ucl_hash_t *
+ucl_hash_insert_object (ucl_hash_t *hashlin,
+ const ucl_object_t *obj,
+ bool ignore_case)
+{
+ if (hashlin == NULL) {
+ hashlin = ucl_hash_create (ignore_case);
+ }
+ ucl_hash_insert (hashlin, obj, obj->key, obj->keylen);
+
+ return hashlin;
+}
+
+/**
+ * Get standard emitter context for a specified emit_type
+ * @param emit_type type of emitter
+ * @return context or NULL if input is invalid
+ */
+const struct ucl_emitter_context *
+ucl_emit_get_standard_context (enum ucl_emitter emit_type);
+
+/**
+ * Serialize string as JSON string
+ * @param str string to emit
+ * @param buf target buffer
+ */
+void ucl_elt_string_write_json (const char *str, size_t size,
+ struct ucl_emitter_context *ctx);
+
+/**
+ * Write multiline string using `EOD` as string terminator
+ * @param str
+ * @param size
+ * @param ctx
+ */
+void ucl_elt_string_write_multiline (const char *str, size_t size,
+ struct ucl_emitter_context *ctx);
+
+/**
+ * Emit a single object to string
+ * @param obj
+ * @return
+ */
+unsigned char * ucl_object_emit_single_json (const ucl_object_t *obj);
+
+/**
+ * Check whether a specified string is long and should be likely printed in
+ * multiline mode
+ * @param obj
+ * @return
+ */
+bool ucl_maybe_long_string (const ucl_object_t *obj);
+
+#endif /* UCL_INTERNAL_H_ */
diff --git a/contrib/libucl/ucl_parser.c b/contrib/libucl/ucl_parser.c
new file mode 100644
index 000000000..704eea189
--- /dev/null
+++ b/contrib/libucl/ucl_parser.c
@@ -0,0 +1,2222 @@
+/* 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 ucl_parser.c
+ * The implementation of ucl parser
+ */
+
+struct ucl_parser_saved_state {
+ unsigned int line;
+ unsigned int column;
+ size_t remain;
+ const unsigned char *pos;
+};
+
+/**
+ * Move up to len characters
+ * @param parser
+ * @param begin
+ * @param len
+ * @return new position in chunk
+ */
+#define ucl_chunk_skipc(chunk, p) do{ \
+ if (*(p) == '\n') { \
+ (chunk)->line ++; \
+ (chunk)->column = 0; \
+ } \
+ else (chunk)->column ++; \
+ (p++); \
+ (chunk)->pos ++; \
+ (chunk)->remain --; \
+ } while (0)
+
+static inline void
+ucl_set_err (struct ucl_parser *parser, int code, const char *str, UT_string **err)
+{
+ const char *fmt_string, *filename;
+ struct ucl_chunk *chunk = parser->chunks;
+
+ if (parser->cur_file) {
+ filename = parser->cur_file;
+ }
+ else {
+ filename = "<unknown>";
+ }
+ if (chunk->pos < chunk->end) {
+ if (isgraph (*chunk->pos)) {
+ fmt_string = "error while parsing %s: "
+ "line: %d, column: %d - '%s', character: '%c'";
+ }
+ else {
+ fmt_string = "error while parsing %s: "
+ "line: %d, column: %d - '%s', character: '0x%02x'";
+ }
+ ucl_create_err (err, fmt_string,
+ filename, chunk->line, chunk->column,
+ str, *chunk->pos);
+ }
+ else {
+ ucl_create_err (err, "error while parsing %s: at the end of chunk: %s",
+ filename, str);
+ }
+}
+
+/**
+ * Skip all comments from the current pos resolving nested and multiline comments
+ * @param parser
+ * @return
+ */
+static bool
+ucl_skip_comments (struct ucl_parser *parser)
+{
+ struct ucl_chunk *chunk = parser->chunks;
+ const unsigned char *p;
+ int comments_nested = 0;
+ bool quoted = false;
+
+ p = chunk->pos;
+
+start:
+ if (chunk->remain > 0 && *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 (chunk->remain >= 2 && *p == '/') {
+ if (p[1] == '*') {
+ ucl_chunk_skipc (chunk, p);
+ comments_nested ++;
+ ucl_chunk_skipc (chunk, p);
+
+ while (p < chunk->end) {
+ if (*p == '"' && *(p - 1) != '\\') {
+ quoted = !quoted;
+ }
+
+ if (!quoted) {
+ 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 (parser, UCL_ENESTED,
+ "unfinished multiline comment", &parser->err);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Return multiplier for a character
+ * @param c multiplier character
+ * @param is_bytes if true use 1024 multiplier
+ * @return multiplier
+ */
+static inline unsigned long
+ucl_lex_num_multiplier (const unsigned char c, bool is_bytes) {
+ const struct {
+ char c;
+ long mult_normal;
+ long mult_bytes;
+ } multipliers[] = {
+ {'m', 1000 * 1000, 1024 * 1024},
+ {'k', 1000, 1024},
+ {'g', 1000 * 1000 * 1000, 1024 * 1024 * 1024}
+ };
+ int i;
+
+ for (i = 0; i < 3; i ++) {
+ if (tolower (c) == multipliers[i].c) {
+ if (is_bytes) {
+ return multipliers[i].mult_bytes;
+ }
+ return multipliers[i].mult_normal;
+ }
+ }
+
+ return 1;
+}
+
+
+/**
+ * Return multiplier for time scaling
+ * @param c
+ * @return
+ */
+static inline double
+ucl_lex_time_multiplier (const unsigned char c) {
+ const struct {
+ char c;
+ double mult;
+ } multipliers[] = {
+ {'m', 60},
+ {'h', 60 * 60},
+ {'d', 60 * 60 * 24},
+ {'w', 60 * 60 * 24 * 7},
+ {'y', 60 * 60 * 24 * 7 * 365}
+ };
+ int i;
+
+ for (i = 0; i < 5; i ++) {
+ if (tolower (c) == multipliers[i].c) {
+ return multipliers[i].mult;
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Return true if a character is a end of an atom
+ * @param c
+ * @return
+ */
+static inline bool
+ucl_lex_is_atom_end (const unsigned char c)
+{
+ return ucl_test_character (c, UCL_CHARACTER_VALUE_END);
+}
+
+static inline bool
+ucl_lex_is_comment (const unsigned char c1, const unsigned char c2)
+{
+ if (c1 == '/') {
+ if (c2 == '*') {
+ return true;
+ }
+ }
+ else if (c1 == '#') {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check variable found
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param strict
+ * @param found
+ * @return
+ */
+static inline const char *
+ucl_check_variable_safe (struct ucl_parser *parser, const char *ptr, size_t remain,
+ size_t *out_len, bool strict, bool *found)
+{
+ struct ucl_variable *var;
+ unsigned char *dst;
+ size_t dstlen;
+ bool need_free = false;
+
+ LL_FOREACH (parser->variables, var) {
+ if (strict) {
+ if (remain == var->var_len) {
+ if (memcmp (ptr, var->var, var->var_len) == 0) {
+ *out_len += var->value_len;
+ *found = true;
+ return (ptr + var->var_len);
+ }
+ }
+ }
+ else {
+ if (remain >= var->var_len) {
+ if (memcmp (ptr, var->var, var->var_len) == 0) {
+ *out_len += var->value_len;
+ *found = true;
+ return (ptr + var->var_len);
+ }
+ }
+ }
+ }
+
+ /* XXX: can only handle ${VAR} */
+ if (!(*found) && parser->var_handler != NULL && strict) {
+ /* Call generic handler */
+ if (parser->var_handler (ptr, remain, &dst, &dstlen, &need_free,
+ parser->var_data)) {
+ *found = true;
+ if (need_free) {
+ free (dst);
+ }
+ return (ptr + remain);
+ }
+ }
+
+ return ptr;
+}
+
+/**
+ * Check for a variable in a given string
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param vars_found
+ * @return
+ */
+static const char *
+ucl_check_variable (struct ucl_parser *parser, const char *ptr,
+ size_t remain, size_t *out_len, bool *vars_found)
+{
+ const char *p, *end, *ret = ptr;
+ bool found = false;
+
+ if (*ptr == '{') {
+ /* We need to match the variable enclosed in braces */
+ p = ptr + 1;
+ end = ptr + remain;
+ while (p < end) {
+ if (*p == '}') {
+ ret = ucl_check_variable_safe (parser, ptr + 1, p - ptr - 1,
+ out_len, true, &found);
+ if (found) {
+ /* {} must be excluded actually */
+ ret ++;
+ if (!*vars_found) {
+ *vars_found = true;
+ }
+ }
+ else {
+ *out_len += 2;
+ }
+ break;
+ }
+ p ++;
+ }
+ }
+ else if (*ptr != '$') {
+ /* Not count escaped dollar sign */
+ ret = ucl_check_variable_safe (parser, ptr, remain, out_len, false, &found);
+ if (found && !*vars_found) {
+ *vars_found = true;
+ }
+ if (!found) {
+ (*out_len) ++;
+ }
+ }
+ else {
+ ret ++;
+ (*out_len) ++;
+ }
+
+ return ret;
+}
+
+/**
+ * Expand a single variable
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param dest
+ * @return
+ */
+static const char *
+ucl_expand_single_variable (struct ucl_parser *parser, const char *ptr,
+ size_t remain, unsigned char **dest)
+{
+ unsigned char *d = *dest, *dst;
+ const char *p = ptr + 1, *ret;
+ struct ucl_variable *var;
+ size_t dstlen;
+ bool need_free = false;
+ bool found = false;
+ bool strict = false;
+
+ ret = ptr + 1;
+ remain --;
+
+ if (*p == '$') {
+ *d++ = *p++;
+ *dest = d;
+ return p;
+ }
+ else if (*p == '{') {
+ p ++;
+ strict = true;
+ ret += 2;
+ remain -= 2;
+ }
+
+ LL_FOREACH (parser->variables, var) {
+ if (remain >= var->var_len) {
+ if (memcmp (p, var->var, var->var_len) == 0) {
+ memcpy (d, var->value, var->value_len);
+ ret += var->var_len;
+ d += var->value_len;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ if (strict && parser->var_handler != NULL) {
+ if (parser->var_handler (ptr, remain, &dst, &dstlen, &need_free,
+ parser->var_data)) {
+ memcpy (d, dst, dstlen);
+ ret += dstlen;
+ d += remain;
+ found = true;
+ }
+ }
+
+ /* Leave variable as is */
+ if (!found) {
+ if (strict) {
+ /* Copy '${' */
+ memcpy (d, ptr, 2);
+ d += 2;
+ ret --;
+ }
+ else {
+ memcpy (d, ptr, 1);
+ d ++;
+ }
+ }
+ }
+
+ *dest = d;
+ return ret;
+}
+
+/**
+ * Expand variables in string
+ * @param parser
+ * @param dst
+ * @param src
+ * @param in_len
+ * @return
+ */
+static ssize_t
+ucl_expand_variable (struct ucl_parser *parser, unsigned char **dst,
+ const char *src, size_t in_len)
+{
+ const char *p, *end = src + in_len;
+ unsigned char *d;
+ size_t out_len = 0;
+ bool vars_found = false;
+
+ p = src;
+ while (p != end) {
+ if (*p == '$') {
+ p = ucl_check_variable (parser, p + 1, end - p - 1, &out_len, &vars_found);
+ }
+ else {
+ p ++;
+ out_len ++;
+ }
+ }
+
+ if (!vars_found) {
+ /* Trivial case */
+ *dst = NULL;
+ return in_len;
+ }
+
+ *dst = UCL_ALLOC (out_len + 1);
+ if (*dst == NULL) {
+ return in_len;
+ }
+
+ d = *dst;
+ p = src;
+ while (p != end) {
+ if (*p == '$') {
+ p = ucl_expand_single_variable (parser, p, end - p, &d);
+ }
+ else {
+ *d++ = *p++;
+ }
+ }
+
+ *d = '\0';
+
+ return out_len;
+}
+
+/**
+ * Store or copy pointer to the trash stack
+ * @param parser parser object
+ * @param src src string
+ * @param dst destination buffer (trash stack pointer)
+ * @param dst_const const destination pointer (e.g. value of object)
+ * @param in_len input length
+ * @param need_unescape need to unescape source (and copy it)
+ * @param need_lowercase need to lowercase value (and copy)
+ * @param need_expand need to expand variables (and copy as well)
+ * @return output length (excluding \0 symbol)
+ */
+static inline ssize_t
+ucl_copy_or_store_ptr (struct ucl_parser *parser,
+ const unsigned char *src, unsigned char **dst,
+ const char **dst_const, size_t in_len,
+ bool need_unescape, bool need_lowercase, bool need_expand)
+{
+ ssize_t ret = -1, tret;
+ unsigned char *tmp;
+
+ if (need_unescape || need_lowercase ||
+ (need_expand && parser->variables != NULL) ||
+ !(parser->flags & UCL_PARSER_ZEROCOPY)) {
+ /* Copy string */
+ *dst = UCL_ALLOC (in_len + 1);
+ if (*dst == NULL) {
+ ucl_set_err (parser, 0, "cannot allocate memory for a string",
+ &parser->err);
+ return false;
+ }
+ if (need_lowercase) {
+ ret = ucl_strlcpy_tolower (*dst, src, in_len + 1);
+ }
+ else {
+ ret = ucl_strlcpy_unsafe (*dst, src, in_len + 1);
+ }
+
+ if (need_unescape) {
+ ret = ucl_unescape_json_string (*dst, ret);
+ }
+ if (need_expand) {
+ tmp = *dst;
+ tret = ret;
+ ret = ucl_expand_variable (parser, dst, tmp, ret);
+ if (*dst == NULL) {
+ /* Nothing to expand */
+ *dst = tmp;
+ ret = tret;
+ }
+ else {
+ /* Free unexpanded value */
+ UCL_FREE (in_len + 1, tmp);
+ }
+ }
+ *dst_const = *dst;
+ }
+ else {
+ *dst_const = src;
+ ret = in_len;
+ }
+
+ return ret;
+}
+
+/**
+ * Create and append an object at the specified level
+ * @param parser
+ * @param is_array
+ * @param level
+ * @return
+ */
+static inline ucl_object_t *
+ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_array, int level)
+{
+ struct ucl_stack *st;
+
+ if (!is_array) {
+ if (obj == NULL) {
+ obj = ucl_object_new_full (UCL_OBJECT, parser->chunks->priority);
+ }
+ else {
+ obj->type = UCL_OBJECT;
+ }
+ obj->value.ov = ucl_hash_create (parser->flags & UCL_PARSER_KEY_LOWERCASE);
+ parser->state = UCL_STATE_KEY;
+ }
+ else {
+ if (obj == NULL) {
+ obj = ucl_object_new_full (UCL_ARRAY, parser->chunks->priority);
+ }
+ else {
+ obj->type = UCL_ARRAY;
+ }
+ parser->state = UCL_STATE_VALUE;
+ }
+
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ if (st == NULL) {
+ ucl_set_err (parser, 0, "cannot allocate memory for an object",
+ &parser->err);
+ ucl_object_unref (obj);
+ return NULL;
+ }
+ st->obj = obj;
+ st->level = level;
+ LL_PREPEND (parser->stack, st);
+ parser->cur_obj = obj;
+
+ return obj;
+}
+
+int
+ucl_maybe_parse_number (ucl_object_t *obj,
+ const char *start, const char *end, const char **pos,
+ bool allow_double, bool number_bytes, bool allow_time)
+{
+ const char *p = start, *c = start;
+ char *endptr;
+ bool got_dot = false, got_exp = false, need_double = false,
+ is_time = false, valid_start = false, is_hex = false,
+ is_neg = false;
+ double dv = 0;
+ int64_t lv = 0;
+
+ if (*p == '-') {
+ is_neg = true;
+ c ++;
+ p ++;
+ }
+ while (p < end) {
+ if (is_hex && isxdigit (*p)) {
+ p ++;
+ }
+ else if (isdigit (*p)) {
+ valid_start = true;
+ p ++;
+ }
+ else if (!is_hex && (*p == 'x' || *p == 'X')) {
+ is_hex = true;
+ allow_double = false;
+ c = p + 1;
+ }
+ else if (allow_double) {
+ if (p == c) {
+ /* Empty digits sequence, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else if (*p == '.') {
+ if (got_dot) {
+ /* Double dots, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ got_dot = true;
+ need_double = true;
+ p ++;
+ }
+ }
+ else if (*p == 'e' || *p == 'E') {
+ if (got_exp) {
+ /* Double exp, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ got_exp = true;
+ need_double = true;
+ p ++;
+ if (p >= end) {
+ *pos = start;
+ return EINVAL;
+ }
+ if (!isdigit (*p) && *p != '+' && *p != '-') {
+ /* Wrong exponent sign */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ p ++;
+ }
+ }
+ }
+ else {
+ /* Got the end of the number, need to check */
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!valid_start) {
+ *pos = start;
+ return EINVAL;
+ }
+
+ errno = 0;
+ if (need_double) {
+ dv = strtod (c, &endptr);
+ }
+ else {
+ if (is_hex) {
+ lv = strtoimax (c, &endptr, 16);
+ }
+ else {
+ lv = strtoimax (c, &endptr, 10);
+ }
+ }
+ if (errno == ERANGE) {
+ *pos = start;
+ return ERANGE;
+ }
+
+ /* Now check endptr */
+ if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0') {
+ p = endptr;
+ goto set_obj;
+ }
+
+ if (endptr < end && endptr != start) {
+ p = endptr;
+ switch (*p) {
+ case 'm':
+ case 'M':
+ case 'g':
+ case 'G':
+ case 'k':
+ case 'K':
+ if (end - p >= 2) {
+ if (p[1] == 's' || p[1] == 'S') {
+ /* Milliseconds */
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_time = true;
+ if (p[0] == 'm' || p[0] == 'M') {
+ dv /= 1000.;
+ }
+ else {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ p += 2;
+ goto set_obj;
+ }
+ else if (number_bytes || (p[1] == 'b' || p[1] == 'B')) {
+ /* Bytes */
+ if (need_double) {
+ need_double = false;
+ lv = dv;
+ }
+ lv *= ucl_lex_num_multiplier (*p, true);
+ p += 2;
+ goto set_obj;
+ }
+ else if (ucl_lex_is_atom_end (p[1])) {
+ if (need_double) {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ else {
+ lv *= ucl_lex_num_multiplier (*p, number_bytes);
+ }
+ p ++;
+ goto set_obj;
+ }
+ else if (allow_time && end - p >= 3) {
+ if (tolower (p[0]) == 'm' &&
+ tolower (p[1]) == 'i' &&
+ tolower (p[2]) == 'n') {
+ /* Minutes */
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_time = true;
+ dv *= 60.;
+ p += 3;
+ goto set_obj;
+ }
+ }
+ }
+ else {
+ if (need_double) {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ else {
+ lv *= ucl_lex_num_multiplier (*p, number_bytes);
+ }
+ p ++;
+ goto set_obj;
+ }
+ break;
+ case 'S':
+ case 's':
+ if (allow_time &&
+ (p == end - 1 || ucl_lex_is_atom_end (p[1]))) {
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ p ++;
+ is_time = true;
+ goto set_obj;
+ }
+ break;
+ case 'h':
+ case 'H':
+ case 'd':
+ case 'D':
+ case 'w':
+ case 'W':
+ case 'Y':
+ case 'y':
+ if (allow_time &&
+ (p == end - 1 || ucl_lex_is_atom_end (p[1]))) {
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_time = true;
+ dv *= ucl_lex_time_multiplier (*p);
+ p ++;
+ goto set_obj;
+ }
+ break;
+ case '\t':
+ case ' ':
+ while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
+ p++;
+ }
+ if (ucl_lex_is_atom_end(*p))
+ goto set_obj;
+ break;
+ }
+ }
+ else if (endptr == end) {
+ /* Just a number at the end of chunk */
+ p = endptr;
+ goto set_obj;
+ }
+
+ *pos = c;
+ return EINVAL;
+
+ set_obj:
+ if (allow_double && (need_double || is_time)) {
+ if (!is_time) {
+ obj->type = UCL_FLOAT;
+ }
+ else {
+ obj->type = UCL_TIME;
+ }
+ obj->value.dv = is_neg ? (-dv) : dv;
+ }
+ else {
+ obj->type = UCL_INT;
+ obj->value.iv = is_neg ? (-lv) : lv;
+ }
+ *pos = p;
+ return 0;
+}
+
+/**
+ * Parse possible number
+ * @param parser
+ * @param chunk
+ * @return true if a number has been parsed
+ */
+static bool
+ucl_lex_number (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, ucl_object_t *obj)
+{
+ const unsigned char *pos;
+ int ret;
+
+ ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos,
+ true, false, ((parser->flags & UCL_PARSER_NO_TIME) == 0));
+
+ if (ret == 0) {
+ chunk->remain -= pos - chunk->pos;
+ chunk->column += pos - chunk->pos;
+ chunk->pos = pos;
+ return true;
+ }
+ else if (ret == ERANGE) {
+ ucl_set_err (parser, ERANGE, "numeric value out of range", &parser->err);
+ }
+
+ return false;
+}
+
+/**
+ * Parse quoted string with possible escapes
+ * @param parser
+ * @param chunk
+ * @return true if a string has been parsed
+ */
+static bool
+ucl_lex_json_string (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, bool *need_unescape, bool *ucl_escape, bool *var_expand)
+{
+ const unsigned char *p = chunk->pos;
+ unsigned char c;
+ int i;
+
+ while (p < chunk->end) {
+ c = *p;
+ if (c < 0x1F) {
+ /* Unmasked control character */
+ if (c == '\n') {
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected newline",
+ &parser->err);
+ }
+ else {
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected control character",
+ &parser->err);
+ }
+ return false;
+ }
+ else if (c == '\\') {
+ ucl_chunk_skipc (chunk, p);
+ c = *p;
+ if (p >= chunk->end) {
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
+ &parser->err);
+ return false;
+ }
+ else if (ucl_test_character (c, UCL_CHARACTER_ESCAPE)) {
+ if (c == 'u') {
+ ucl_chunk_skipc (chunk, p);
+ for (i = 0; i < 4 && p < chunk->end; i ++) {
+ if (!isxdigit (*p)) {
+ ucl_set_err (parser, UCL_ESYNTAX, "invalid utf escape",
+ &parser->err);
+ return false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (p >= chunk->end) {
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
+ &parser->err);
+ return false;
+ }
+ }
+ else {
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ *need_unescape = true;
+ *ucl_escape = true;
+ continue;
+ }
+ else if (c == '"') {
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ }
+ else if (ucl_test_character (c, UCL_CHARACTER_UCL_UNSAFE)) {
+ *ucl_escape = true;
+ }
+ else if (c == '$') {
+ *var_expand = true;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ ucl_set_err (parser, UCL_ESYNTAX, "no quote at the end of json string",
+ &parser->err);
+ return false;
+}
+
+static void
+ucl_parser_append_elt (struct ucl_parser *parser, ucl_hash_t *cont,
+ ucl_object_t *top,
+ ucl_object_t *elt)
+{
+ ucl_object_t *nobj;
+
+ if ((parser->flags & UCL_PARSER_NO_IMPLICIT_ARRAYS) == 0) {
+ /* Implicit array */
+ top->flags |= UCL_OBJECT_MULTIVALUE;
+ DL_APPEND (top, elt);
+ }
+ else {
+ if ((top->flags & UCL_OBJECT_MULTIVALUE) != 0) {
+ /* Just add to the explicit array */
+ ucl_array_append (top, elt);
+ }
+ else {
+ /* Convert to an array */
+ ucl_hash_delete (cont, top);
+ nobj = ucl_object_typed_new (UCL_ARRAY);
+ nobj->key = top->key;
+ nobj->keylen = top->keylen;
+ nobj->flags |= UCL_OBJECT_MULTIVALUE;
+ ucl_array_append (nobj, top);
+ ucl_array_append (nobj, elt);
+ ucl_hash_insert (cont, nobj, nobj->key, nobj->keylen);
+ }
+ }
+}
+
+/**
+ * Parse a key in an object
+ * @param parser
+ * @param chunk
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_key, bool *end_of_object)
+{
+ const unsigned char *p, *c = NULL, *end, *t;
+ const char *key = NULL;
+ bool got_quote = false, got_eq = false, got_semicolon = false,
+ need_unescape = false, ucl_escape = false, var_expand = false,
+ got_content = false, got_sep = false;
+ ucl_object_t *nobj, *tobj;
+ ucl_hash_t *container;
+ ssize_t keylen;
+
+ p = chunk->pos;
+
+ if (*p == '.') {
+ /* It is macro actually */
+ ucl_chunk_skipc (chunk, p);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_MACRO_NAME;
+ return true;
+ }
+ while (p < chunk->end) {
+ /*
+ * A key must start with alpha, number, '/' or '_' and end with space character
+ */
+ if (c == NULL) {
+ if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) {
+ /* The first symbol */
+ c = p;
+ ucl_chunk_skipc (chunk, p);
+ got_content = true;
+ }
+ else if (*p == '"') {
+ /* JSON style key */
+ c = p + 1;
+ got_quote = true;
+ got_content = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (*p == '}') {
+ /* We have actually end of an object */
+ *end_of_object = true;
+ return true;
+ }
+ else if (*p == '.') {
+ ucl_chunk_skipc (chunk, p);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_MACRO_NAME;
+ return true;
+ }
+ else {
+ /* Invalid identifier */
+ ucl_set_err (parser, UCL_ESYNTAX, "key must begin with a letter",
+ &parser->err);
+ return false;
+ }
+ }
+ else {
+ /* Parse the body of a key */
+ if (!got_quote) {
+ if (ucl_test_character (*p, UCL_CHARACTER_KEY)) {
+ got_content = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_KEY_SEP)) {
+ end = p;
+ break;
+ }
+ else {
+ ucl_set_err (parser, UCL_ESYNTAX, "invalid character in a key",
+ &parser->err);
+ return false;
+ }
+ }
+ else {
+ /* We need to parse json like quoted string */
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+ /* Always escape keys obtained via json */
+ end = chunk->pos - 1;
+ p = chunk->pos;
+ break;
+ }
+ }
+ }
+
+ if (p >= chunk->end && got_content) {
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
+ return false;
+ }
+ else if (!got_content) {
+ return true;
+ }
+ *end_of_object = false;
+ /* We are now at the end of the key, need to parse the rest */
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (*p == '=') {
+ if (!got_eq && !got_semicolon) {
+ ucl_chunk_skipc (chunk, p);
+ got_eq = true;
+ }
+ else {
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected '=' character",
+ &parser->err);
+ return false;
+ }
+ }
+ else if (*p == ':') {
+ if (!got_eq && !got_semicolon) {
+ ucl_chunk_skipc (chunk, p);
+ got_semicolon = true;
+ }
+ else {
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected ':' character",
+ &parser->err);
+ return false;
+ }
+ }
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ /* Check for comment */
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else {
+ /* Start value */
+ break;
+ }
+ }
+
+ if (p >= chunk->end && got_content) {
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
+ return false;
+ }
+
+ got_sep = got_semicolon || got_eq;
+
+ if (!got_sep) {
+ /*
+ * Maybe we have more keys nested, so search for termination character.
+ * Possible choices:
+ * 1) key1 key2 ... keyN [:=] value <- we treat that as error
+ * 2) key1 ... keyN {} or [] <- we treat that as nested objects
+ * 3) key1 value[;,\n] <- we treat that as linear object
+ */
+ t = p;
+ *next_key = false;
+ while (ucl_test_character (*t, UCL_CHARACTER_WHITESPACE)) {
+ t ++;
+ }
+ /* Check first non-space character after a key */
+ if (*t != '{' && *t != '[') {
+ while (t < chunk->end) {
+ if (*t == ',' || *t == ';' || *t == '\n' || *t == '\r') {
+ break;
+ }
+ else if (*t == '{' || *t == '[') {
+ *next_key = true;
+ break;
+ }
+ t ++;
+ }
+ }
+ }
+
+ /* Create a new object */
+ nobj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
+ keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
+ &key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE, false);
+ if (keylen == -1) {
+ ucl_object_unref (nobj);
+ return false;
+ }
+ else if (keylen == 0) {
+ ucl_set_err (parser, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
+ ucl_object_unref (nobj);
+ return false;
+ }
+
+ container = parser->stack->obj->value.ov;
+ nobj->key = key;
+ nobj->keylen = keylen;
+ tobj = __DECONST (ucl_object_t *, ucl_hash_search_obj (container, nobj));
+ if (tobj == NULL) {
+ container = ucl_hash_insert_object (container, nobj,
+ parser->flags & UCL_PARSER_KEY_LOWERCASE);
+ nobj->prev = nobj;
+ nobj->next = NULL;
+ parser->stack->obj->len ++;
+ }
+ else {
+ /*
+ * The logic here is the following:
+ *
+ * - if we have two objects with the same priority, then we form an
+ * implicit or explicit array
+ * - if a new object has bigger priority, then we overwrite an old one
+ * - if a new object has lower priority, then we ignore it
+ */
+ unsigned priold = ucl_object_get_priority (tobj),
+ prinew = ucl_object_get_priority (nobj);
+ if (priold == prinew) {
+ ucl_parser_append_elt (parser, container, tobj, nobj);
+ }
+ else if (priold > prinew) {
+ ucl_object_unref (nobj);
+ return true;
+ }
+ else {
+ ucl_hash_replace (container, tobj, nobj);
+ ucl_object_unref (tobj);
+ }
+ }
+
+ if (ucl_escape) {
+ nobj->flags |= UCL_OBJECT_NEED_KEY_ESCAPE;
+ }
+ parser->stack->obj->value.ov = container;
+
+ parser->cur_obj = nobj;
+
+ return true;
+}
+
+/**
+ * Parse a cl string
+ * @param parser
+ * @param chunk
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_string_value (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, bool *var_expand, bool *need_unescape)
+{
+ const unsigned char *p;
+ enum {
+ UCL_BRACE_ROUND = 0,
+ UCL_BRACE_SQUARE,
+ UCL_BRACE_FIGURE
+ };
+ int braces[3][2] = {{0, 0}, {0, 0}, {0, 0}};
+
+ p = chunk->pos;
+
+ while (p < chunk->end) {
+
+ /* Skip pairs of figure braces */
+ if (*p == '{') {
+ braces[UCL_BRACE_FIGURE][0] ++;
+ }
+ else if (*p == '}') {
+ braces[UCL_BRACE_FIGURE][1] ++;
+ if (braces[UCL_BRACE_FIGURE][1] <= braces[UCL_BRACE_FIGURE][0]) {
+ /* This is not a termination symbol, continue */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ }
+ /* Skip pairs of square braces */
+ else if (*p == '[') {
+ braces[UCL_BRACE_SQUARE][0] ++;
+ }
+ else if (*p == ']') {
+ braces[UCL_BRACE_SQUARE][1] ++;
+ if (braces[UCL_BRACE_SQUARE][1] <= braces[UCL_BRACE_SQUARE][0]) {
+ /* This is not a termination symbol, continue */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ }
+ else if (*p == '$') {
+ *var_expand = true;
+ }
+ else if (*p == '\\') {
+ *need_unescape = true;
+ ucl_chunk_skipc (chunk, p);
+ if (p < chunk->end) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ continue;
+ }
+
+ if (ucl_lex_is_atom_end (*p) || (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ return true;
+}
+
+/**
+ * Parse multiline string ending with \n{term}\n
+ * @param parser
+ * @param chunk
+ * @param term
+ * @param term_len
+ * @return size of multiline string or 0 in case of error
+ */
+static int
+ucl_parse_multiline_string (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, const unsigned char *term,
+ int term_len, unsigned char const **beg,
+ bool *var_expand)
+{
+ const unsigned char *p, *c, *tend;
+ 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) {
+ tend = p + term_len;
+ if (*tend != '\n' && *tend != ';' && *tend != ',') {
+ /* Incomplete terminator */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ len = p - c;
+ chunk->remain -= term_len;
+ chunk->pos = p + term_len;
+ chunk->column = term_len;
+ *beg = c;
+ break;
+ }
+ }
+ if (*p == '\n') {
+ newline = true;
+ }
+ else {
+ if (*p == '$') {
+ *var_expand = true;
+ }
+ newline = false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ return len;
+}
+
+static ucl_object_t*
+ucl_get_value_object (struct ucl_parser *parser)
+{
+ ucl_object_t *t, *obj = NULL;
+
+ if (parser == NULL || parser->stack == NULL || parser->stack->obj == NULL) {
+ return NULL;
+ }
+
+ if (parser->stack->obj->type == UCL_ARRAY) {
+ /* Object must be allocated */
+ obj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
+ t = parser->stack->obj;
+ ucl_array_append (t, obj);
+ parser->cur_obj = obj;
+ }
+ else {
+ /* Object has been already allocated */
+ obj = parser->cur_obj;
+ }
+
+ return obj;
+}
+
+/**
+ * Handle value data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
+{
+ const unsigned char *p, *c;
+ ucl_object_t *obj = NULL;
+ unsigned int stripped_spaces;
+ int str_len;
+ bool need_unescape = false, ucl_escape = false, var_expand = false;
+
+ p = chunk->pos;
+
+ /* Skip any spaces and comments */
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) ||
+ (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
+ while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+
+ while (p < chunk->end) {
+ c = p;
+ switch (*p) {
+ case '"':
+ obj = ucl_get_value_object (parser);
+ ucl_chunk_skipc (chunk, p);
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+ str_len = chunk->pos - c - 2;
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c + 1, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len, need_unescape, false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ p = chunk->pos;
+ return true;
+ break;
+ case '{':
+ obj = ucl_get_value_object (parser);
+ /* We have a new object */
+ obj = ucl_add_parser_stack (obj, parser, false, parser->stack->level);
+ if (obj == NULL) {
+ return false;
+ }
+
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ break;
+ case '[':
+ obj = ucl_get_value_object (parser);
+ /* We have a new array */
+ obj = ucl_add_parser_stack (obj, parser, true, parser->stack->level);
+ if (obj == NULL) {
+ return false;
+ }
+
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ break;
+ case ']':
+ /* We have the array ending */
+ if (parser->stack && parser->stack->obj->type == UCL_ARRAY) {
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ else {
+ goto parse_string;
+ }
+ break;
+ case '<':
+ obj = ucl_get_value_object (parser);
+ /* We have something like multiline value, which must be <<[A-Z]+\n */
+ if (chunk->end - p > 3) {
+ if (memcmp (p, "<<", 2) == 0) {
+ p += 2;
+ /* We allow only uppercase characters in multiline definitions */
+ while (p < chunk->end && *p >= 'A' && *p <= 'Z') {
+ p ++;
+ }
+ if (*p =='\n') {
+ /* Set chunk positions and start multiline parsing */
+ c += 2;
+ chunk->remain -= p - c;
+ chunk->pos = p + 1;
+ chunk->column = 0;
+ chunk->line ++;
+ if ((str_len = ucl_parse_multiline_string (parser, chunk, c,
+ p - c, &c, &var_expand)) == 0) {
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "unterminated multiline value", &parser->err);
+ return false;
+ }
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len - 1, false, false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ }
+ }
+ /* Fallback to ordinary strings */
+ default:
+parse_string:
+ if (obj == NULL) {
+ obj = ucl_get_value_object (parser);
+ }
+ /* Parse atom */
+ if (ucl_test_character (*p, UCL_CHARACTER_VALUE_DIGIT_START)) {
+ if (!ucl_lex_number (parser, chunk, obj)) {
+ if (parser->state == UCL_STATE_ERROR) {
+ return false;
+ }
+ }
+ else {
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ /* Fallback to normal string */
+ }
+
+ if (!ucl_parse_string_value (parser, chunk, &var_expand, &need_unescape)) {
+ return false;
+ }
+ /* Cut trailing spaces */
+ stripped_spaces = 0;
+ while (ucl_test_character (*(chunk->pos - 1 - stripped_spaces),
+ UCL_CHARACTER_WHITESPACE)) {
+ stripped_spaces ++;
+ }
+ str_len = chunk->pos - c - stripped_spaces;
+ if (str_len <= 0) {
+ ucl_set_err (parser, 0, "string value must not be empty",
+ &parser->err);
+ return false;
+ }
+ else if (str_len == 4 && memcmp (c, "null", 4) == 0) {
+ obj->len = 0;
+ obj->type = UCL_NULL;
+ }
+ else if (!ucl_maybe_parse_boolean (obj, c, str_len)) {
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len, need_unescape,
+ false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ }
+ parser->state = UCL_STATE_AFTER_VALUE;
+ p = chunk->pos;
+
+ return true;
+ break;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle after value data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
+{
+ const unsigned char *p;
+ bool got_sep = false;
+ struct ucl_stack *st;
+
+ p = chunk->pos;
+
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) {
+ /* Skip whitespaces */
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ /* Skip comment */
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ /* Treat comment as a separator */
+ got_sep = true;
+ p = chunk->pos;
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) {
+ if (*p == '}' || *p == ']') {
+ if (parser->stack == NULL) {
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "end of array or object detected without corresponding start",
+ &parser->err);
+ return false;
+ }
+ if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
+ (*p == ']' && parser->stack->obj->type == UCL_ARRAY)) {
+
+ /* Pop all nested objects from a stack */
+ st = parser->stack;
+ parser->stack = st->next;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+
+ while (parser->stack != NULL) {
+ st = parser->stack;
+ if (st->next == NULL || st->next->level == st->level) {
+ break;
+ }
+ parser->stack = st->next;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+ }
+ }
+ else {
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "unexpected terminating symbol detected",
+ &parser->err);
+ return false;
+ }
+
+ if (parser->stack == NULL) {
+ /* Ignore everything after a top object */
+ return true;
+ }
+ else {
+ ucl_chunk_skipc (chunk, p);
+ }
+ got_sep = true;
+ }
+ else {
+ /* Got a separator */
+ got_sep = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ else {
+ /* Anything else */
+ if (!got_sep) {
+ ucl_set_err (parser, UCL_ESYNTAX, "delimiter is missing",
+ &parser->err);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle macro data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_macro_value (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, struct ucl_macro *macro,
+ unsigned char const **macro_start, size_t *macro_len)
+{
+ const unsigned char *p, *c;
+ bool need_unescape = false, ucl_escape = false, var_expand = false;
+
+ p = chunk->pos;
+
+ switch (*p) {
+ case '"':
+ /* We have macro value encoded in quotes */
+ c = p;
+ ucl_chunk_skipc (chunk, p);
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+
+ *macro_start = c + 1;
+ *macro_len = chunk->pos - c - 2;
+ p = chunk->pos;
+ break;
+ case '{':
+ /* We got a multiline macro body */
+ ucl_chunk_skipc (chunk, p);
+ /* Skip spaces at the beginning */
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ break;
+ }
+ }
+ c = p;
+ while (p < chunk->end) {
+ if (*p == '}') {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ *macro_start = c;
+ *macro_len = p - c;
+ ucl_chunk_skipc (chunk, p);
+ break;
+ default:
+ /* Macro is not enclosed in quotes or braces */
+ c = p;
+ while (p < chunk->end) {
+ if (ucl_lex_is_atom_end (*p)) {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ *macro_start = c;
+ *macro_len = p - c;
+ break;
+ }
+
+ /* We are at the end of a macro */
+ /* Skip ';' and space characters and return to previous state */
+ while (p < chunk->end) {
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) && *p != ';') {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ return true;
+}
+
+/**
+ * Parse macro arguments as UCL object
+ * @param parser parser structure
+ * @param chunk the current data chunk
+ * @return
+ */
+static ucl_object_t *
+ucl_parse_macro_arguments (struct ucl_parser *parser,
+ struct ucl_chunk *chunk)
+{
+ ucl_object_t *res = NULL;
+ struct ucl_parser *params_parser;
+ int obraces = 1, ebraces = 0, state = 0;
+ const unsigned char *p, *c;
+ size_t args_len = 0;
+ struct ucl_parser_saved_state saved;
+
+ saved.column = chunk->column;
+ saved.line = chunk->line;
+ saved.pos = chunk->pos;
+ saved.remain = chunk->remain;
+ p = chunk->pos;
+
+ if (*p != '(' || chunk->remain < 2) {
+ return NULL;
+ }
+
+ /* Set begin and start */
+ ucl_chunk_skipc (chunk, p);
+ c = p;
+
+ while ((p) < (chunk)->end) {
+ switch (state) {
+ case 0:
+ /* Parse symbols and check for '(', ')' and '"' */
+ if (*p == '(') {
+ obraces ++;
+ }
+ else if (*p == ')') {
+ ebraces ++;
+ }
+ else if (*p == '"') {
+ state = 1;
+ }
+ /* Check pairing */
+ if (obraces == ebraces) {
+ state = 99;
+ }
+ else {
+ args_len ++;
+ }
+ /* Check overflow */
+ if (chunk->remain == 0) {
+ goto restore_chunk;
+ }
+ ucl_chunk_skipc (chunk, p);
+ break;
+ case 1:
+ /* We have quote character, so skip all but quotes */
+ if (*p == '"' && *(p - 1) != '\\') {
+ state = 0;
+ }
+ if (chunk->remain == 0) {
+ goto restore_chunk;
+ }
+ ucl_chunk_skipc (chunk, p);
+ break;
+ case 99:
+ /*
+ * We have read the full body of arguments, so we need to parse and set
+ * object from that
+ */
+ params_parser = ucl_parser_new (parser->flags);
+ if (!ucl_parser_add_chunk (params_parser, c, args_len)) {
+ ucl_set_err (parser, UCL_ESYNTAX, "macro arguments parsing error",
+ &parser->err);
+ }
+ else {
+ res = ucl_parser_get_object (params_parser);
+ }
+ ucl_parser_free (params_parser);
+
+ return res;
+
+ break;
+ }
+ }
+
+ return res;
+
+restore_chunk:
+ chunk->column = saved.column;
+ chunk->line = saved.line;
+ chunk->pos = saved.pos;
+ chunk->remain = saved.remain;
+
+ return NULL;
+}
+
+#define SKIP_SPACES_COMMENTS(parser, chunk, p) do { \
+ while ((p) < (chunk)->end) { \
+ if (!ucl_test_character (*(p), UCL_CHARACTER_WHITESPACE_UNSAFE)) { \
+ if ((chunk)->remain >= 2 && ucl_lex_is_comment ((p)[0], (p)[1])) { \
+ if (!ucl_skip_comments (parser)) { \
+ return false; \
+ } \
+ p = (chunk)->pos; \
+ } \
+ break; \
+ } \
+ ucl_chunk_skipc (chunk, p); \
+ } \
+} while(0)
+
+/**
+ * Handle the main states of rcl parser
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @return true if chunk has been parsed and false in case of error
+ */
+static bool
+ucl_state_machine (struct ucl_parser *parser)
+{
+ ucl_object_t *obj, *macro_args;
+ struct ucl_chunk *chunk = parser->chunks;
+ const unsigned char *p, *c = NULL, *macro_start = NULL;
+ unsigned char *macro_escaped;
+ size_t macro_len = 0;
+ struct ucl_macro *macro = NULL;
+ bool next_key = false, end_of_object = false, ret;
+
+ if (parser->top_obj == NULL) {
+ if (*chunk->pos == '[') {
+ obj = ucl_add_parser_stack (NULL, parser, true, 0);
+ }
+ else {
+ obj = ucl_add_parser_stack (NULL, parser, false, 0);
+ }
+ if (obj == NULL) {
+ return false;
+ }
+ parser->top_obj = obj;
+ parser->cur_obj = obj;
+ parser->state = UCL_STATE_INIT;
+ }
+
+ p = chunk->pos;
+ while (chunk->pos < chunk->end) {
+ switch (parser->state) {
+ case UCL_STATE_INIT:
+ /*
+ * At the init state we can either go to the parse array or object
+ * if we got [ or { correspondingly or can just treat new data as
+ * a key of newly created object
+ */
+ if (!ucl_skip_comments (parser)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ else {
+ /* Skip any spaces */
+ while (p < chunk->end && ucl_test_character (*p,
+ UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ p = chunk->pos;
+ if (*p == '[') {
+ parser->state = UCL_STATE_VALUE;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ parser->state = UCL_STATE_KEY;
+ if (*p == '{') {
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ }
+ break;
+ case UCL_STATE_KEY:
+ /* Skip any spaces */
+ while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (*p == '}') {
+ /* We have the end of an object */
+ parser->state = UCL_STATE_AFTER_VALUE;
+ continue;
+ }
+ if (parser->stack == NULL) {
+ /* No objects are on stack, but we want to parse a key */
+ ucl_set_err (parser, UCL_ESYNTAX, "top object is finished but the parser "
+ "expects a key", &parser->err);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (!ucl_parse_key (parser, chunk, &next_key, &end_of_object)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (end_of_object) {
+ p = chunk->pos;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ continue;
+ }
+ else if (parser->state != UCL_STATE_MACRO_NAME) {
+ if (next_key && parser->stack->obj->type == UCL_OBJECT) {
+ /* Parse more keys and nest objects accordingly */
+ obj = ucl_add_parser_stack (parser->cur_obj, parser, false,
+ parser->stack->level + 1);
+ if (obj == NULL) {
+ return false;
+ }
+ }
+ else {
+ parser->state = UCL_STATE_VALUE;
+ }
+ }
+ else {
+ c = chunk->pos;
+ }
+ p = chunk->pos;
+ break;
+ case UCL_STATE_VALUE:
+ /* We need to check what we do have */
+ if (!ucl_parse_value (parser, chunk)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ /* State is set in ucl_parse_value call */
+ p = chunk->pos;
+ break;
+ case UCL_STATE_AFTER_VALUE:
+ if (!ucl_parse_after_value (parser, chunk)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (parser->stack != NULL) {
+ if (parser->stack->obj->type == UCL_OBJECT) {
+ parser->state = UCL_STATE_KEY;
+ }
+ else {
+ /* Array */
+ parser->state = UCL_STATE_VALUE;
+ }
+ }
+ else {
+ /* Skip everything at the end */
+ return true;
+ }
+ p = chunk->pos;
+ break;
+ case UCL_STATE_MACRO_NAME:
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) &&
+ *p != '(') {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (p - c > 0) {
+ /* We got macro name */
+ macro_len = (size_t)(p - c);
+ HASH_FIND (hh, parser->macroes, c, macro_len, macro);
+ if (macro == NULL) {
+ ucl_create_err (&parser->err, "error on line %d at column %d: "
+ "unknown macro: '%.*s', character: '%c'",
+ chunk->line, chunk->column, (int)(p - c), c, *chunk->pos);
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ /* Now we need to skip all spaces */
+ SKIP_SPACES_COMMENTS(parser, chunk, p);
+ parser->state = UCL_STATE_MACRO;
+ }
+ break;
+ case UCL_STATE_MACRO:
+ if (*chunk->pos == '(') {
+ macro_args = ucl_parse_macro_arguments (parser, chunk);
+ p = chunk->pos;
+ if (macro_args) {
+ SKIP_SPACES_COMMENTS(parser, chunk, p);
+ }
+ }
+ else {
+ macro_args = NULL;
+ }
+ if (!ucl_parse_macro_value (parser, chunk, macro,
+ &macro_start, &macro_len)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ macro_len = ucl_expand_variable (parser, &macro_escaped,
+ macro_start, macro_len);
+ parser->state = parser->prev_state;
+ if (macro_escaped == NULL) {
+ ret = macro->handler (macro_start, macro_len, macro_args,
+ macro->ud);
+ }
+ else {
+ ret = macro->handler (macro_escaped, macro_len, macro_args,
+ macro->ud);
+ UCL_FREE (macro_len + 1, macro_escaped);
+ }
+ p = chunk->pos;
+ if (macro_args) {
+ ucl_object_unref (macro_args);
+ }
+ if (!ret) {
+ return false;
+ }
+ break;
+ default:
+ /* TODO: add all states */
+ ucl_set_err (parser, UCL_EINTERNAL,
+ "internal error: parser is in an unknown state", &parser->err);
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+struct ucl_parser*
+ucl_parser_new (int flags)
+{
+ struct ucl_parser *new;
+
+ new = UCL_ALLOC (sizeof (struct ucl_parser));
+ if (new == NULL) {
+ return NULL;
+ }
+ memset (new, 0, sizeof (struct ucl_parser));
+
+ ucl_parser_register_macro (new, "include", ucl_include_handler, new);
+ ucl_parser_register_macro (new, "try_include", ucl_try_include_handler, new);
+ ucl_parser_register_macro (new, "includes", ucl_includes_handler, new);
+
+ new->flags = flags;
+
+ /* Initial assumption about filevars */
+ ucl_parser_set_filevars (new, NULL, false);
+
+ return new;
+}
+
+
+void
+ucl_parser_register_macro (struct ucl_parser *parser, const char *macro,
+ ucl_macro_handler handler, void* ud)
+{
+ struct ucl_macro *new;
+
+ if (macro == NULL || handler == NULL) {
+ return;
+ }
+ new = UCL_ALLOC (sizeof (struct ucl_macro));
+ if (new == NULL) {
+ return;
+ }
+ memset (new, 0, sizeof (struct ucl_macro));
+ new->handler = handler;
+ new->name = strdup (macro);
+ new->ud = ud;
+ HASH_ADD_KEYPTR (hh, parser->macroes, new->name, strlen (new->name), new);
+}
+
+void
+ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
+ const char *value)
+{
+ struct ucl_variable *new = NULL, *cur;
+
+ if (var == NULL) {
+ return;
+ }
+
+ /* Find whether a variable already exists */
+ LL_FOREACH (parser->variables, cur) {
+ if (strcmp (cur->var, var) == 0) {
+ new = cur;
+ break;
+ }
+ }
+
+ if (value == NULL) {
+
+ if (new != NULL) {
+ /* Remove variable */
+ DL_DELETE (parser->variables, new);
+ free (new->var);
+ free (new->value);
+ UCL_FREE (sizeof (struct ucl_variable), new);
+ }
+ else {
+ /* Do nothing */
+ return;
+ }
+ }
+ else {
+ if (new == NULL) {
+ new = UCL_ALLOC (sizeof (struct ucl_variable));
+ if (new == NULL) {
+ return;
+ }
+ memset (new, 0, sizeof (struct ucl_variable));
+ new->var = strdup (var);
+ new->var_len = strlen (var);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+
+ DL_APPEND (parser->variables, new);
+ }
+ else {
+ free (new->value);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+ }
+ }
+}
+
+void
+ucl_parser_set_variables_handler (struct ucl_parser *parser,
+ ucl_variable_handler handler, void *ud)
+{
+ parser->var_handler = handler;
+ parser->var_data = ud;
+}
+
+bool
+ucl_parser_add_chunk_priority (struct ucl_parser *parser, const unsigned char *data,
+ size_t len, unsigned priority)
+{
+ struct ucl_chunk *chunk;
+
+ if (data == NULL) {
+ ucl_create_err (&parser->err, "invalid chunk added");
+ return false;
+ }
+ if (len == 0) {
+ parser->top_obj = ucl_object_new_full (UCL_OBJECT, priority);
+ return true;
+ }
+ if (parser->state != UCL_STATE_ERROR) {
+ chunk = UCL_ALLOC (sizeof (struct ucl_chunk));
+ if (chunk == NULL) {
+ ucl_create_err (&parser->err, "cannot allocate chunk structure");
+ return false;
+ }
+ chunk->begin = data;
+ chunk->remain = len;
+ chunk->pos = chunk->begin;
+ chunk->end = chunk->begin + len;
+ chunk->line = 1;
+ chunk->column = 0;
+ chunk->priority = priority;
+ LL_PREPEND (parser->chunks, chunk);
+ parser->recursion ++;
+ if (parser->recursion > UCL_MAX_RECURSION) {
+ ucl_create_err (&parser->err, "maximum include nesting limit is reached: %d",
+ parser->recursion);
+ return false;
+ }
+ return ucl_state_machine (parser);
+ }
+
+ ucl_create_err (&parser->err, "a parser is in an invalid state");
+
+ return false;
+}
+
+bool
+ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
+ size_t len)
+{
+ return ucl_parser_add_chunk_priority (parser, data, len, 0);
+}
+
+bool
+ucl_parser_add_string (struct ucl_parser *parser, const char *data,
+ size_t len)
+{
+ if (data == NULL) {
+ ucl_create_err (&parser->err, "invalid string added");
+ return false;
+ }
+ if (len == 0) {
+ len = strlen (data);
+ }
+
+ return ucl_parser_add_chunk (parser, (const unsigned char *)data, len);
+}
diff --git a/contrib/libucl/ucl_schema.c b/contrib/libucl/ucl_schema.c
new file mode 100644
index 000000000..834b62acc
--- /dev/null
+++ b/contrib/libucl/ucl_schema.c
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (c) 2014, Vsevolod Stakhov
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "tree.h"
+#include "utlist.h"
+#ifdef HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#ifdef HAVE_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+#ifdef HAVE_MATH_H
+#include <math.h>
+#endif
+
+static bool ucl_schema_validate (const ucl_object_t *schema,
+ const ucl_object_t *obj, bool try_array,
+ struct ucl_schema_error *err,
+ const ucl_object_t *root);
+
+static bool
+ucl_string_to_type (const char *input, ucl_type_t *res)
+{
+ if (strcasecmp (input, "object") == 0) {
+ *res = UCL_OBJECT;
+ }
+ else if (strcasecmp (input, "array") == 0) {
+ *res = UCL_ARRAY;
+ }
+ else if (strcasecmp (input, "integer") == 0) {
+ *res = UCL_INT;
+ }
+ else if (strcasecmp (input, "number") == 0) {
+ *res = UCL_FLOAT;
+ }
+ else if (strcasecmp (input, "string") == 0) {
+ *res = UCL_STRING;
+ }
+ else if (strcasecmp (input, "boolean") == 0) {
+ *res = UCL_BOOLEAN;
+ }
+ else if (strcasecmp (input, "null") == 0) {
+ *res = UCL_NULL;
+ }
+ else {
+ return false;
+ }
+
+ return true;
+}
+
+static const char *
+ucl_object_type_to_string (ucl_type_t type)
+{
+ const char *res = "unknown";
+
+ switch (type) {
+ case UCL_OBJECT:
+ res = "object";
+ break;
+ case UCL_ARRAY:
+ res = "array";
+ break;
+ case UCL_INT:
+ res = "integer";
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ res = "number";
+ break;
+ case UCL_STRING:
+ res = "string";
+ break;
+ case UCL_BOOLEAN:
+ res = "boolean";
+ break;
+ case UCL_NULL:
+ case UCL_USERDATA:
+ res = "null";
+ break;
+ }
+
+ return res;
+}
+
+/*
+ * Create validation error
+ */
+static void
+ucl_schema_create_error (struct ucl_schema_error *err,
+ enum ucl_schema_error_code code, const ucl_object_t *obj,
+ const char *fmt, ...)
+{
+ va_list va;
+
+ if (err != NULL) {
+ err->code = code;
+ err->obj = obj;
+ va_start (va, fmt);
+ vsnprintf (err->msg, sizeof (err->msg), fmt, va);
+ va_end (va);
+ }
+}
+
+/*
+ * Check whether we have a pattern specified
+ */
+static const ucl_object_t *
+ucl_schema_test_pattern (const ucl_object_t *obj, const char *pattern)
+{
+ const ucl_object_t *res = NULL;
+#ifdef HAVE_REGEX_H
+ regex_t reg;
+ const ucl_object_t *elt;
+ ucl_object_iter_t iter = NULL;
+
+ if (regcomp (&reg, pattern, REG_EXTENDED | REG_NOSUB) == 0) {
+ while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) {
+ if (regexec (&reg, ucl_object_key (elt), 0, NULL, 0) == 0) {
+ res = elt;
+ break;
+ }
+ }
+ regfree (&reg);
+ }
+#endif
+ return res;
+}
+
+/*
+ * Check dependencies for an object
+ */
+static bool
+ucl_schema_validate_dependencies (const ucl_object_t *deps,
+ const ucl_object_t *obj, struct ucl_schema_error *err,
+ const ucl_object_t *root)
+{
+ const ucl_object_t *elt, *cur, *cur_dep;
+ ucl_object_iter_t iter = NULL, piter;
+ bool ret = true;
+
+ while (ret && (cur = ucl_iterate_object (deps, &iter, true)) != NULL) {
+ elt = ucl_object_find_key (obj, ucl_object_key (cur));
+ if (elt != NULL) {
+ /* Need to check dependencies */
+ if (cur->type == UCL_ARRAY) {
+ piter = NULL;
+ while (ret && (cur_dep = ucl_iterate_object (cur, &piter, true)) != NULL) {
+ if (ucl_object_find_key (obj, ucl_object_tostring (cur_dep)) == NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt,
+ "dependency %s is missing for key %s",
+ ucl_object_tostring (cur_dep), ucl_object_key (cur));
+ ret = false;
+ break;
+ }
+ }
+ }
+ else if (cur->type == UCL_OBJECT) {
+ ret = ucl_schema_validate (cur, obj, true, err, root);
+ }
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Validate object
+ */
+static bool
+ucl_schema_validate_object (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err,
+ const ucl_object_t *root)
+{
+ const ucl_object_t *elt, *prop, *found, *additional_schema = NULL,
+ *required = NULL, *pat, *pelt;
+ ucl_object_iter_t iter = NULL, piter = NULL;
+ bool ret = true, allow_additional = true;
+ int64_t minmax;
+
+ while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) {
+ if (elt->type == UCL_OBJECT &&
+ strcmp (ucl_object_key (elt), "properties") == 0) {
+ piter = NULL;
+ while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) {
+ found = ucl_object_find_key (obj, ucl_object_key (prop));
+ if (found) {
+ ret = ucl_schema_validate (prop, found, true, err, root);
+ }
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) {
+ if (elt->type == UCL_BOOLEAN) {
+ if (!ucl_object_toboolean (elt)) {
+ /* Deny additional fields completely */
+ allow_additional = false;
+ }
+ }
+ else if (elt->type == UCL_OBJECT) {
+ /* Define validator for additional fields */
+ additional_schema = elt;
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "additionalProperties attribute is invalid in schema");
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "required") == 0) {
+ if (elt->type == UCL_ARRAY) {
+ required = elt;
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "required attribute is invalid in schema");
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "minProperties") == 0
+ && ucl_object_toint_safe (elt, &minmax)) {
+ if (obj->len < minmax) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object has not enough properties: %u, minimum is: %u",
+ obj->len, (unsigned)minmax);
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "maxProperties") == 0
+ && ucl_object_toint_safe (elt, &minmax)) {
+ if (obj->len > minmax) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object has too many properties: %u, maximum is: %u",
+ obj->len, (unsigned)minmax);
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "patternProperties") == 0) {
+ piter = NULL;
+ while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) {
+ found = ucl_schema_test_pattern (obj, ucl_object_key (prop));
+ if (found) {
+ ret = ucl_schema_validate (prop, found, true, err, root);
+ }
+ }
+ }
+ else if (elt->type == UCL_OBJECT &&
+ strcmp (ucl_object_key (elt), "dependencies") == 0) {
+ ret = ucl_schema_validate_dependencies (elt, obj, err, root);
+ }
+ }
+
+ if (ret) {
+ /* Additional properties */
+ if (!allow_additional || additional_schema != NULL) {
+ /* Check if we have exactly the same properties in schema and object */
+ iter = NULL;
+ prop = ucl_object_find_key (schema, "properties");
+ while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) {
+ found = ucl_object_find_key (prop, ucl_object_key (elt));
+ if (found == NULL) {
+ /* Try patternProperties */
+ piter = NULL;
+ pat = ucl_object_find_key (schema, "patternProperties");
+ while ((pelt = ucl_iterate_object (pat, &piter, true)) != NULL) {
+ found = ucl_schema_test_pattern (obj, ucl_object_key (pelt));
+ if (found != NULL) {
+ break;
+ }
+ }
+ }
+ if (found == NULL) {
+ if (!allow_additional) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object has non-allowed property %s",
+ ucl_object_key (elt));
+ ret = false;
+ break;
+ }
+ else if (additional_schema != NULL) {
+ if (!ucl_schema_validate (additional_schema, elt, true, err, root)) {
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ /* Required properties */
+ if (required != NULL) {
+ iter = NULL;
+ while ((elt = ucl_iterate_object (required, &iter, true)) != NULL) {
+ if (ucl_object_find_key (obj, ucl_object_tostring (elt)) == NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_MISSING_PROPERTY, obj,
+ "object has missing property %s",
+ ucl_object_tostring (elt));
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+
+
+ return ret;
+}
+
+static bool
+ucl_schema_validate_number (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err)
+{
+ const ucl_object_t *elt, *test;
+ ucl_object_iter_t iter = NULL;
+ bool ret = true, exclusive = false;
+ double constraint, val;
+ const double alpha = 1e-16;
+
+ while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) {
+ if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
+ strcmp (ucl_object_key (elt), "multipleOf") == 0) {
+ constraint = ucl_object_todouble (elt);
+ if (constraint <= 0) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "multipleOf must be greater than zero");
+ ret = false;
+ break;
+ }
+ val = ucl_object_todouble (obj);
+ if (fabs (remainder (val, constraint)) > alpha) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "number %.4f is not multiple of %.4f, remainder is %.7f",
+ val, constraint);
+ ret = false;
+ break;
+ }
+ }
+ else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
+ strcmp (ucl_object_key (elt), "maximum") == 0) {
+ constraint = ucl_object_todouble (elt);
+ test = ucl_object_find_key (schema, "exclusiveMaximum");
+ if (test && test->type == UCL_BOOLEAN) {
+ exclusive = ucl_object_toboolean (test);
+ }
+ val = ucl_object_todouble (obj);
+ if (val > constraint || (exclusive && val >= constraint)) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "number is too big: %.3f, maximum is: %.3f",
+ val, constraint);
+ ret = false;
+ break;
+ }
+ }
+ else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
+ strcmp (ucl_object_key (elt), "minimum") == 0) {
+ constraint = ucl_object_todouble (elt);
+ test = ucl_object_find_key (schema, "exclusiveMinimum");
+ if (test && test->type == UCL_BOOLEAN) {
+ exclusive = ucl_object_toboolean (test);
+ }
+ val = ucl_object_todouble (obj);
+ if (val < constraint || (exclusive && val <= constraint)) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "number is too small: %.3f, minimum is: %.3f",
+ val, constraint);
+ ret = false;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static bool
+ucl_schema_validate_string (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err)
+{
+ const ucl_object_t *elt;
+ ucl_object_iter_t iter = NULL;
+ bool ret = true;
+ int64_t constraint;
+#ifdef HAVE_REGEX_H
+ regex_t re;
+#endif
+
+ while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) {
+ if (elt->type == UCL_INT &&
+ strcmp (ucl_object_key (elt), "maxLength") == 0) {
+ constraint = ucl_object_toint (elt);
+ if (obj->len > constraint) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "string is too big: %.3f, maximum is: %.3f",
+ obj->len, constraint);
+ ret = false;
+ break;
+ }
+ }
+ else if (elt->type == UCL_INT &&
+ strcmp (ucl_object_key (elt), "minLength") == 0) {
+ constraint = ucl_object_toint (elt);
+ if (obj->len < constraint) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "string is too short: %.3f, minimum is: %.3f",
+ obj->len, constraint);
+ ret = false;
+ break;
+ }
+ }
+#ifdef HAVE_REGEX_H
+ else if (elt->type == UCL_STRING &&
+ strcmp (ucl_object_key (elt), "pattern") == 0) {
+ if (regcomp (&re, ucl_object_tostring (elt),
+ REG_EXTENDED | REG_NOSUB) != 0) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "cannot compile pattern %s", ucl_object_tostring (elt));
+ ret = false;
+ break;
+ }
+ if (regexec (&re, ucl_object_tostring (obj), 0, NULL, 0) != 0) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "string doesn't match regexp %s",
+ ucl_object_tostring (elt));
+ ret = false;
+ }
+ regfree (&re);
+ }
+#endif
+ }
+
+ return ret;
+}
+
+struct ucl_compare_node {
+ const ucl_object_t *obj;
+ TREE_ENTRY(ucl_compare_node) link;
+ struct ucl_compare_node *next;
+};
+
+typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t;
+
+TREE_DEFINE(ucl_compare_node, link)
+
+static int
+ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2)
+{
+ const ucl_object_t *o1 = n1->obj, *o2 = n2->obj;
+
+ return ucl_object_compare (o1, o2);
+}
+
+static bool
+ucl_schema_array_is_unique (const ucl_object_t *obj, struct ucl_schema_error *err)
+{
+ ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare);
+ ucl_object_iter_t iter = NULL;
+ const ucl_object_t *elt;
+ struct ucl_compare_node *node, test, *nodes = NULL, *tmp;
+ bool ret = true;
+
+ while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) {
+ test.obj = elt;
+ node = TREE_FIND (&tree, ucl_compare_node, link, &test);
+ if (node != NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt,
+ "duplicate values detected while uniqueItems is true");
+ ret = false;
+ break;
+ }
+ node = calloc (1, sizeof (*node));
+ if (node == NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt,
+ "cannot allocate tree node");
+ ret = false;
+ break;
+ }
+ node->obj = elt;
+ TREE_INSERT (&tree, ucl_compare_node, link, node);
+ LL_PREPEND (nodes, node);
+ }
+
+ LL_FOREACH_SAFE (nodes, node, tmp) {
+ free (node);
+ }
+
+ return ret;
+}
+
+static bool
+ucl_schema_validate_array (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err,
+ const ucl_object_t *root)
+{
+ const ucl_object_t *elt, *it, *found, *additional_schema = NULL,
+ *first_unvalidated = NULL;
+ ucl_object_iter_t iter = NULL, piter = NULL;
+ bool ret = true, allow_additional = true, need_unique = false;
+ int64_t minmax;
+ unsigned int idx = 0;
+
+ while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) {
+ if (strcmp (ucl_object_key (elt), "items") == 0) {
+ if (elt->type == UCL_ARRAY) {
+ found = ucl_array_head (obj);
+ while (ret && (it = ucl_iterate_object (elt, &piter, true)) != NULL) {
+ if (found) {
+ ret = ucl_schema_validate (it, found, false, err, root);
+ found = ucl_array_find_index (obj, ++idx);
+ }
+ }
+ if (found != NULL) {
+ /* The first element that is not validated */
+ first_unvalidated = found;
+ }
+ }
+ else if (elt->type == UCL_OBJECT) {
+ /* Validate all items using the specified schema */
+ while (ret && (it = ucl_iterate_object (obj, &piter, true)) != NULL) {
+ ret = ucl_schema_validate (elt, it, false, err, root);
+ }
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "items attribute is invalid in schema");
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) {
+ if (elt->type == UCL_BOOLEAN) {
+ if (!ucl_object_toboolean (elt)) {
+ /* Deny additional fields completely */
+ allow_additional = false;
+ }
+ }
+ else if (elt->type == UCL_OBJECT) {
+ /* Define validator for additional fields */
+ additional_schema = elt;
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
+ "additionalItems attribute is invalid in schema");
+ ret = false;
+ break;
+ }
+ }
+ else if (elt->type == UCL_BOOLEAN &&
+ strcmp (ucl_object_key (elt), "uniqueItems") == 0) {
+ need_unique = ucl_object_toboolean (elt);
+ }
+ else if (strcmp (ucl_object_key (elt), "minItems") == 0
+ && ucl_object_toint_safe (elt, &minmax)) {
+ if (obj->len < minmax) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "array has not enough items: %u, minimum is: %u",
+ obj->len, (unsigned)minmax);
+ ret = false;
+ break;
+ }
+ }
+ else if (strcmp (ucl_object_key (elt), "maxItems") == 0
+ && ucl_object_toint_safe (elt, &minmax)) {
+ if (obj->len > minmax) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "array has too many items: %u, maximum is: %u",
+ obj->len, (unsigned)minmax);
+ ret = false;
+ break;
+ }
+ }
+ }
+
+ if (ret) {
+ /* Additional properties */
+ if (!allow_additional || additional_schema != NULL) {
+ if (first_unvalidated != NULL) {
+ if (!allow_additional) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "array has undefined item");
+ ret = false;
+ }
+ else if (additional_schema != NULL) {
+ elt = ucl_array_find_index (obj, idx);
+ while (elt) {
+ if (!ucl_schema_validate (additional_schema, elt, false,
+ err, root)) {
+ ret = false;
+ break;
+ }
+ elt = ucl_array_find_index (obj, idx ++);
+ }
+ }
+ }
+ }
+ /* Required properties */
+ if (ret && need_unique) {
+ ret = ucl_schema_array_is_unique (obj, err);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Returns whether this object is allowed for this type
+ */
+static bool
+ucl_schema_type_is_allowed (const ucl_object_t *type, const ucl_object_t *obj,
+ struct ucl_schema_error *err)
+{
+ ucl_object_iter_t iter = NULL;
+ const ucl_object_t *elt;
+ const char *type_str;
+ ucl_type_t t;
+
+ if (type == NULL) {
+ /* Any type is allowed */
+ return true;
+ }
+
+ if (type->type == UCL_ARRAY) {
+ /* One of allowed types */
+ while ((elt = ucl_iterate_object (type, &iter, true)) != NULL) {
+ if (ucl_schema_type_is_allowed (elt, obj, err)) {
+ return true;
+ }
+ }
+ }
+ else if (type->type == UCL_STRING) {
+ type_str = ucl_object_tostring (type);
+ if (!ucl_string_to_type (type_str, &t)) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, type,
+ "Type attribute is invalid in schema");
+ return false;
+ }
+ if (obj->type != t) {
+ /* Some types are actually compatible */
+ if (obj->type == UCL_TIME && t == UCL_FLOAT) {
+ return true;
+ }
+ else if (obj->type == UCL_INT && t == UCL_FLOAT) {
+ return true;
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_TYPE_MISMATCH, obj,
+ "Invalid type of %s, expected %s",
+ ucl_object_type_to_string (obj->type),
+ ucl_object_type_to_string (t));
+ }
+ }
+ else {
+ /* Types are equal */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Check if object is equal to one of elements of enum
+ */
+static bool
+ucl_schema_validate_enum (const ucl_object_t *en, const ucl_object_t *obj,
+ struct ucl_schema_error *err)
+{
+ ucl_object_iter_t iter = NULL;
+ const ucl_object_t *elt;
+ bool ret = false;
+
+ while ((elt = ucl_iterate_object (en, &iter, true)) != NULL) {
+ if (ucl_object_compare (elt, obj) == 0) {
+ ret = true;
+ break;
+ }
+ }
+
+ if (!ret) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object is not one of enumerated patterns");
+ }
+
+ return ret;
+}
+
+
+/*
+ * Check a single ref component
+ */
+static const ucl_object_t *
+ucl_schema_resolve_ref_component (const ucl_object_t *cur,
+ const char *refc, int len,
+ struct ucl_schema_error *err)
+{
+ const ucl_object_t *res = NULL;
+ char *err_str;
+ int num, i;
+
+ if (cur->type == UCL_OBJECT) {
+ /* Find a key inside an object */
+ res = ucl_object_find_keyl (cur, refc, len);
+ if (res == NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
+ "reference %s is invalid, missing path component", refc);
+ return NULL;
+ }
+ }
+ else if (cur->type == UCL_ARRAY) {
+ /* We must figure out a number inside array */
+ num = strtoul (refc, &err_str, 10);
+ if (err_str != NULL && *err_str != '/' && *err_str != '\0') {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
+ "reference %s is invalid, invalid item number", refc);
+ return NULL;
+ }
+ res = ucl_array_head (cur);
+ i = 0;
+ while (res != NULL) {
+ if (i == num) {
+ break;
+ }
+ res = res->next;
+ }
+ if (res == NULL) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
+ "reference %s is invalid, item number %d does not exist",
+ refc, num);
+ return NULL;
+ }
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
+ "reference %s is invalid, contains primitive object in the path",
+ refc);
+ return NULL;
+ }
+
+ return res;
+}
+/*
+ * Find reference schema
+ */
+static const ucl_object_t *
+ucl_schema_resolve_ref (const ucl_object_t *root, const char *ref,
+ struct ucl_schema_error *err)
+{
+ const char *p, *c;
+ const ucl_object_t *res = NULL;
+
+
+ if (ref[0] != '#') {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root,
+ "reference %s is invalid, not started with #", ref);
+ return NULL;
+ }
+ if (ref[1] == '/') {
+ p = &ref[2];
+ }
+ else if (ref[1] == '\0') {
+ return root;
+ }
+ else {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root,
+ "reference %s is invalid, not started with #/", ref);
+ return NULL;
+ }
+
+ c = p;
+ res = root;
+
+ while (*p != '\0') {
+ if (*p == '/') {
+ if (p - c == 0) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
+ "reference %s is invalid, empty path component", ref);
+ return NULL;
+ }
+ /* Now we have some url part, so we need to figure out where we are */
+ res = ucl_schema_resolve_ref_component (res, c, p - c, err);
+ if (res == NULL) {
+ return NULL;
+ }
+ c = p + 1;
+ }
+ p ++;
+ }
+
+ if (p - c != 0) {
+ res = ucl_schema_resolve_ref_component (res, c, p - c, err);
+ }
+
+ if (res == NULL || res->type != UCL_OBJECT) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
+ "reference %s is invalid, cannot find specified object",
+ ref);
+ return NULL;
+ }
+
+ return res;
+}
+
+static bool
+ucl_schema_validate_values (const ucl_object_t *schema, const ucl_object_t *obj,
+ struct ucl_schema_error *err)
+{
+ const ucl_object_t *elt, *cur;
+ int64_t constraint, i;
+
+ elt = ucl_object_find_key (schema, "maxValues");
+ if (elt != NULL && elt->type == UCL_INT) {
+ constraint = ucl_object_toint (elt);
+ cur = obj;
+ i = 0;
+ while (cur) {
+ if (i > constraint) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object has more values than defined: %ld",
+ (long int)constraint);
+ return false;
+ }
+ i ++;
+ cur = cur->next;
+ }
+ }
+ elt = ucl_object_find_key (schema, "minValues");
+ if (elt != NULL && elt->type == UCL_INT) {
+ constraint = ucl_object_toint (elt);
+ cur = obj;
+ i = 0;
+ while (cur) {
+ if (i >= constraint) {
+ break;
+ }
+ i ++;
+ cur = cur->next;
+ }
+ if (i < constraint) {
+ ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
+ "object has less values than defined: %ld",
+ (long int)constraint);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+ucl_schema_validate (const ucl_object_t *schema,
+ const ucl_object_t *obj, bool try_array,
+ struct ucl_schema_error *err,
+ const ucl_object_t *root)
+{
+ const ucl_object_t *elt, *cur;
+ ucl_object_iter_t iter = NULL;
+ bool ret;
+
+ if (schema->type != UCL_OBJECT) {
+ ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, schema,
+ "schema is %s instead of object", ucl_object_type_to_string (schema->type));
+ return false;
+ }
+
+ if (try_array) {
+ /*
+ * Special case for multiple values
+ */
+ if (!ucl_schema_validate_values (schema, obj, err)) {
+ return false;
+ }
+ LL_FOREACH (obj, cur) {
+ if (!ucl_schema_validate (schema, cur, false, err, root)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ elt = ucl_object_find_key (schema, "enum");
+ if (elt != NULL && elt->type == UCL_ARRAY) {
+ if (!ucl_schema_validate_enum (elt, obj, err)) {
+ return false;
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "allOf");
+ if (elt != NULL && elt->type == UCL_ARRAY) {
+ iter = NULL;
+ while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) {
+ ret = ucl_schema_validate (cur, obj, true, err, root);
+ if (!ret) {
+ return false;
+ }
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "anyOf");
+ if (elt != NULL && elt->type == UCL_ARRAY) {
+ iter = NULL;
+ while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) {
+ ret = ucl_schema_validate (cur, obj, true, err, root);
+ if (ret) {
+ break;
+ }
+ }
+ if (!ret) {
+ return false;
+ }
+ else {
+ /* Reset error */
+ err->code = UCL_SCHEMA_OK;
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "oneOf");
+ if (elt != NULL && elt->type == UCL_ARRAY) {
+ iter = NULL;
+ ret = false;
+ while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) {
+ if (!ret) {
+ ret = ucl_schema_validate (cur, obj, true, err, root);
+ }
+ else if (ucl_schema_validate (cur, obj, true, err, root)) {
+ ret = false;
+ break;
+ }
+ }
+ if (!ret) {
+ return false;
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "not");
+ if (elt != NULL && elt->type == UCL_OBJECT) {
+ if (ucl_schema_validate (elt, obj, true, err, root)) {
+ return false;
+ }
+ else {
+ /* Reset error */
+ err->code = UCL_SCHEMA_OK;
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "$ref");
+ if (elt != NULL) {
+ cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt), err);
+ if (cur == NULL) {
+ return false;
+ }
+ if (!ucl_schema_validate (cur, obj, try_array, err, root)) {
+ return false;
+ }
+ }
+
+ elt = ucl_object_find_key (schema, "type");
+ if (!ucl_schema_type_is_allowed (elt, obj, err)) {
+ return false;
+ }
+
+ switch (obj->type) {
+ case UCL_OBJECT:
+ return ucl_schema_validate_object (schema, obj, err, root);
+ break;
+ case UCL_ARRAY:
+ return ucl_schema_validate_array (schema, obj, err, root);
+ break;
+ case UCL_INT:
+ case UCL_FLOAT:
+ return ucl_schema_validate_number (schema, obj, err);
+ break;
+ case UCL_STRING:
+ return ucl_schema_validate_string (schema, obj, err);
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool
+ucl_object_validate (const ucl_object_t *schema,
+ const ucl_object_t *obj, struct ucl_schema_error *err)
+{
+ return ucl_schema_validate (schema, obj, true, err, schema);
+}
diff --git a/contrib/libucl/ucl_util.c b/contrib/libucl/ucl_util.c
new file mode 100644
index 000000000..41e012bf1
--- /dev/null
+++ b/contrib/libucl/ucl_util.c
@@ -0,0 +1,2568 @@
+/* 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"
+#include "kvec.h"
+
+#ifndef _WIN32
+#include <glob.h>
+#endif
+
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h> /* For dirname */
+#endif
+
+typedef kvec_t(ucl_object_t *) ucl_array_t;
+
+#define UCL_ARRAY_GET(ar, obj) ucl_array_t *ar = \
+ (ucl_array_t *)((obj) != NULL ? (obj)->value.av : NULL)
+
+#ifdef HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#endif
+
+#ifdef CURL_FOUND
+#include <curl/curl.h>
+#endif
+#ifdef HAVE_FETCH_H
+#include <fetch.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+
+#ifndef PROT_READ
+#define PROT_READ 1
+#endif
+#ifndef PROT_WRITE
+#define PROT_WRITE 2
+#endif
+#ifndef PROT_READWRITE
+#define PROT_READWRITE 3
+#endif
+#ifndef MAP_SHARED
+#define MAP_SHARED 1
+#endif
+#ifndef MAP_PRIVATE
+#define MAP_PRIVATE 2
+#endif
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *) -1)
+#endif
+
+#ifdef _WIN32
+#include <limits.h>
+#define NBBY CHAR_BIT
+#endif
+
+static void *ucl_mmap(char *addr, size_t length, int prot, int access, int fd, off_t offset)
+{
+ void *map = NULL;
+ HANDLE handle = INVALID_HANDLE_VALUE;
+
+ switch (prot) {
+ default:
+ case PROT_READ:
+ {
+ handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READONLY, 0, length, 0);
+ if (!handle) break;
+ map = (void *) MapViewOfFile(handle, FILE_MAP_READ, 0, 0, length);
+ CloseHandle(handle);
+ break;
+ }
+ case PROT_WRITE:
+ {
+ handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READWRITE, 0, length, 0);
+ if (!handle) break;
+ map = (void *) MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, length);
+ CloseHandle(handle);
+ break;
+ }
+ case PROT_READWRITE:
+ {
+ handle = CreateFileMapping((HANDLE) _get_osfhandle(fd), 0, PAGE_READWRITE, 0, length, 0);
+ if (!handle) break;
+ map = (void *) MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, length);
+ CloseHandle(handle);
+ break;
+ }
+ }
+ if (map == (void *) NULL) {
+ return (void *) MAP_FAILED;
+ }
+ return (void *) ((char *) map + offset);
+}
+
+static int ucl_munmap(void *map,size_t length)
+{
+ if (!UnmapViewOfFile(map)) {
+ return(-1);
+ }
+ return(0);
+}
+
+static char* ucl_realpath(const char *path, char *resolved_path) {
+ char *p;
+ char tmp[MAX_PATH + 1];
+ strncpy(tmp, path, sizeof(tmp)-1);
+ p = tmp;
+ while(*p) {
+ if (*p == '/') *p = '\\';
+ p++;
+ }
+ return _fullpath(resolved_path, tmp, MAX_PATH);
+}
+#else
+#define ucl_mmap mmap
+#define ucl_munmap munmap
+#define ucl_realpath realpath
+#endif
+
+typedef void (*ucl_object_dtor) (ucl_object_t *obj);
+static void ucl_object_free_internal (ucl_object_t *obj, bool allow_rec,
+ ucl_object_dtor dtor);
+static void ucl_object_dtor_unref (ucl_object_t *obj);
+
+static void
+ucl_object_dtor_free (ucl_object_t *obj)
+{
+ if (obj->trash_stack[UCL_TRASH_KEY] != NULL) {
+ UCL_FREE (obj->hh.keylen, obj->trash_stack[UCL_TRASH_KEY]);
+ }
+ if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ UCL_FREE (obj->len, obj->trash_stack[UCL_TRASH_VALUE]);
+ }
+ /* Do not free ephemeral objects */
+ if ((obj->flags & UCL_OBJECT_EPHEMERAL) == 0) {
+ if (obj->type != UCL_USERDATA) {
+ UCL_FREE (sizeof (ucl_object_t), obj);
+ }
+ else {
+ struct ucl_object_userdata *ud = (struct ucl_object_userdata *)obj;
+ if (ud->dtor) {
+ ud->dtor (obj->value.ud);
+ }
+ UCL_FREE (sizeof (*ud), obj);
+ }
+ }
+}
+
+/*
+ * This is a helper function that performs exactly the same as
+ * `ucl_object_unref` but it doesn't iterate over elements allowing
+ * to use it for individual elements of arrays and multiple values
+ */
+static void
+ucl_object_dtor_unref_single (ucl_object_t *obj)
+{
+ if (obj != NULL) {
+#ifdef HAVE_ATOMIC_BUILTINS
+ unsigned int rc = __sync_sub_and_fetch (&obj->ref, 1);
+ if (rc == 0) {
+#else
+ if (--obj->ref == 0) {
+#endif
+ ucl_object_free_internal (obj, false, ucl_object_dtor_unref);
+ }
+ }
+}
+
+static void
+ucl_object_dtor_unref (ucl_object_t *obj)
+{
+ if (obj->ref == 0) {
+ ucl_object_dtor_free (obj);
+ }
+ else {
+ /* This may cause dtor unref being called one more time */
+ ucl_object_dtor_unref_single (obj);
+ }
+}
+
+static void
+ucl_object_free_internal (ucl_object_t *obj, bool allow_rec, ucl_object_dtor dtor)
+{
+ ucl_object_t *tmp, *sub;
+
+ while (obj != NULL) {
+ if (obj->type == UCL_ARRAY) {
+ UCL_ARRAY_GET (vec, obj);
+ unsigned int i;
+
+ if (vec != NULL) {
+ for (i = 0; i < vec->n; i ++) {
+ sub = kv_A (*vec, i);
+ if (sub != NULL) {
+ tmp = sub;
+ while (sub) {
+ tmp = sub->next;
+ dtor (sub);
+ sub = tmp;
+ }
+ }
+ }
+ kv_destroy (*vec);
+ UCL_FREE (sizeof (*vec), vec);
+ }
+ }
+ else if (obj->type == UCL_OBJECT) {
+ if (obj->value.ov != NULL) {
+ ucl_hash_destroy (obj->value.ov, (ucl_hash_free_func *)dtor);
+ }
+ }
+ tmp = obj->next;
+ dtor (obj);
+ obj = tmp;
+
+ if (!allow_rec) {
+ break;
+ }
+ }
+}
+
+void
+ucl_object_free (ucl_object_t *obj)
+{
+ ucl_object_free_internal (obj, true, ucl_object_dtor_free);
+}
+
+size_t
+ucl_unescape_json_string (char *str, size_t len)
+{
+ char *t = str, *h = str;
+ int i, uval;
+
+ if (len <= 1) {
+ return len;
+ }
+ /* t is target (tortoise), h is source (hare) */
+
+ while (len) {
+ if (*h == '\\') {
+ h ++;
+ switch (*h) {
+ case 'n':
+ *t++ = '\n';
+ break;
+ case 'r':
+ *t++ = '\r';
+ break;
+ case 'b':
+ *t++ = '\b';
+ break;
+ case 't':
+ *t++ = '\t';
+ break;
+ case 'f':
+ *t++ = '\f';
+ break;
+ case '\\':
+ *t++ = '\\';
+ break;
+ case '"':
+ *t++ = '"';
+ break;
+ case 'u':
+ /* Unicode escape */
+ uval = 0;
+ if (len > 3) {
+ for (i = 0; i < 4; i++) {
+ uval <<= 4;
+ if (isdigit (h[i])) {
+ uval += h[i] - '0';
+ }
+ else if (h[i] >= 'a' && h[i] <= 'f') {
+ uval += h[i] - 'a' + 10;
+ }
+ else if (h[i] >= 'A' && h[i] <= 'F') {
+ uval += h[i] - 'A' + 10;
+ }
+ else {
+ break;
+ }
+ }
+ h += 3;
+ len -= 3;
+ /* Encode */
+ if(uval < 0x80) {
+ t[0] = (char)uval;
+ t ++;
+ }
+ else if(uval < 0x800) {
+ t[0] = 0xC0 + ((uval & 0x7C0) >> 6);
+ t[1] = 0x80 + ((uval & 0x03F));
+ t += 2;
+ }
+ else if(uval < 0x10000) {
+ t[0] = 0xE0 + ((uval & 0xF000) >> 12);
+ t[1] = 0x80 + ((uval & 0x0FC0) >> 6);
+ t[2] = 0x80 + ((uval & 0x003F));
+ t += 3;
+ }
+ else if(uval <= 0x10FFFF) {
+ t[0] = 0xF0 + ((uval & 0x1C0000) >> 18);
+ t[1] = 0x80 + ((uval & 0x03F000) >> 12);
+ t[2] = 0x80 + ((uval & 0x000FC0) >> 6);
+ t[3] = 0x80 + ((uval & 0x00003F));
+ t += 4;
+ }
+ else {
+ *t++ = '?';
+ }
+ }
+ else {
+ *t++ = 'u';
+ }
+ break;
+ default:
+ *t++ = *h;
+ break;
+ }
+ h ++;
+ len --;
+ }
+ else {
+ *t++ = *h++;
+ }
+ len --;
+ }
+ *t = '\0';
+
+ return (t - str);
+}
+
+char *
+ucl_copy_key_trash (const ucl_object_t *obj)
+{
+ ucl_object_t *deconst;
+
+ if (obj == NULL) {
+ return NULL;
+ }
+ if (obj->trash_stack[UCL_TRASH_KEY] == NULL && obj->key != NULL) {
+ deconst = __DECONST (ucl_object_t *, obj);
+ deconst->trash_stack[UCL_TRASH_KEY] = malloc (obj->keylen + 1);
+ if (deconst->trash_stack[UCL_TRASH_KEY] != NULL) {
+ memcpy (deconst->trash_stack[UCL_TRASH_KEY], obj->key, obj->keylen);
+ deconst->trash_stack[UCL_TRASH_KEY][obj->keylen] = '\0';
+ }
+ deconst->key = obj->trash_stack[UCL_TRASH_KEY];
+ deconst->flags |= UCL_OBJECT_ALLOCATED_KEY;
+ }
+
+ return obj->trash_stack[UCL_TRASH_KEY];
+}
+
+char *
+ucl_copy_value_trash (const ucl_object_t *obj)
+{
+ ucl_object_t *deconst;
+
+ if (obj == NULL) {
+ return NULL;
+ }
+ if (obj->trash_stack[UCL_TRASH_VALUE] == NULL) {
+ deconst = __DECONST (ucl_object_t *, obj);
+ if (obj->type == UCL_STRING) {
+
+ /* Special case for strings */
+ deconst->trash_stack[UCL_TRASH_VALUE] = malloc (obj->len + 1);
+ if (deconst->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ memcpy (deconst->trash_stack[UCL_TRASH_VALUE], obj->value.sv, obj->len);
+ deconst->trash_stack[UCL_TRASH_VALUE][obj->len] = '\0';
+ deconst->value.sv = obj->trash_stack[UCL_TRASH_VALUE];
+ }
+ }
+ else {
+ /* Just emit value in json notation */
+ deconst->trash_stack[UCL_TRASH_VALUE] = ucl_object_emit_single_json (obj);
+ deconst->len = strlen (obj->trash_stack[UCL_TRASH_VALUE]);
+ }
+ deconst->flags |= UCL_OBJECT_ALLOCATED_VALUE;
+ }
+ return obj->trash_stack[UCL_TRASH_VALUE];
+}
+
+UCL_EXTERN ucl_object_t*
+ucl_parser_get_object (struct ucl_parser *parser)
+{
+ if (parser->state != UCL_STATE_ERROR && parser->top_obj != NULL) {
+ return ucl_object_ref (parser->top_obj);
+ }
+
+ return NULL;
+}
+
+UCL_EXTERN void
+ucl_parser_free (struct ucl_parser *parser)
+{
+ struct ucl_stack *stack, *stmp;
+ struct ucl_macro *macro, *mtmp;
+ struct ucl_chunk *chunk, *ctmp;
+ struct ucl_pubkey *key, *ktmp;
+ struct ucl_variable *var, *vtmp;
+
+ if (parser == NULL) {
+ return;
+ }
+
+ if (parser->top_obj != NULL) {
+ ucl_object_unref (parser->top_obj);
+ }
+
+ LL_FOREACH_SAFE (parser->stack, stack, stmp) {
+ free (stack);
+ }
+ HASH_ITER (hh, parser->macroes, macro, mtmp) {
+ free (macro->name);
+ HASH_DEL (parser->macroes, macro);
+ UCL_FREE (sizeof (struct ucl_macro), macro);
+ }
+ LL_FOREACH_SAFE (parser->chunks, chunk, ctmp) {
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ }
+ LL_FOREACH_SAFE (parser->keys, key, ktmp) {
+ UCL_FREE (sizeof (struct ucl_pubkey), key);
+ }
+ LL_FOREACH_SAFE (parser->variables, var, vtmp) {
+ free (var->value);
+ free (var->var);
+ UCL_FREE (sizeof (struct ucl_variable), var);
+ }
+
+ if (parser->err != NULL) {
+ utstring_free (parser->err);
+ }
+
+ if (parser->cur_file) {
+ free (parser->cur_file);
+ }
+
+ UCL_FREE (sizeof (struct ucl_parser), parser);
+}
+
+UCL_EXTERN const char *
+ucl_parser_get_error(struct ucl_parser *parser)
+{
+ if (parser == NULL) {
+ return NULL;
+ }
+
+ if (parser->err == NULL)
+ return NULL;
+
+ return utstring_body(parser->err);
+}
+
+UCL_EXTERN void
+ucl_parser_clear_error(struct ucl_parser *parser)
+{
+ if (parser != NULL && parser->err != NULL) {
+ utstring_free(parser->err);
+ parser->err = NULL;
+ }
+}
+
+UCL_EXTERN bool
+ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len)
+{
+#ifndef HAVE_OPENSSL
+ ucl_create_err (&parser->err, "cannot check signatures without openssl");
+ return false;
+#else
+# if (OPENSSL_VERSION_NUMBER < 0x10000000L)
+ ucl_create_err (&parser->err, "cannot check signatures, openssl version is unsupported");
+ return EXIT_FAILURE;
+# else
+ struct ucl_pubkey *nkey;
+ BIO *mem;
+
+ mem = BIO_new_mem_buf ((void *)key, len);
+ nkey = UCL_ALLOC (sizeof (struct ucl_pubkey));
+ if (nkey == NULL) {
+ ucl_create_err (&parser->err, "cannot allocate memory for key");
+ return false;
+ }
+ nkey->key = PEM_read_bio_PUBKEY (mem, &nkey->key, NULL, NULL);
+ BIO_free (mem);
+ if (nkey->key == NULL) {
+ UCL_FREE (sizeof (struct ucl_pubkey), nkey);
+ ucl_create_err (&parser->err, "%s",
+ ERR_error_string (ERR_get_error (), NULL));
+ return false;
+ }
+ LL_PREPEND (parser->keys, nkey);
+# endif
+#endif
+ return true;
+}
+
+#ifdef CURL_FOUND
+struct ucl_curl_cbdata {
+ unsigned char *buf;
+ size_t buflen;
+};
+
+static size_t
+ucl_curl_write_callback (void* contents, size_t size, size_t nmemb, void* ud)
+{
+ struct ucl_curl_cbdata *cbdata = ud;
+ size_t realsize = size * nmemb;
+
+ cbdata->buf = realloc (cbdata->buf, cbdata->buflen + realsize + 1);
+ if (cbdata->buf == NULL) {
+ return 0;
+ }
+
+ memcpy (&(cbdata->buf[cbdata->buflen]), contents, realsize);
+ cbdata->buflen += realsize;
+ cbdata->buf[cbdata->buflen] = 0;
+
+ return realsize;
+}
+#endif
+
+/**
+ * Fetch a url and save results to the memory buffer
+ * @param url url to fetch
+ * @param len length of url
+ * @param buf target buffer
+ * @param buflen target length
+ * @return
+ */
+static bool
+ucl_fetch_url (const unsigned char *url, unsigned char **buf, size_t *buflen,
+ UT_string **err, bool must_exist)
+{
+
+#ifdef HAVE_FETCH_H
+ struct url *fetch_url;
+ struct url_stat us;
+ FILE *in;
+
+ fetch_url = fetchParseURL (url);
+ if (fetch_url == NULL) {
+ ucl_create_err (err, "invalid URL %s: %s",
+ url, strerror (errno));
+ return false;
+ }
+ if ((in = fetchXGet (fetch_url, &us, "")) == NULL) {
+ if (!must_exist) {
+ ucl_create_err (err, "cannot fetch URL %s: %s",
+ url, strerror (errno));
+ }
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ *buflen = us.size;
+ *buf = malloc (*buflen);
+ if (*buf == NULL) {
+ ucl_create_err (err, "cannot allocate buffer for URL %s: %s",
+ url, strerror (errno));
+ fclose (in);
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ if (fread (*buf, *buflen, 1, in) != 1) {
+ ucl_create_err (err, "cannot read URL %s: %s",
+ url, strerror (errno));
+ fclose (in);
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ fetchFreeURL (fetch_url);
+ return true;
+#elif defined(CURL_FOUND)
+ CURL *curl;
+ int r;
+ struct ucl_curl_cbdata cbdata;
+
+ curl = curl_easy_init ();
+ if (curl == NULL) {
+ ucl_create_err (err, "CURL interface is broken");
+ return false;
+ }
+ if ((r = curl_easy_setopt (curl, CURLOPT_URL, url)) != CURLE_OK) {
+ ucl_create_err (err, "invalid URL %s: %s",
+ url, curl_easy_strerror (r));
+ curl_easy_cleanup (curl);
+ return false;
+ }
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ucl_curl_write_callback);
+ cbdata.buf = *buf;
+ cbdata.buflen = *buflen;
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbdata);
+
+ if ((r = curl_easy_perform (curl)) != CURLE_OK) {
+ if (!must_exist) {
+ ucl_create_err (err, "error fetching URL %s: %s",
+ url, curl_easy_strerror (r));
+ }
+ curl_easy_cleanup (curl);
+ if (cbdata.buf) {
+ free (cbdata.buf);
+ }
+ return false;
+ }
+ *buf = cbdata.buf;
+ *buflen = cbdata.buflen;
+
+ return true;
+#else
+ ucl_create_err (err, "URL support is disabled");
+ return false;
+#endif
+}
+
+/**
+ * Fetch a file and save results to the memory buffer
+ * @param filename filename to fetch
+ * @param len length of filename
+ * @param buf target buffer
+ * @param buflen target length
+ * @return
+ */
+static bool
+ucl_fetch_file (const unsigned char *filename, unsigned char **buf, size_t *buflen,
+ UT_string **err, bool must_exist)
+{
+ int fd;
+ struct stat st;
+
+ if (stat (filename, &st) == -1 || !S_ISREG (st.st_mode)) {
+ if (must_exist) {
+ ucl_create_err (err, "cannot stat file %s: %s",
+ filename, strerror (errno));
+ }
+ return false;
+ }
+ if (st.st_size == 0) {
+ /* Do not map empty files */
+ *buf = "";
+ *buflen = 0;
+ }
+ else {
+ if ((fd = open (filename, O_RDONLY)) == -1) {
+ ucl_create_err (err, "cannot open file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ if ((*buf = ucl_mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ close (fd);
+ ucl_create_err (err, "cannot mmap file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ *buflen = st.st_size;
+ close (fd);
+ }
+
+ return true;
+}
+
+
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+static inline bool
+ucl_sig_check (const unsigned char *data, size_t datalen,
+ const unsigned char *sig, size_t siglen, struct ucl_parser *parser)
+{
+ struct ucl_pubkey *key;
+ char dig[EVP_MAX_MD_SIZE];
+ unsigned int diglen;
+ EVP_PKEY_CTX *key_ctx;
+ EVP_MD_CTX *sign_ctx = NULL;
+
+ sign_ctx = EVP_MD_CTX_create ();
+
+ LL_FOREACH (parser->keys, key) {
+ key_ctx = EVP_PKEY_CTX_new (key->key, NULL);
+ if (key_ctx != NULL) {
+ if (EVP_PKEY_verify_init (key_ctx) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding (key_ctx, RSA_PKCS1_PADDING) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ if (EVP_PKEY_CTX_set_signature_md (key_ctx, EVP_sha256 ()) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ EVP_DigestInit (sign_ctx, EVP_sha256 ());
+ EVP_DigestUpdate (sign_ctx, data, datalen);
+ EVP_DigestFinal (sign_ctx, dig, &diglen);
+
+ if (EVP_PKEY_verify (key_ctx, sig, siglen, dig, diglen) == 1) {
+ EVP_MD_CTX_destroy (sign_ctx);
+ EVP_PKEY_CTX_free (key_ctx);
+ return true;
+ }
+
+ EVP_PKEY_CTX_free (key_ctx);
+ }
+ }
+
+ EVP_MD_CTX_destroy (sign_ctx);
+
+ return false;
+}
+#endif
+
+/**
+ * Include an url to configuration
+ * @param data
+ * @param len
+ * @param parser
+ * @param err
+ * @return
+ */
+static bool
+ucl_include_url (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ unsigned priority)
+{
+
+ bool res;
+ unsigned char *buf = NULL;
+ size_t buflen = 0;
+ struct ucl_chunk *chunk;
+ char urlbuf[PATH_MAX];
+ int prev_state;
+
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data);
+
+ if (!ucl_fetch_url (urlbuf, &buf, &buflen, &parser->err, must_exist)) {
+ return (!must_exist || false);
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ unsigned char *sigbuf = NULL;
+ size_t siglen = 0;
+ /* We need to check signature first */
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s.sig", (int)len, data);
+ if (!ucl_fetch_url (urlbuf, &sigbuf, &siglen, &parser->err, true)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (&parser->err, "cannot verify url %s: %s",
+ urlbuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ if (siglen > 0) {
+ ucl_munmap (sigbuf, siglen);
+ }
+ return false;
+ }
+ if (siglen > 0) {
+ ucl_munmap (sigbuf, siglen);
+ }
+#endif
+ }
+
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
+ res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
+ if (res == true) {
+ /* Remove chunk from the stack */
+ chunk = parser->chunks;
+ if (chunk != NULL) {
+ parser->chunks = chunk->next;
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ }
+ }
+
+ parser->state = prev_state;
+ free (buf);
+
+ return res;
+}
+
+/**
+ * Include a single file to the parser
+ * @param data
+ * @param len
+ * @param parser
+ * @param check_signature
+ * @param must_exist
+ * @param allow_glob
+ * @param priority
+ * @return
+ */
+static bool
+ucl_include_file_single (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ unsigned priority)
+{
+ bool res;
+ struct ucl_chunk *chunk;
+ unsigned char *buf = NULL;
+ char *old_curfile;
+ size_t buflen;
+ char filebuf[PATH_MAX], realbuf[PATH_MAX];
+ int prev_state;
+ struct ucl_variable *cur_var, *tmp_var, *old_curdir = NULL,
+ *old_filename = NULL;
+
+ snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
+ if (ucl_realpath (filebuf, realbuf) == NULL) {
+ if (!must_exist) {
+ return true;
+ }
+ ucl_create_err (&parser->err, "cannot open file %s: %s",
+ filebuf,
+ strerror (errno));
+ return false;
+ }
+
+ if (parser->cur_file && strcmp (realbuf, parser->cur_file) == 0) {
+ /* We are likely including the file itself */
+ ucl_create_err (&parser->err, "trying to include the file %s from itself",
+ realbuf);
+ return false;
+ }
+
+ if (!ucl_fetch_file (realbuf, &buf, &buflen, &parser->err, must_exist)) {
+ return (!must_exist || false);
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ unsigned char *sigbuf = NULL;
+ size_t siglen = 0;
+ /* We need to check signature first */
+ snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf);
+ if (!ucl_fetch_file (filebuf, &sigbuf, &siglen, &parser->err, true)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (&parser->err, "cannot verify file %s: %s",
+ filebuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ if (siglen > 0) {
+ ucl_munmap (sigbuf, siglen);
+ }
+ return false;
+ }
+ if (siglen > 0) {
+ ucl_munmap (sigbuf, siglen);
+ }
+#endif
+ }
+
+ old_curfile = parser->cur_file;
+ parser->cur_file = strdup (realbuf);
+
+ /* Store old file vars */
+ DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
+ if (strcmp (cur_var->var, "CURDIR") == 0) {
+ old_curdir = cur_var;
+ DL_DELETE (parser->variables, cur_var);
+ }
+ else if (strcmp (cur_var->var, "FILENAME") == 0) {
+ old_filename = cur_var;
+ DL_DELETE (parser->variables, cur_var);
+ }
+ }
+
+ ucl_parser_set_filevars (parser, realbuf, false);
+
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
+ res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
+ if (!res && !must_exist) {
+ /* Free error */
+ utstring_free (parser->err);
+ parser->err = NULL;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ }
+
+ /* Remove chunk from the stack */
+ chunk = parser->chunks;
+ if (chunk != NULL) {
+ parser->chunks = chunk->next;
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ parser->recursion --;
+ }
+
+ /* Restore old file vars */
+ parser->cur_file = old_curfile;
+ DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
+ if (strcmp (cur_var->var, "CURDIR") == 0 && old_curdir) {
+ DL_DELETE (parser->variables, cur_var);
+ free (cur_var->var);
+ free (cur_var->value);
+ UCL_FREE (sizeof (struct ucl_variable), cur_var);
+ }
+ else if (strcmp (cur_var->var, "FILENAME") == 0 && old_filename) {
+ DL_DELETE (parser->variables, cur_var);
+ free (cur_var->var);
+ free (cur_var->value);
+ UCL_FREE (sizeof (struct ucl_variable), cur_var);
+ }
+ }
+ if (old_filename) {
+ DL_APPEND (parser->variables, old_filename);
+ }
+ if (old_curdir) {
+ DL_APPEND (parser->variables, old_curdir);
+ }
+ if (old_curfile) {
+ free (old_curfile);
+ }
+
+ parser->state = prev_state;
+
+ if (buflen > 0) {
+ ucl_munmap (buf, buflen);
+ }
+
+ return res;
+}
+
+/**
+ * Include a file to configuration
+ * @param data
+ * @param len
+ * @param parser
+ * @param err
+ * @return
+ */
+static bool
+ucl_include_file (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ bool allow_glob, unsigned priority)
+{
+ const unsigned char *p = data, *end = data + len;
+ bool need_glob = false;
+ int cnt = 0;
+ char glob_pattern[PATH_MAX];
+ size_t i;
+
+#ifndef _WIN32
+ if (!allow_glob) {
+ return ucl_include_file_single (data, len, parser, check_signature,
+ must_exist, priority);
+ }
+ else {
+ /* Check for special symbols in a filename */
+ while (p != end) {
+ if (*p == '*' || *p == '?') {
+ need_glob = true;
+ break;
+ }
+ p ++;
+ }
+ if (need_glob) {
+ glob_t globbuf;
+ memset (&globbuf, 0, sizeof (globbuf));
+ ucl_strlcpy (glob_pattern, (const char *)data, sizeof (glob_pattern));
+ if (glob (glob_pattern, 0, NULL, &globbuf) != 0) {
+ return (!must_exist || false);
+ }
+ for (i = 0; i < globbuf.gl_pathc; i ++) {
+ if (!ucl_include_file_single ((unsigned char *)globbuf.gl_pathv[i],
+ strlen (globbuf.gl_pathv[i]), parser, check_signature,
+ must_exist, priority)) {
+ globfree (&globbuf);
+ return false;
+ }
+ cnt ++;
+ }
+ globfree (&globbuf);
+
+ if (cnt == 0 && must_exist) {
+ ucl_create_err (&parser->err, "cannot match any files for pattern %s",
+ glob_pattern);
+ return false;
+ }
+ }
+ else {
+ return ucl_include_file_single (data, len, parser, check_signature,
+ must_exist, priority);
+ }
+ }
+#else
+ /* Win32 compilers do not support globbing. Therefore, for Win32,
+ treat allow_glob/need_glob as a NOOP and just return */
+ return ucl_include_file_single (data, len, parser, check_signature,
+ must_exist, priority);
+#endif
+
+ return true;
+}
+
+/**
+ * Common function to handle .*include* macros
+ * @param data
+ * @param len
+ * @param args
+ * @param parser
+ * @param default_try
+ * @param default_sign
+ * @return
+ */
+static bool
+ucl_include_common (const unsigned char *data, size_t len,
+ const ucl_object_t *args, struct ucl_parser *parser,
+ bool default_try,
+ bool default_sign)
+{
+ bool try_load, allow_glob, allow_url, need_sign;
+ unsigned priority;
+ const ucl_object_t *param;
+ ucl_object_iter_t it = NULL;
+
+ /* Default values */
+ try_load = default_try;
+ allow_glob = false;
+ allow_url = true;
+ need_sign = default_sign;
+ priority = 0;
+
+ /* Process arguments */
+ if (args != NULL && args->type == UCL_OBJECT) {
+ while ((param = ucl_iterate_object (args, &it, true)) != NULL) {
+ if (param->type == UCL_BOOLEAN) {
+ if (strcmp (param->key, "try") == 0) {
+ try_load = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "sign") == 0) {
+ need_sign = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "glob") == 0) {
+ allow_glob = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "url") == 0) {
+ allow_url = ucl_object_toboolean (param);
+ }
+ }
+ else if (param->type == UCL_INT) {
+ if (strcmp (param->key, "priority") == 0) {
+ priority = ucl_object_toint (param);
+ }
+ }
+ }
+ }
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, need_sign, !try_load,
+ allow_glob, priority);
+ }
+ else if (allow_url) {
+ /* Globbing is not used for URL's */
+ return ucl_include_url (data, len, parser, need_sign, !try_load,
+ priority);
+ }
+
+ return false;
+}
+
+/**
+ * Handle include macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+UCL_EXTERN bool
+ucl_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ return ucl_include_common (data, len, args, parser, false, false);
+}
+
+/**
+ * Handle includes macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+UCL_EXTERN bool
+ucl_includes_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ return ucl_include_common (data, len, args, parser, false, true);
+}
+
+
+UCL_EXTERN bool
+ucl_try_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ return ucl_include_common (data, len, args, parser, true, false);
+}
+
+UCL_EXTERN bool
+ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool need_expand)
+{
+ char realbuf[PATH_MAX], *curdir;
+
+ if (filename != NULL) {
+ if (need_expand) {
+ if (ucl_realpath (filename, realbuf) == NULL) {
+ return false;
+ }
+ }
+ else {
+ ucl_strlcpy (realbuf, filename, sizeof (realbuf));
+ }
+
+ /* Define variables */
+ ucl_parser_register_variable (parser, "FILENAME", realbuf);
+ curdir = dirname (realbuf);
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+ else {
+ /* Set everything from the current dir */
+ curdir = getcwd (realbuf, sizeof (realbuf));
+ ucl_parser_register_variable (parser, "FILENAME", "undef");
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+
+ return true;
+}
+
+UCL_EXTERN bool
+ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
+{
+ unsigned char *buf;
+ size_t len;
+ bool ret;
+ char realbuf[PATH_MAX];
+
+ if (ucl_realpath (filename, realbuf) == NULL) {
+ ucl_create_err (&parser->err, "cannot open file %s: %s",
+ filename,
+ strerror (errno));
+ return false;
+ }
+
+ if (!ucl_fetch_file (realbuf, &buf, &len, &parser->err, true)) {
+ return false;
+ }
+
+ if (parser->cur_file) {
+ free (parser->cur_file);
+ }
+ parser->cur_file = strdup (realbuf);
+ ucl_parser_set_filevars (parser, realbuf, false);
+ ret = ucl_parser_add_chunk (parser, buf, len);
+
+ if (len > 0) {
+ ucl_munmap (buf, len);
+ }
+
+ return ret;
+}
+
+UCL_EXTERN bool
+ucl_parser_add_fd (struct ucl_parser *parser, int fd)
+{
+ unsigned char *buf;
+ size_t len;
+ bool ret;
+ struct stat st;
+
+ if (fstat (fd, &st) == -1) {
+ ucl_create_err (&parser->err, "cannot stat fd %d: %s",
+ fd, strerror (errno));
+ return false;
+ }
+ if ((buf = ucl_mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ ucl_create_err (&parser->err, "cannot mmap fd %d: %s",
+ fd, strerror (errno));
+ return false;
+ }
+
+ if (parser->cur_file) {
+ free (parser->cur_file);
+ }
+ parser->cur_file = NULL;
+ len = st.st_size;
+ ret = ucl_parser_add_chunk (parser, buf, len);
+
+ if (len > 0) {
+ ucl_munmap (buf, len);
+ }
+
+ return ret;
+}
+
+size_t
+ucl_strlcpy (char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+size_t
+ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz)
+{
+ memcpy (dst, src, siz - 1);
+ dst[siz - 1] = '\0';
+
+ return siz - 1;
+}
+
+size_t
+ucl_strlcpy_tolower (char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = tolower (*s++)) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src); /* count does not include NUL */
+}
+
+ucl_object_t *
+ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags flags)
+{
+ ucl_object_t *obj;
+ const char *start, *end, *p, *pos;
+ char *dst, *d;
+ size_t escaped_len;
+
+ if (str == NULL) {
+ return NULL;
+ }
+
+ obj = ucl_object_new ();
+ if (obj) {
+ if (len == 0) {
+ len = strlen (str);
+ }
+ if (flags & UCL_STRING_TRIM) {
+ /* Skip leading spaces */
+ for (start = str; (size_t)(start - str) < len; start ++) {
+ if (!ucl_test_character (*start, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ break;
+ }
+ }
+ /* Skip trailing spaces */
+ for (end = str + len - 1; end > start; end --) {
+ if (!ucl_test_character (*end, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ break;
+ }
+ }
+ end ++;
+ }
+ else {
+ start = str;
+ end = str + len;
+ }
+
+ obj->type = UCL_STRING;
+ if (flags & UCL_STRING_ESCAPE) {
+ for (p = start, escaped_len = 0; p < end; p ++, escaped_len ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ escaped_len ++;
+ }
+ }
+ dst = malloc (escaped_len + 1);
+ if (dst != NULL) {
+ for (p = start, d = dst; p < end; p ++, d ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ switch (*p) {
+ case '\n':
+ *d++ = '\\';
+ *d = 'n';
+ break;
+ case '\r':
+ *d++ = '\\';
+ *d = 'r';
+ break;
+ case '\b':
+ *d++ = '\\';
+ *d = 'b';
+ break;
+ case '\t':
+ *d++ = '\\';
+ *d = 't';
+ break;
+ case '\f':
+ *d++ = '\\';
+ *d = 'f';
+ break;
+ case '\\':
+ *d++ = '\\';
+ *d = '\\';
+ break;
+ case '"':
+ *d++ = '\\';
+ *d = '"';
+ break;
+ }
+ }
+ else {
+ *d = *p;
+ }
+ }
+ *d = '\0';
+ obj->value.sv = dst;
+ obj->trash_stack[UCL_TRASH_VALUE] = dst;
+ obj->len = escaped_len;
+ }
+ }
+ else {
+ dst = malloc (end - start + 1);
+ if (dst != NULL) {
+ ucl_strlcpy_unsafe (dst, start, end - start + 1);
+ obj->value.sv = dst;
+ obj->trash_stack[UCL_TRASH_VALUE] = dst;
+ obj->len = end - start;
+ }
+ }
+ if ((flags & UCL_STRING_PARSE) && dst != NULL) {
+ /* Parse what we have */
+ if (flags & UCL_STRING_PARSE_BOOLEAN) {
+ if (!ucl_maybe_parse_boolean (obj, dst, obj->len) && (flags & UCL_STRING_PARSE_NUMBER)) {
+ ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos,
+ flags & UCL_STRING_PARSE_DOUBLE,
+ flags & UCL_STRING_PARSE_BYTES,
+ flags & UCL_STRING_PARSE_TIME);
+ }
+ }
+ else {
+ ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos,
+ flags & UCL_STRING_PARSE_DOUBLE,
+ flags & UCL_STRING_PARSE_BYTES,
+ flags & UCL_STRING_PARSE_TIME);
+ }
+ }
+ }
+
+ return obj;
+}
+
+static bool
+ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key, bool merge, bool replace)
+{
+ ucl_object_t *found, *tmp;
+ const ucl_object_t *cur;
+ ucl_object_iter_t it = NULL;
+ const char *p;
+ int ret = true;
+
+ if (elt == NULL || key == NULL) {
+ return false;
+ }
+
+ if (top == NULL) {
+ return false;
+ }
+
+ if (top->type != UCL_OBJECT) {
+ /* It is possible to convert NULL type to an object */
+ if (top->type == UCL_NULL) {
+ top->type = UCL_OBJECT;
+ }
+ else {
+ /* Refuse converting of other object types */
+ return false;
+ }
+ }
+
+ if (top->value.ov == NULL) {
+ top->value.ov = ucl_hash_create (false);
+ }
+
+ if (keylen == 0) {
+ keylen = strlen (key);
+ }
+
+ for (p = key; p < key + keylen; p ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_UCL_UNSAFE)) {
+ elt->flags |= UCL_OBJECT_NEED_KEY_ESCAPE;
+ break;
+ }
+ }
+
+ /* workaround for some use cases */
+ if (elt->trash_stack[UCL_TRASH_KEY] != NULL &&
+ key != (const char *)elt->trash_stack[UCL_TRASH_KEY]) {
+ /* Remove copied key */
+ free (elt->trash_stack[UCL_TRASH_KEY]);
+ elt->trash_stack[UCL_TRASH_KEY] = NULL;
+ elt->flags &= ~UCL_OBJECT_ALLOCATED_KEY;
+ }
+
+ elt->key = key;
+ elt->keylen = keylen;
+
+ if (copy_key) {
+ ucl_copy_key_trash (elt);
+ }
+
+ found = __DECONST (ucl_object_t *, ucl_hash_search_obj (top->value.ov, elt));
+
+ if (found == NULL) {
+ top->value.ov = ucl_hash_insert_object (top->value.ov, elt, false);
+ top->len ++;
+ if (replace) {
+ ret = false;
+ }
+ }
+ else {
+ if (replace) {
+ ucl_hash_replace (top->value.ov, found, elt);
+ ucl_object_unref (found);
+ }
+ else if (merge) {
+ if (found->type != UCL_OBJECT && elt->type == UCL_OBJECT) {
+ /* Insert old elt to new one */
+ ucl_object_insert_key_common (elt, found, found->key,
+ found->keylen, copy_key, false, false);
+ ucl_hash_delete (top->value.ov, found);
+ top->value.ov = ucl_hash_insert_object (top->value.ov, elt, false);
+ }
+ else if (found->type == UCL_OBJECT && elt->type != UCL_OBJECT) {
+ /* Insert new to old */
+ ucl_object_insert_key_common (found, elt, elt->key,
+ elt->keylen, copy_key, false, false);
+ }
+ else if (found->type == UCL_OBJECT && elt->type == UCL_OBJECT) {
+ /* Mix two hashes */
+ while ((cur = ucl_iterate_object (elt, &it, true)) != NULL) {
+ tmp = ucl_object_ref (cur);
+ ucl_object_insert_key_common (found, tmp, cur->key,
+ cur->keylen, copy_key, false, false);
+ }
+ ucl_object_unref (elt);
+ }
+ else {
+ /* Just make a list of scalars */
+ DL_APPEND (found, elt);
+ }
+ }
+ else {
+ DL_APPEND (found, elt);
+ }
+ }
+
+ return ret;
+}
+
+bool
+ucl_object_delete_keyl (ucl_object_t *top, const char *key, size_t keylen)
+{
+ ucl_object_t *found;
+
+ if (top == NULL || key == NULL) {
+ return false;
+ }
+
+ found = __DECONST (ucl_object_t *, ucl_object_find_keyl (top, key, keylen));
+
+ if (found == NULL) {
+ return false;
+ }
+
+ ucl_hash_delete (top->value.ov, found);
+ ucl_object_unref (found);
+ top->len --;
+
+ return true;
+}
+
+bool
+ucl_object_delete_key (ucl_object_t *top, const char *key)
+{
+ return ucl_object_delete_keyl (top, key, strlen(key));
+}
+
+ucl_object_t*
+ucl_object_pop_keyl (ucl_object_t *top, const char *key, size_t keylen)
+{
+ const ucl_object_t *found;
+
+ if (top == NULL || key == NULL) {
+ return false;
+ }
+ found = ucl_object_find_keyl (top, key, keylen);
+
+ if (found == NULL) {
+ return NULL;
+ }
+ ucl_hash_delete (top->value.ov, found);
+ top->len --;
+
+ return __DECONST (ucl_object_t *, found);
+}
+
+ucl_object_t*
+ucl_object_pop_key (ucl_object_t *top, const char *key)
+{
+ return ucl_object_pop_keyl (top, key, strlen(key));
+}
+
+bool
+ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, false);
+}
+
+bool
+ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, true, false);
+}
+
+bool
+ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, true);
+}
+
+bool
+ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
+{
+ ucl_object_t *cur = NULL, *cp = NULL, *found = NULL;
+ ucl_object_iter_t iter = NULL;
+
+ if (top == NULL || top->type != UCL_OBJECT || elt == NULL || elt->type != UCL_OBJECT) {
+ return false;
+ }
+
+ /* Mix two hashes */
+ while ((cur = (ucl_object_t*)ucl_hash_iterate (elt->value.ov, &iter))) {
+ if (copy) {
+ cp = ucl_object_copy (cur);
+ }
+ else {
+ cp = ucl_object_ref (cur);
+ }
+ found = __DECONST(ucl_object_t *, ucl_hash_search (top->value.ov, cp->key, cp->keylen));
+ if (found == NULL) {
+ /* The key does not exist */
+ top->value.ov = ucl_hash_insert_object (top->value.ov, cp, false);
+ top->len ++;
+ }
+ else {
+ /* The key already exists, replace it */
+ ucl_hash_replace (top->value.ov, found, cp);
+ ucl_object_unref (found);
+ }
+ }
+
+ return true;
+}
+
+const ucl_object_t *
+ucl_object_find_keyl (const ucl_object_t *obj, const char *key, size_t klen)
+{
+ const ucl_object_t *ret;
+ ucl_object_t srch;
+
+ if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) {
+ return NULL;
+ }
+
+ srch.key = key;
+ srch.keylen = klen;
+ ret = ucl_hash_search_obj (obj->value.ov, &srch);
+
+ return ret;
+}
+
+const ucl_object_t *
+ucl_object_find_key (const ucl_object_t *obj, const char *key)
+{
+ if (key == NULL)
+ return NULL;
+
+ return ucl_object_find_keyl (obj, key, strlen(key));
+}
+
+const ucl_object_t*
+ucl_iterate_object (const ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_values)
+{
+ const ucl_object_t *elt = NULL;
+
+ if (obj == NULL || iter == NULL) {
+ return NULL;
+ }
+
+ if (expand_values) {
+ switch (obj->type) {
+ case UCL_OBJECT:
+ return (const ucl_object_t*)ucl_hash_iterate (obj->value.ov, iter);
+ break;
+ case UCL_ARRAY: {
+ unsigned int idx;
+ UCL_ARRAY_GET (vec, obj);
+ idx = (unsigned int)(uintptr_t)(*iter);
+
+ if (vec != NULL) {
+ while (idx < kv_size (*vec)) {
+ if ((elt = kv_A (*vec, idx)) != NULL) {
+ idx ++;
+ break;
+ }
+ idx ++;
+ }
+ *iter = (void *)(uintptr_t)idx;
+ }
+
+ return elt;
+ break;
+ }
+ default:
+ /* Go to linear iteration */
+ break;
+ }
+ }
+ /* Treat everything as a linear list */
+ elt = *iter;
+ if (elt == NULL) {
+ elt = obj;
+ }
+ else if (elt == obj) {
+ return NULL;
+ }
+ *iter = __DECONST (void *, elt->next ? elt->next : obj);
+ return elt;
+
+ /* Not reached */
+ return NULL;
+}
+
+const char safe_iter_magic[4] = {'u', 'i', 't', 'e'};
+struct ucl_object_safe_iter {
+ char magic[4]; /* safety check */
+ const ucl_object_t *impl_it; /* implicit object iteration */
+ ucl_object_iter_t expl_it; /* explicit iteration */
+};
+
+#define UCL_SAFE_ITER(ptr) (struct ucl_object_safe_iter *)(ptr)
+#define UCL_SAFE_ITER_CHECK(it) do { \
+ assert (it != NULL); \
+ assert (memcmp (it->magic, safe_iter_magic, sizeof (it->magic)) == 0); \
+ } while (0)
+
+ucl_object_iter_t
+ucl_object_iterate_new (const ucl_object_t *obj)
+{
+ struct ucl_object_safe_iter *it;
+
+ it = UCL_ALLOC (sizeof (*it));
+ if (it != NULL) {
+ memcpy (it->magic, safe_iter_magic, sizeof (it->magic));
+ it->expl_it = NULL;
+ it->impl_it = obj;
+ }
+
+ return (ucl_object_iter_t)it;
+}
+
+
+ucl_object_iter_t
+ucl_object_iterate_reset (ucl_object_iter_t it, const ucl_object_t *obj)
+{
+ struct ucl_object_safe_iter *rit = UCL_SAFE_ITER (it);
+
+ UCL_SAFE_ITER_CHECK (rit);
+
+ rit->impl_it = obj;
+ rit->expl_it = NULL;
+
+ return it;
+}
+
+const ucl_object_t*
+ucl_object_iterate_safe (ucl_object_iter_t it, bool expand_values)
+{
+ struct ucl_object_safe_iter *rit = UCL_SAFE_ITER (it);
+ const ucl_object_t *ret = NULL;
+
+ UCL_SAFE_ITER_CHECK (rit);
+
+ if (rit->impl_it == NULL) {
+ return NULL;
+ }
+
+ if (rit->impl_it->type == UCL_OBJECT || rit->impl_it->type == UCL_ARRAY) {
+ ret = ucl_iterate_object (rit->impl_it, &rit->expl_it, true);
+
+ if (ret == NULL) {
+ /* Need to switch to another implicit object in chain */
+ rit->impl_it = rit->impl_it->next;
+ rit->expl_it = NULL;
+ return ucl_object_iterate_safe (it, expand_values);
+ }
+ }
+ else {
+ /* Just iterate over the implicit array */
+ ret = rit->impl_it;
+ rit->impl_it = rit->impl_it->next;
+ if (expand_values) {
+ /* We flatten objects if need to expand values */
+ if (ret->type == UCL_OBJECT || ret->type == UCL_ARRAY) {
+ return ucl_object_iterate_safe (it, expand_values);
+ }
+ }
+ }
+
+ return ret;
+}
+
+void
+ucl_object_iterate_free (ucl_object_iter_t it)
+{
+ struct ucl_object_safe_iter *rit = UCL_SAFE_ITER (it);
+
+ UCL_SAFE_ITER_CHECK (rit);
+
+ UCL_FREE (sizeof (*rit), it);
+}
+
+const ucl_object_t *
+ucl_lookup_path (const ucl_object_t *top, const char *path_in) {
+ const ucl_object_t *o = NULL, *found;
+ const char *p, *c;
+ char *err_str;
+ unsigned index;
+
+ if (path_in == NULL || top == NULL) {
+ return NULL;
+ }
+
+ found = NULL;
+ p = path_in;
+
+ /* Skip leading dots */
+ while (*p == '.') {
+ p ++;
+ }
+
+ c = p;
+ while (*p != '\0') {
+ p ++;
+ if (*p == '.' || *p == '\0') {
+ if (p > c) {
+ switch (top->type) {
+ case UCL_ARRAY:
+ /* Key should be an int */
+ index = strtoul (c, &err_str, 10);
+ if (err_str != NULL && (*err_str != '.' && *err_str != '\0')) {
+ return NULL;
+ }
+ o = ucl_array_find_index (top, index);
+ break;
+ default:
+ o = ucl_object_find_keyl (top, c, p - c);
+ break;
+ }
+ if (o == NULL) {
+ return NULL;
+ }
+ top = o;
+ }
+ if (*p != '\0') {
+ c = p + 1;
+ }
+ }
+ }
+ found = o;
+
+ return found;
+}
+
+
+ucl_object_t *
+ucl_object_new (void)
+{
+ return ucl_object_typed_new (UCL_NULL);
+}
+
+ucl_object_t *
+ucl_object_typed_new (ucl_type_t type)
+{
+ return ucl_object_new_full (type, 0);
+}
+
+ucl_object_t *
+ucl_object_new_full (ucl_type_t type, unsigned priority)
+{
+ ucl_object_t *new;
+
+ if (type != UCL_USERDATA) {
+ new = UCL_ALLOC (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ new->type = (type <= UCL_NULL ? type : UCL_NULL);
+ new->next = NULL;
+ new->prev = new;
+ ucl_object_set_priority (new, priority);
+
+ if (type == UCL_ARRAY) {
+ new->value.av = UCL_ALLOC (sizeof (ucl_array_t));
+ if (new->value.av) {
+ memset (new->value.av, 0, sizeof (ucl_array_t));
+ UCL_ARRAY_GET (vec, new);
+
+ /* Preallocate some space for arrays */
+ kv_resize (ucl_object_t *, *vec, 8);
+ }
+ }
+ }
+ }
+ else {
+ new = ucl_object_new_userdata (NULL, NULL);
+ ucl_object_set_priority (new, priority);
+ }
+
+ return new;
+}
+
+ucl_object_t*
+ucl_object_new_userdata (ucl_userdata_dtor dtor, ucl_userdata_emitter emitter)
+{
+ struct ucl_object_userdata *new;
+ size_t nsize = sizeof (*new);
+
+ new = UCL_ALLOC (nsize);
+ if (new != NULL) {
+ memset (new, 0, nsize);
+ new->obj.ref = 1;
+ new->obj.type = UCL_USERDATA;
+ new->obj.next = NULL;
+ new->obj.prev = (ucl_object_t *)new;
+ new->dtor = dtor;
+ new->emitter = emitter;
+ }
+
+ return (ucl_object_t *)new;
+}
+
+ucl_type_t
+ucl_object_type (const ucl_object_t *obj)
+{
+ return obj->type;
+}
+
+ucl_object_t*
+ucl_object_fromstring (const char *str)
+{
+ return ucl_object_fromstring_common (str, 0, UCL_STRING_ESCAPE);
+}
+
+ucl_object_t *
+ucl_object_fromlstring (const char *str, size_t len)
+{
+ return ucl_object_fromstring_common (str, len, UCL_STRING_ESCAPE);
+}
+
+ucl_object_t *
+ucl_object_fromint (int64_t iv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_INT;
+ obj->value.iv = iv;
+ }
+
+ return obj;
+}
+
+ucl_object_t *
+ucl_object_fromdouble (double dv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_FLOAT;
+ obj->value.dv = dv;
+ }
+
+ return obj;
+}
+
+ucl_object_t*
+ucl_object_frombool (bool bv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_BOOLEAN;
+ obj->value.iv = bv;
+ }
+
+ return obj;
+}
+
+bool
+ucl_array_append (ucl_object_t *top, ucl_object_t *elt)
+{
+ UCL_ARRAY_GET (vec, top);
+
+ if (elt == NULL || top == NULL) {
+ return false;
+ }
+
+ if (vec == NULL) {
+ vec = UCL_ALLOC (sizeof (*vec));
+ kv_init (*vec);
+ top->value.av = (void *)vec;
+ }
+
+ kv_push (ucl_object_t *, *vec, elt);
+
+ top->len ++;
+
+ return true;
+}
+
+bool
+ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt)
+{
+ UCL_ARRAY_GET (vec, top);
+
+ if (elt == NULL || top == NULL) {
+ return false;
+ }
+
+ if (vec == NULL) {
+ vec = UCL_ALLOC (sizeof (*vec));
+ kv_init (*vec);
+ top->value.av = (void *)vec;
+ kv_push (ucl_object_t *, *vec, elt);
+ }
+ else {
+ /* Slow O(n) algorithm */
+ kv_prepend (ucl_object_t *, *vec, elt);
+ }
+
+ top->len ++;
+
+ return true;
+}
+
+bool
+ucl_array_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
+{
+ unsigned i;
+ ucl_object_t **obj;
+ UCL_ARRAY_GET (v1, top);
+ UCL_ARRAY_GET (v2, elt);
+
+ if (elt == NULL || top == NULL || top->type != UCL_ARRAY || elt->type != UCL_ARRAY) {
+ return false;
+ }
+
+ kv_concat (ucl_object_t *, *v1, *v2);
+
+ for (i = v2->n; i < v1->n; i ++) {
+ obj = &kv_A (*v1, i);
+ if (*obj == NULL) {
+ continue;
+ }
+
+ top->len ++;
+ if (copy) {
+ *obj = ucl_object_copy (*obj);
+ }
+ else {
+ ucl_object_ref (*obj);
+ }
+ }
+
+ return true;
+}
+
+ucl_object_t *
+ucl_array_delete (ucl_object_t *top, ucl_object_t *elt)
+{
+ UCL_ARRAY_GET (vec, top);
+ ucl_object_t *ret = NULL;
+ unsigned i;
+
+ for (i = 0; i < vec->n; i ++) {
+ if (kv_A (*vec, i) == elt) {
+ kv_del (ucl_object_t *, *vec, i);
+ ret = elt;
+ top->len --;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+const ucl_object_t *
+ucl_array_head (const ucl_object_t *top)
+{
+ UCL_ARRAY_GET (vec, top);
+
+ if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) {
+ return NULL;
+ }
+
+ return (vec->n > 0 ? vec->a[0] : NULL);
+}
+
+const ucl_object_t *
+ucl_array_tail (const ucl_object_t *top)
+{
+ UCL_ARRAY_GET (vec, top);
+
+ if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) {
+ return NULL;
+ }
+
+ return (vec->n > 0 ? vec->a[vec->n - 1] : NULL);
+}
+
+ucl_object_t *
+ucl_array_pop_last (ucl_object_t *top)
+{
+ UCL_ARRAY_GET (vec, top);
+ ucl_object_t **obj, *ret = NULL;
+
+ if (vec != NULL && vec->n > 0) {
+ obj = &kv_A (*vec, vec->n - 1);
+ ret = *obj;
+ kv_del (ucl_object_t *, *vec, vec->n - 1);
+ top->len --;
+ }
+
+ return ret;
+}
+
+ucl_object_t *
+ucl_array_pop_first (ucl_object_t *top)
+{
+ UCL_ARRAY_GET (vec, top);
+ ucl_object_t **obj, *ret = NULL;
+
+ if (vec != NULL && vec->n > 0) {
+ obj = &kv_A (*vec, 0);
+ ret = *obj;
+ kv_del (ucl_object_t *, *vec, 0);
+ top->len --;
+ }
+
+ return ret;
+}
+
+const ucl_object_t *
+ucl_array_find_index (const ucl_object_t *top, unsigned int index)
+{
+ UCL_ARRAY_GET (vec, top);
+
+ if (vec != NULL && vec->n > 0 && index < vec->n) {
+ return kv_A (*vec, index);
+ }
+
+ return NULL;
+}
+
+ucl_object_t *
+ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
+ unsigned int index)
+{
+ UCL_ARRAY_GET (vec, top);
+ ucl_object_t *ret = NULL;
+
+ if (vec != NULL && vec->n > 0 && index < vec->n) {
+ ret = kv_A (*vec, index);
+ kv_A (*vec, index) = elt;
+ }
+
+ return ret;
+}
+
+ucl_object_t *
+ucl_elt_append (ucl_object_t *head, ucl_object_t *elt)
+{
+
+ if (head == NULL) {
+ elt->next = NULL;
+ elt->prev = elt;
+ head = elt;
+ }
+ else {
+ elt->prev = head->prev;
+ head->prev->next = elt;
+ head->prev = elt;
+ elt->next = NULL;
+ }
+
+ return head;
+}
+
+bool
+ucl_object_todouble_safe (const ucl_object_t *obj, double *target)
+{
+ if (obj == NULL || target == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_INT:
+ *target = obj->value.iv; /* Probaly could cause overflow */
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ *target = obj->value.dv;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+double
+ucl_object_todouble (const ucl_object_t *obj)
+{
+ double result = 0.;
+
+ ucl_object_todouble_safe (obj, &result);
+ return result;
+}
+
+bool
+ucl_object_toint_safe (const ucl_object_t *obj, int64_t *target)
+{
+ if (obj == NULL || target == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_INT:
+ *target = obj->value.iv;
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ *target = obj->value.dv; /* Loosing of decimal points */
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+int64_t
+ucl_object_toint (const ucl_object_t *obj)
+{
+ int64_t result = 0;
+
+ ucl_object_toint_safe (obj, &result);
+ return result;
+}
+
+bool
+ucl_object_toboolean_safe (const ucl_object_t *obj, bool *target)
+{
+ if (obj == NULL || target == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_BOOLEAN:
+ *target = (obj->value.iv == true);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ucl_object_toboolean (const ucl_object_t *obj)
+{
+ bool result = false;
+
+ ucl_object_toboolean_safe (obj, &result);
+ return result;
+}
+
+bool
+ucl_object_tostring_safe (const ucl_object_t *obj, const char **target)
+{
+ if (obj == NULL || target == NULL) {
+ return false;
+ }
+
+ switch (obj->type) {
+ case UCL_STRING:
+ *target = ucl_copy_value_trash (obj);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+const char *
+ucl_object_tostring (const ucl_object_t *obj)
+{
+ const char *result = NULL;
+
+ ucl_object_tostring_safe (obj, &result);
+ return result;
+}
+
+const char *
+ucl_object_tostring_forced (const ucl_object_t *obj)
+{
+ return ucl_copy_value_trash (obj);
+}
+
+bool
+ucl_object_tolstring_safe (const ucl_object_t *obj, const char **target, size_t *tlen)
+{
+ if (obj == NULL || target == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_STRING:
+ *target = obj->value.sv;
+ if (tlen != NULL) {
+ *tlen = obj->len;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+const char *
+ucl_object_tolstring (const ucl_object_t *obj, size_t *tlen)
+{
+ const char *result = NULL;
+
+ ucl_object_tolstring_safe (obj, &result, tlen);
+ return result;
+}
+
+const char *
+ucl_object_key (const ucl_object_t *obj)
+{
+ return ucl_copy_key_trash (obj);
+}
+
+const char *
+ucl_object_keyl (const ucl_object_t *obj, size_t *len)
+{
+ if (len == NULL || obj == NULL) {
+ return NULL;
+ }
+ *len = obj->keylen;
+ return obj->key;
+}
+
+ucl_object_t *
+ucl_object_ref (const ucl_object_t *obj)
+{
+ ucl_object_t *res = NULL;
+
+ if (obj != NULL) {
+ if (obj->flags & UCL_OBJECT_EPHEMERAL) {
+ /*
+ * Use deep copy for ephemeral objects, note that its refcount
+ * is NOT increased, since ephemeral objects does not need refcount
+ * at all
+ */
+ res = ucl_object_copy (obj);
+ }
+ else {
+ res = __DECONST (ucl_object_t *, obj);
+#ifdef HAVE_ATOMIC_BUILTINS
+ (void)__sync_add_and_fetch (&res->ref, 1);
+#else
+ res->ref ++;
+#endif
+ }
+ }
+ return res;
+}
+
+static ucl_object_t *
+ucl_object_copy_internal (const ucl_object_t *other, bool allow_array)
+{
+
+ ucl_object_t *new;
+ ucl_object_iter_t it = NULL;
+ const ucl_object_t *cur;
+
+ new = malloc (sizeof (*new));
+
+ if (new != NULL) {
+ memcpy (new, other, sizeof (*new));
+ if (other->flags & UCL_OBJECT_EPHEMERAL) {
+ /* Copied object is always non ephemeral */
+ new->flags &= ~UCL_OBJECT_EPHEMERAL;
+ }
+ new->ref = 1;
+ /* Unlink from others */
+ new->next = NULL;
+ new->prev = new;
+
+ /* deep copy of values stored */
+ if (other->trash_stack[UCL_TRASH_KEY] != NULL) {
+ new->trash_stack[UCL_TRASH_KEY] =
+ strdup (other->trash_stack[UCL_TRASH_KEY]);
+ if (other->key == (const char *)other->trash_stack[UCL_TRASH_KEY]) {
+ new->key = new->trash_stack[UCL_TRASH_KEY];
+ }
+ }
+ if (other->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ new->trash_stack[UCL_TRASH_VALUE] =
+ strdup (other->trash_stack[UCL_TRASH_VALUE]);
+ if (new->type == UCL_STRING) {
+ new->value.sv = new->trash_stack[UCL_TRASH_VALUE];
+ }
+ }
+
+ if (other->type == UCL_ARRAY || other->type == UCL_OBJECT) {
+ /* reset old value */
+ memset (&new->value, 0, sizeof (new->value));
+
+ while ((cur = ucl_iterate_object (other, &it, true)) != NULL) {
+ if (other->type == UCL_ARRAY) {
+ ucl_array_append (new, ucl_object_copy_internal (cur, false));
+ }
+ else {
+ ucl_object_t *cp = ucl_object_copy_internal (cur, true);
+ if (cp != NULL) {
+ ucl_object_insert_key (new, cp, cp->key, cp->keylen,
+ false);
+ }
+ }
+ }
+ }
+ else if (allow_array && other->next != NULL) {
+ LL_FOREACH (other->next, cur) {
+ ucl_object_t *cp = ucl_object_copy_internal (cur, false);
+ if (cp != NULL) {
+ DL_APPEND (new, cp);
+ }
+ }
+ }
+ }
+
+ return new;
+}
+
+ucl_object_t *
+ucl_object_copy (const ucl_object_t *other)
+{
+ return ucl_object_copy_internal (other, true);
+}
+
+void
+ucl_object_unref (ucl_object_t *obj)
+{
+ if (obj != NULL) {
+#ifdef HAVE_ATOMIC_BUILTINS
+ unsigned int rc = __sync_sub_and_fetch (&obj->ref, 1);
+ if (rc == 0) {
+#else
+ if (--obj->ref == 0) {
+#endif
+ ucl_object_free_internal (obj, true, ucl_object_dtor_unref);
+ }
+ }
+}
+
+int
+ucl_object_compare (const ucl_object_t *o1, const ucl_object_t *o2)
+{
+ const ucl_object_t *it1, *it2;
+ ucl_object_iter_t iter = NULL;
+ int ret = 0;
+
+ if (o1->type != o2->type) {
+ return (o1->type) - (o2->type);
+ }
+
+ switch (o1->type) {
+ case UCL_STRING:
+ if (o1->len == o2->len && o1->len > 0) {
+ ret = strcmp (ucl_object_tostring(o1), ucl_object_tostring(o2));
+ }
+ else {
+ ret = o1->len - o2->len;
+ }
+ break;
+ case UCL_FLOAT:
+ case UCL_INT:
+ case UCL_TIME:
+ ret = ucl_object_todouble (o1) - ucl_object_todouble (o2);
+ break;
+ case UCL_BOOLEAN:
+ ret = ucl_object_toboolean (o1) - ucl_object_toboolean (o2);
+ break;
+ case UCL_ARRAY:
+ if (o1->len == o2->len && o1->len > 0) {
+ UCL_ARRAY_GET (vec1, o1);
+ UCL_ARRAY_GET (vec2, o2);
+ unsigned i;
+
+ /* Compare all elements in both arrays */
+ for (i = 0; i < vec1->n; i ++) {
+ it1 = kv_A (*vec1, i);
+ it2 = kv_A (*vec2, i);
+
+ if (it1 == NULL && it2 != NULL) {
+ return -1;
+ }
+ else if (it2 == NULL && it1 != NULL) {
+ return 1;
+ }
+ else if (it1 != NULL && it2 != NULL) {
+ ret = ucl_object_compare (it1, it2);
+ if (ret != 0) {
+ break;
+ }
+ }
+ }
+ }
+ else {
+ ret = o1->len - o2->len;
+ }
+ break;
+ case UCL_OBJECT:
+ if (o1->len == o2->len && o1->len > 0) {
+ while ((it1 = ucl_iterate_object (o1, &iter, true)) != NULL) {
+ it2 = ucl_object_find_key (o2, ucl_object_key (it1));
+ if (it2 == NULL) {
+ ret = 1;
+ break;
+ }
+ ret = ucl_object_compare (it1, it2);
+ if (ret != 0) {
+ break;
+ }
+ }
+ }
+ else {
+ ret = o1->len - o2->len;
+ }
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+void
+ucl_object_array_sort (ucl_object_t *ar,
+ int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2))
+{
+ UCL_ARRAY_GET (vec, ar);
+
+ if (cmp == NULL || ar == NULL || ar->type != UCL_ARRAY) {
+ return;
+ }
+
+ qsort (vec->a, vec->n, sizeof (ucl_object_t *),
+ (int (*)(const void *, const void *))cmp);
+}
+
+#define PRIOBITS 4
+
+unsigned int
+ucl_object_get_priority (const ucl_object_t *obj)
+{
+ if (obj == NULL) {
+ return 0;
+ }
+
+ return (obj->flags >> ((sizeof (obj->flags) * NBBY) - PRIOBITS));
+}
+
+void
+ucl_object_set_priority (ucl_object_t *obj,
+ unsigned int priority)
+{
+ if (obj != NULL) {
+ priority &= (0x1 << PRIOBITS) - 1;
+ obj->flags |= priority << ((sizeof (obj->flags) * NBBY) - PRIOBITS);
+ }
+}
diff --git a/contrib/libucl/xxhash.c b/contrib/libucl/xxhash.c
new file mode 100644
index 000000000..5869503be
--- /dev/null
+++ b/contrib/libucl/xxhash.c
@@ -0,0 +1,475 @@
+/*
+xxHash - Fast Hash algorithm
+Copyright (C) 2012-2013, Yann Collet.
+BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+You can contact the author at :
+- xxHash source repository : http://code.google.com/p/xxhash/
+*/
+
+
+//**************************************
+// Tuning parameters
+//**************************************
+// Unaligned memory access is automatically enabled for "common" CPU, such as x86.
+// For others CPU, the compiler will be more cautious, and insert extra code to ensure aligned access is respected.
+// If you know your target CPU supports unaligned memory access, you want to force this option manually to improve performance.
+// You can also enable this parameter if you know your input data will always be aligned (boundaries of 4, for U32).
+#if defined(__ARM_FEATURE_UNALIGNED) || defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+# define XXH_USE_UNALIGNED_ACCESS 1
+#endif
+
+// XXH_ACCEPT_NULL_INPUT_POINTER :
+// If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer.
+// When this option is enabled, xxHash output for null input pointers will be the same as a null-length input.
+// This option has a very small performance cost (only measurable on small inputs).
+// By default, this option is disabled. To enable it, uncomment below define :
+//#define XXH_ACCEPT_NULL_INPUT_POINTER 1
+
+// XXH_FORCE_NATIVE_FORMAT :
+// By default, xxHash library provides endian-independant Hash values, based on little-endian convention.
+// Results are therefore identical for little-endian and big-endian CPU.
+// This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format.
+// Should endian-independance be of no importance for your application, you may set the #define below to 1.
+// It will improve speed for Big-endian CPU.
+// This option has no impact on Little_Endian CPU.
+#define XXH_FORCE_NATIVE_FORMAT 0
+
+
+//**************************************
+// Compiler Specific Options
+//**************************************
+// Disable some Visual warning messages
+#ifdef _MSC_VER // Visual Studio
+# pragma warning(disable : 4127) // disable: C4127: conditional expression is constant
+#endif
+
+#ifdef _MSC_VER // Visual Studio
+# define forceinline static __forceinline
+#else
+# ifdef __GNUC__
+# define forceinline static inline __attribute__((always_inline))
+# else
+# define forceinline static inline
+# endif
+#endif
+
+
+//**************************************
+// Includes & Memory related functions
+//**************************************
+#include "xxhash.h"
+// Modify the local functions below should you wish to use some other memory related routines
+// for malloc(), free()
+#include <stdlib.h>
+forceinline void* XXH_malloc(size_t s) { return malloc(s); }
+forceinline void XXH_free (void* p) { free(p); }
+// for memcpy()
+#include <string.h>
+forceinline void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); }
+
+
+//**************************************
+// Basic Types
+//**************************************
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L // C99
+# include <stdint.h>
+ typedef uint8_t BYTE;
+ typedef uint16_t U16;
+ typedef uint32_t U32;
+ typedef int32_t S32;
+ typedef uint64_t U64;
+#else
+ typedef unsigned char BYTE;
+ typedef unsigned short U16;
+ typedef unsigned int U32;
+ typedef signed int S32;
+ typedef unsigned long long U64;
+#endif
+
+#if defined(__GNUC__) && !defined(XXH_USE_UNALIGNED_ACCESS)
+# define _PACKED __attribute__ ((packed))
+#else
+# define _PACKED
+#endif
+
+#if !defined(XXH_USE_UNALIGNED_ACCESS) && !defined(__GNUC__)
+# ifdef __IBMC__
+# pragma pack(1)
+# else
+# pragma pack(push, 1)
+# endif
+#endif
+
+typedef struct _U32_S { U32 v; } _PACKED U32_S;
+
+#if !defined(XXH_USE_UNALIGNED_ACCESS) && !defined(__GNUC__)
+# pragma pack(pop)
+#endif
+
+#define A32(x) (((U32_S *)(x))->v)
+
+
+//***************************************
+// Compiler-specific Functions and Macros
+//***************************************
+#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+// Note : although _rotl exists for minGW (GCC under windows), performance seems poor
+#if defined(_MSC_VER)
+# define XXH_rotl32(x,r) _rotl(x,r)
+#else
+# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r)))
+#endif
+
+#if defined(_MSC_VER) // Visual Studio
+# define XXH_swap32 _byteswap_ulong
+#elif GCC_VERSION >= 403
+# define XXH_swap32 __builtin_bswap32
+#else
+static inline U32 XXH_swap32 (U32 x) {
+ return ((x << 24) & 0xff000000 ) |
+ ((x << 8) & 0x00ff0000 ) |
+ ((x >> 8) & 0x0000ff00 ) |
+ ((x >> 24) & 0x000000ff );}
+#endif
+
+
+//**************************************
+// Constants
+//**************************************
+#define PRIME32_1 2654435761U
+#define PRIME32_2 2246822519U
+#define PRIME32_3 3266489917U
+#define PRIME32_4 668265263U
+#define PRIME32_5 374761393U
+
+
+//**************************************
+// Architecture Macros
+//**************************************
+typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess;
+#ifndef XXH_CPU_LITTLE_ENDIAN // It is possible to define XXH_CPU_LITTLE_ENDIAN externally, for example using a compiler switch
+ static const int one = 1;
+# define XXH_CPU_LITTLE_ENDIAN (*(char*)(&one))
+#endif
+
+
+//**************************************
+// Macros
+//**************************************
+#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(!!(c)) }; } // use only *after* variable declarations
+
+
+//****************************
+// Memory reads
+//****************************
+typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;
+
+forceinline U32 XXH_readLE32_align(const U32* ptr, XXH_endianess endian, XXH_alignment align)
+{
+ if (align==XXH_unaligned)
+ return endian==XXH_littleEndian ? A32(ptr) : XXH_swap32(A32(ptr));
+ else
+ return endian==XXH_littleEndian ? *ptr : XXH_swap32(*ptr);
+}
+
+forceinline U32 XXH_readLE32(const U32* ptr, XXH_endianess endian) { return XXH_readLE32_align(ptr, endian, XXH_unaligned); }
+
+
+//****************************
+// Simple Hash Functions
+//****************************
+forceinline U32 XXH32_endian_align(const void* input, int len, U32 seed, XXH_endianess endian, XXH_alignment align)
+{
+ const BYTE* p = (const BYTE*)input;
+ const BYTE* const bEnd = p + len;
+ U32 h32;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+ if (p==NULL) { len=0; p=(const BYTE*)(size_t)16; }
+#endif
+
+ if (len>=16)
+ {
+ const BYTE* const limit = bEnd - 16;
+ U32 v1 = seed + PRIME32_1 + PRIME32_2;
+ U32 v2 = seed + PRIME32_2;
+ U32 v3 = seed + 0;
+ U32 v4 = seed - PRIME32_1;
+
+ do
+ {
+ v1 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v1 = XXH_rotl32(v1, 13); v1 *= PRIME32_1; p+=4;
+ v2 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v2 = XXH_rotl32(v2, 13); v2 *= PRIME32_1; p+=4;
+ v3 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v3 = XXH_rotl32(v3, 13); v3 *= PRIME32_1; p+=4;
+ v4 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v4 = XXH_rotl32(v4, 13); v4 *= PRIME32_1; p+=4;
+ } while (p<=limit);
+
+ h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+ }
+ else
+ {
+ h32 = seed + PRIME32_5;
+ }
+
+ h32 += (U32) len;
+
+ while (p<=bEnd-4)
+ {
+ h32 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_3;
+ h32 = XXH_rotl32(h32, 17) * PRIME32_4 ;
+ p+=4;
+ }
+
+ while (p<bEnd)
+ {
+ h32 += (*p) * PRIME32_5;
+ h32 = XXH_rotl32(h32, 11) * PRIME32_1 ;
+ p++;
+ }
+
+ h32 ^= h32 >> 15;
+ h32 *= PRIME32_2;
+ h32 ^= h32 >> 13;
+ h32 *= PRIME32_3;
+ h32 ^= h32 >> 16;
+
+ return h32;
+}
+
+
+U32 XXH32(const void* input, int len, U32 seed)
+{
+#if 0
+ // Simple version, good for code maintenance, but unfortunately slow for small inputs
+ void* state = XXH32_init(seed);
+ XXH32_update(state, input, len);
+ return XXH32_digest(state);
+#else
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+# if !defined(XXH_USE_UNALIGNED_ACCESS)
+ if (!(((size_t)input) & 3)) // Input is aligned, let's leverage the speed advantage
+ {
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);
+ else
+ return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);
+ }
+# endif
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);
+ else
+ return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);
+#endif
+}
+
+
+//****************************
+// Advanced Hash Functions
+//****************************
+
+struct XXH_state32_t
+{
+ U64 total_len;
+ U32 seed;
+ U32 v1;
+ U32 v2;
+ U32 v3;
+ U32 v4;
+ int memsize;
+ char memory[16];
+};
+
+
+int XXH32_sizeofState(void)
+{
+ XXH_STATIC_ASSERT(XXH32_SIZEOFSTATE >= sizeof(struct XXH_state32_t)); // A compilation error here means XXH32_SIZEOFSTATE is not large enough
+ return sizeof(struct XXH_state32_t);
+}
+
+
+XXH_errorcode XXH32_resetState(void* state_in, U32 seed)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ state->seed = seed;
+ state->v1 = seed + PRIME32_1 + PRIME32_2;
+ state->v2 = seed + PRIME32_2;
+ state->v3 = seed + 0;
+ state->v4 = seed - PRIME32_1;
+ state->total_len = 0;
+ state->memsize = 0;
+ return XXH_OK;
+}
+
+
+void* XXH32_init (U32 seed)
+{
+ void* state = XXH_malloc (sizeof(struct XXH_state32_t));
+ XXH32_resetState(state, seed);
+ return state;
+}
+
+
+forceinline XXH_errorcode XXH32_update_endian (void* state_in, const void* input, int len, XXH_endianess endian)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ const BYTE* p = (const BYTE*)input;
+ const BYTE* const bEnd = p + len;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+ if (input==NULL) return XXH_ERROR;
+#endif
+
+ state->total_len += len;
+
+ if (state->memsize + len < 16) // fill in tmp buffer
+ {
+ XXH_memcpy(state->memory + state->memsize, input, len);
+ state->memsize += len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) // some data left from previous update
+ {
+ XXH_memcpy(state->memory + state->memsize, input, 16-state->memsize);
+ {
+ const U32* p32 = (const U32*)state->memory;
+ state->v1 += XXH_readLE32(p32, endian) * PRIME32_2; state->v1 = XXH_rotl32(state->v1, 13); state->v1 *= PRIME32_1; p32++;
+ state->v2 += XXH_readLE32(p32, endian) * PRIME32_2; state->v2 = XXH_rotl32(state->v2, 13); state->v2 *= PRIME32_1; p32++;
+ state->v3 += XXH_readLE32(p32, endian) * PRIME32_2; state->v3 = XXH_rotl32(state->v3, 13); state->v3 *= PRIME32_1; p32++;
+ state->v4 += XXH_readLE32(p32, endian) * PRIME32_2; state->v4 = XXH_rotl32(state->v4, 13); state->v4 *= PRIME32_1; p32++;
+ }
+ p += 16-state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p <= bEnd-16)
+ {
+ const BYTE* const limit = bEnd - 16;
+ U32 v1 = state->v1;
+ U32 v2 = state->v2;
+ U32 v3 = state->v3;
+ U32 v4 = state->v4;
+
+ do
+ {
+ v1 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v1 = XXH_rotl32(v1, 13); v1 *= PRIME32_1; p+=4;
+ v2 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v2 = XXH_rotl32(v2, 13); v2 *= PRIME32_1; p+=4;
+ v3 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v3 = XXH_rotl32(v3, 13); v3 *= PRIME32_1; p+=4;
+ v4 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v4 = XXH_rotl32(v4, 13); v4 *= PRIME32_1; p+=4;
+ } while (p<=limit);
+
+ state->v1 = v1;
+ state->v2 = v2;
+ state->v3 = v3;
+ state->v4 = v4;
+ }
+
+ if (p < bEnd)
+ {
+ XXH_memcpy(state->memory, p, bEnd-p);
+ state->memsize = (int)(bEnd-p);
+ }
+
+ return XXH_OK;
+}
+
+XXH_errorcode XXH32_update (void* state_in, const void* input, int len)
+{
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_update_endian(state_in, input, len, XXH_littleEndian);
+ else
+ return XXH32_update_endian(state_in, input, len, XXH_bigEndian);
+}
+
+
+
+forceinline U32 XXH32_intermediateDigest_endian (void* state_in, XXH_endianess endian)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ const BYTE * p = (const BYTE*)state->memory;
+ BYTE* bEnd = (BYTE*)state->memory + state->memsize;
+ U32 h32;
+
+ if (state->total_len >= 16)
+ {
+ h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18);
+ }
+ else
+ {
+ h32 = state->seed + PRIME32_5;
+ }
+
+ h32 += (U32) state->total_len;
+
+ while (p<=bEnd-4)
+ {
+ h32 += XXH_readLE32((const U32*)p, endian) * PRIME32_3;
+ h32 = XXH_rotl32(h32, 17) * PRIME32_4;
+ p+=4;
+ }
+
+ while (p<bEnd)
+ {
+ h32 += (*p) * PRIME32_5;
+ h32 = XXH_rotl32(h32, 11) * PRIME32_1;
+ p++;
+ }
+
+ h32 ^= h32 >> 15;
+ h32 *= PRIME32_2;
+ h32 ^= h32 >> 13;
+ h32 *= PRIME32_3;
+ h32 ^= h32 >> 16;
+
+ return h32;
+}
+
+
+U32 XXH32_intermediateDigest (void* state_in)
+{
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_intermediateDigest_endian(state_in, XXH_littleEndian);
+ else
+ return XXH32_intermediateDigest_endian(state_in, XXH_bigEndian);
+}
+
+
+U32 XXH32_digest (void* state_in)
+{
+ U32 h32 = XXH32_intermediateDigest(state_in);
+
+ XXH_free(state_in);
+
+ return h32;
+}
diff --git a/contrib/libucl/xxhash.h b/contrib/libucl/xxhash.h
new file mode 100644
index 000000000..b9f528828
--- /dev/null
+++ b/contrib/libucl/xxhash.h
@@ -0,0 +1,164 @@
+/*
+ xxHash - Fast Hash algorithm
+ Header File
+ Copyright (C) 2012-2013, Yann Collet.
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - xxHash source repository : http://code.google.com/p/xxhash/
+*/
+
+/* Notice extracted from xxHash homepage :
+
+xxHash is an extremely fast Hash algorithm, running at RAM speed limits.
+It also successfully passes all tests from the SMHasher suite.
+
+Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)
+
+Name Speed Q.Score Author
+xxHash 5.4 GB/s 10
+CrapWow 3.2 GB/s 2 Andrew
+MumurHash 3a 2.7 GB/s 10 Austin Appleby
+SpookyHash 2.0 GB/s 10 Bob Jenkins
+SBox 1.4 GB/s 9 Bret Mulvey
+Lookup3 1.2 GB/s 9 Bob Jenkins
+SuperFastHash 1.2 GB/s 1 Paul Hsieh
+CityHash64 1.05 GB/s 10 Pike & Alakuijala
+FNV 0.55 GB/s 5 Fowler, Noll, Vo
+CRC32 0.43 GB/s 9
+MD5-32 0.33 GB/s 10 Ronald L. Rivest
+SHA1-32 0.28 GB/s 10
+
+Q.Score is a measure of quality of the hash function.
+It depends on successfully passing SMHasher test set.
+10 is a perfect score.
+*/
+
+#pragma once
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+//****************************
+// Type
+//****************************
+typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
+
+
+
+//****************************
+// Simple Hash Functions
+//****************************
+
+unsigned int XXH32 (const void* input, int len, unsigned int seed);
+
+/*
+XXH32() :
+ Calculate the 32-bits hash of sequence of length "len" stored at memory address "input".
+ The memory between input & input+len must be valid (allocated and read-accessible).
+ "seed" can be used to alter the result predictably.
+ This function successfully passes all SMHasher tests.
+ Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s
+ Note that "len" is type "int", which means it is limited to 2^31-1.
+ If your data is larger, use the advanced functions below.
+*/
+
+
+
+//****************************
+// Advanced Hash Functions
+//****************************
+
+void* XXH32_init (unsigned int seed);
+XXH_errorcode XXH32_update (void* state, const void* input, int len);
+unsigned int XXH32_digest (void* state);
+
+/*
+These functions calculate the xxhash of an input provided in several small packets,
+as opposed to an input provided as a single block.
+
+It must be started with :
+void* XXH32_init()
+The function returns a pointer which holds the state of calculation.
+
+This pointer must be provided as "void* state" parameter for XXH32_update().
+XXH32_update() can be called as many times as necessary.
+The user must provide a valid (allocated) input.
+The function returns an error code, with 0 meaning OK, and any other value meaning there is an error.
+Note that "len" is type "int", which means it is limited to 2^31-1.
+If your data is larger, it is recommended to chunk your data into blocks
+of size for example 2^30 (1GB) to avoid any "int" overflow issue.
+
+Finally, you can end the calculation anytime, by using XXH32_digest().
+This function returns the final 32-bits hash.
+You must provide the same "void* state" parameter created by XXH32_init().
+Memory will be freed by XXH32_digest().
+*/
+
+
+int XXH32_sizeofState(void);
+XXH_errorcode XXH32_resetState(void* state, unsigned int seed);
+
+#define XXH32_SIZEOFSTATE 48
+typedef struct { long long ll[(XXH32_SIZEOFSTATE+(sizeof(long long)-1))/sizeof(long long)]; } XXH32_stateSpace_t;
+/*
+These functions allow user application to make its own allocation for state.
+
+XXH32_sizeofState() is used to know how much space must be allocated for the xxHash 32-bits state.
+Note that the state must be aligned to access 'long long' fields. Memory must be allocated and referenced by a pointer.
+This pointer must then be provided as 'state' into XXH32_resetState(), which initializes the state.
+
+For static allocation purposes (such as allocation on stack, or freestanding systems without malloc()),
+use the structure XXH32_stateSpace_t, which will ensure that memory space is large enough and correctly aligned to access 'long long' fields.
+*/
+
+
+unsigned int XXH32_intermediateDigest (void* state);
+/*
+This function does the same as XXH32_digest(), generating a 32-bit hash,
+but preserve memory context.
+This way, it becomes possible to generate intermediate hashes, and then continue feeding data with XXH32_update().
+To free memory context, use XXH32_digest(), or free().
+*/
+
+
+
+//****************************
+// Deprecated function names
+//****************************
+// The following translations are provided to ease code transition
+// You are encouraged to no longer this function names
+#define XXH32_feed XXH32_update
+#define XXH32_result XXH32_digest
+#define XXH32_getIntermediateResult XXH32_intermediateDigest
+
+
+
+#if defined (__cplusplus)
+}
+#endif