diff options
Diffstat (limited to 'contrib')
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, + ¯o_start, ¯o_len)) { + parser->prev_state = parser->state; + parser->state = UCL_STATE_ERROR; + return false; + } + macro_len = ucl_expand_variable (parser, ¯o_escaped, + macro_start, macro_len); + parser->state = parser->prev_state; + if (macro_escaped == NULL) { + 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 (®, pattern, REG_EXTENDED | REG_NOSUB) == 0) { + while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { + if (regexec (®, ucl_object_key (elt), 0, NULL, 0) == 0) { + res = elt; + break; + } + } + regfree (®); + } +#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
|