diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2020-02-11 15:51:29 +0100 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2020-02-11 15:51:29 +0100 |
commit | 38d71b4e92d60b751ada2ee326acc22707aa5900 (patch) | |
tree | 94bdf2a2265d4f1e6f533d972c85816c71f4aeb0 /src | |
download | ping903-38d71b4e92d60b751ada2ee326acc22707aa5900.tar.gz ping903-38d71b4e92d60b751ada2ee326acc22707aa5900.tar.bz2 |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 32 | ||||
-rw-r--r-- | src/config.c | 314 | ||||
-rw-r--r-- | src/json.c | 898 | ||||
-rw-r--r-- | src/json.h | 86 | ||||
-rw-r--r-- | src/logger.c | 135 | ||||
-rw-r--r-- | src/main.c | 297 | ||||
-rw-r--r-- | src/ping903.c | 409 | ||||
-rw-r--r-- | src/ping903.h | 131 | ||||
-rw-r--r-- | src/pinger.c | 568 | ||||
-rw-r--r-- | src/remoteip.c | 379 | ||||
-rw-r--r-- | src/wrapacl.c | 21 |
12 files changed, 3271 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..deb4593 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/ping903 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..981856f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,32 @@ +# This file is part of Ping903 +# Copyright (C) 2020 Sergey Poznyakoff +# +# Ping903 is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# Ping903 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ping903. If not, see <http://www.gnu.org/licenses/>. + +sbin_PROGRAMS=ping903 +ping903_SOURCES=\ + main.c\ + logger.c\ + ping903.c\ + ping903.h\ + pinger.c\ + config.c\ + remoteip.c\ + json.c\ + json.h +if COND_TCPD + ping903_SOURCES += wrapacl.c +endif +AM_YFLAGS = -dtv + diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..26d858d --- /dev/null +++ b/src/config.c @@ -0,0 +1,314 @@ +/* This file is part of Ping903 + Copyright (C) 2020 Sergey Poznyakoff + + Ping903 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + Ping903 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Ping903. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include "ping903.h" +#include "json.h" + +static int +get_num(char const *str, unsigned long *retval, + char const *file, unsigned line) +{ + unsigned long n; + char *p; + errno = 0; + n = strtoul(str, &p, 10); + if (errno || *p) { + error("%s:%d: invalid numeric value", file, line); + return -1; + } + *retval = n; + return 0; +} + +static int +cf_ip_list(int mode, union cf_callback_arg *arg, void *data) +{ + FILE *fp; + char buf[1024]; + unsigned ln = 0; + int skip_to_eol = 0; + int ret = CF_RET_OK; + struct addrinfo hints; + + if (mode != CF_PARSE) + return CF_RET_IGNORE; + + fp = fopen(arg->input.val, "r"); + if (!fp) { + error("%s:%d: can't open file %s: %s", + arg->input.file, arg->input.line, + arg->input.val, strerror(errno)); + exit(1); + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_protocol = IPPROTO_TCP; + + while (fgets(buf, sizeof(buf), fp)) { + int len; + char *p; + struct addrinfo *res; + int rc; + + ln++; + len = strlen(buf); + if (len == 0) + continue; + + if (skip_to_eol) { + skip_to_eol = buf[len-1] != '\n'; + continue; + } + + if (buf[len-1] == '\n') + buf[--len] = 0; + else if (!feof(fp)) { + error("%s:%d: line too long", arg->input.val, ln); + skip_to_eol = 1; + ret = CF_RET_FAIL; + continue; + } + + while (len > 0 && isspace(buf[len-1])) + buf[--len] = 0; + + if (len == 0) + continue; + for (p = buf; (*p == ' ' || *p == '\t'); p++) + ; + if (*p == 0 || *p == '#') + continue; + + rc = getaddrinfo(p, NULL, &hints, &res); + if (rc) { + error("%s:%d: invalid IP address", arg->input.val, ln); + ret = CF_RET_FAIL; + continue; + } + if (hostaddr_count == hostaddr_max) { + hostaddr = e2nrealloc(hostaddr, + &hostaddr_max, + sizeof(hostaddr[0])); + } + memset(&hostaddr[hostaddr_count], 0, sizeof(hostaddr[0])); + hostaddr[hostaddr_count].name = estrdup(p); + hostaddr[hostaddr_count].addr = emalloc(res->ai_addrlen); + memcpy(hostaddr[hostaddr_count].addr, res->ai_addr, res->ai_addrlen); + hostaddr[hostaddr_count].addrlen = res->ai_addrlen; + hostaddr_count++; + freeaddrinfo(res); + } + fclose(fp); + return ret; +} + +struct cf_stmt { + char const *kw; + int type; + void *data; + CF_CALLBACK callback; +}; + +struct cf_stmt statements[] = { + { "listen", STMT_T_STRING, &httpd_addr }, + { "pidfile", STMT_T_STRING, &pidfile }, + { "trusted-ip", STMT_T_CALLBACK, NULL, cf_trusted_ip }, + { "interval", STMT_T_ULONG, &ping_interval }, + { "ping-count", STMT_T_ULONG, &ping_count }, + { "tolerance", STMT_T_ULONG, &ping_tolerance }, + { "ip-list", STMT_T_CALLBACK, NULL, cf_ip_list }, + { "data-length", STMT_T_ULONG, &data_length }, + { NULL } +}; + +static struct cf_stmt * +find_stmt(char const *kw) +{ + struct cf_stmt *cf; + for (cf = statements; cf->kw; cf++) { + if (strcmp(cf->kw, kw) == 0) + return cf; + } + return NULL; +} + +static int +stmt_parse(char const *kw, char const *val, char const *file, unsigned line) +{ + struct cf_stmt *cf = find_stmt(kw); + union cf_callback_arg arg; + + if (!cf) { + error("%s:%d: unrecognized keyword", file, line); + return -1; + } + + switch (cf->type) { + case STMT_T_STRING: + *(char**)cf->data = estrdup(val); + break; + + case STMT_T_ULONG: + return get_num(val, cf->data, file, line); + + case STMT_T_BOOL: + abort(); + + case STMT_T_CALLBACK: + arg.input.val = val; + arg.input.file = file; + arg.input.line = line; + return cf->callback(CF_PARSE, &arg, cf->data); + + default: + abort(); + } + return 0; +} + +int +readconfig(char const *file) +{ + FILE *fp; + char buf[1024]; + unsigned ln = 0; + int skip_to_eol = 0; + int ec = 0; + + fp = fopen(file, "r"); + if (!fp) { + error("can't open file %s: %s", file, + strerror(errno)); + exit(1); + } + + while (fgets(buf, sizeof(buf), fp)) { + int len; + char *kw, *p; + + ln++; + len = strlen(buf); + if (len == 0) + continue; + + if (skip_to_eol) { + skip_to_eol = buf[len-1] != '\n'; + continue; + } + + if (buf[len-1] == '\n') + buf[--len] = 0; + else if (!feof(fp)) { + error("%s:%d: line too long", file, ln); + skip_to_eol = 1; + ec = 1; + continue; + } + + while (len > 0 && isspace(buf[len-1])) + buf[--len] = 0; + + if (len == 0) + continue; + + for (p = buf; (*p == ' ' || *p == '\t'); p++) + ; + if (*p == 0 || *p == '#') + continue; + kw = p; + + while (*p && isascii(*p) && (isalnum(*p) || *p == '_' || *p == '-')) + p++; + if (*p) + *p++ = 0; + while (*p == ' ' || *p == '\t') + p++; + + if (!*p) { + error("%s:%d: malformed statement", file, ln); + ec = 1; + continue; + } + + if (stmt_parse(kw, p, file, ln)) { + ec = 1; + } + } + fclose(fp); + return ec; +} + +struct json_value * +config_to_json(void) +{ + struct cf_stmt *cf; + struct json_value *obj = json_new_object(); + struct json_value *vn; + union cf_callback_arg arg; + + if (!obj) + goto err; + + for (cf = statements; cf->kw; cf++) { + switch (cf->type) { + case STMT_T_STRING: + if (*(char**)cf->data == NULL) + vn = json_new_null(); + else + vn = json_new_string(*(char**)cf->data); + break; + + case STMT_T_ULONG: + vn = json_new_number(*(unsigned long*)cf->data); + break; + + case STMT_T_BOOL: + abort(); + + case STMT_T_CALLBACK: + arg.output = NULL; + switch (cf->callback(CF_SERIALIZE, &arg, cf->data)) { + case CF_RET_OK: + vn = arg.output; + break; + + case CF_RET_IGNORE: + continue; + + default: + goto err; + } + } + if (json_object_set(obj, cf->kw, vn)) { + json_value_free(vn); + goto err; + } + } + return obj; + err: + json_value_free(obj); + return NULL; +} diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..370d830 --- /dev/null +++ b/src/json.c @@ -0,0 +1,898 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <math.h> +#include "json.h" + +void * +json_2nrealloc(void *p, size_t *pn, size_t s) +{ + size_t n = *pn; + char *newp; + + if (!p) { + if (!n) { + /* The approximate size to use for initial small + allocation requests, when the invoking code + specifies an old size of zero. 64 bytes is + the largest "small" request for the + GNU C library malloc. */ + enum { DEFAULT_MXFAST = 64 }; + + n = DEFAULT_MXFAST / s; + n += !n; + } + } else { + /* Set N = ceil (1.5 * N) so that progress is made if N == 1. + Check for overflow, so that N * S stays in size_t range. + The check is slightly conservative, but an exact check isn't + worth the trouble. */ + if ((size_t) -1 / 3 * 2 / s <= n) { + errno = ENOMEM; + return NULL; + } + n += (n + 1) / 2; + } + + newp = realloc(p, n * s); + if (!newp) + return NULL; + *pn = n; + return newp; +} + +static void +json_format_writez(struct json_format *fmt, char const *str) +{ + size_t len = strlen(str); + fmt->write(fmt->data, str, len); +} + +static void +json_format_writec(struct json_format *fmt, char c) +{ + fmt->write(fmt->data, &c, 1); +} + +static void +json_format_indent(struct json_format *fmt, size_t level) +{ + level *= fmt->indent; + while (level--) + json_format_writec(fmt, ' '); +} + +static void +json_format_delim(struct json_format *fmt, size_t level) +{ + json_format_writec(fmt, ','); + if (fmt->indent) { + json_format_writec(fmt, '\n'); + json_format_indent(fmt, level); + } else + json_format_writec(fmt, ' '); +} + +/* General-purpose */ +struct json_value * +json_value_create(int type) +{ + struct json_value *obj = calloc(1, sizeof(*obj)); + if (obj) + obj->type = type; + return obj; +} + +typedef void (*json_format_fun)(struct json_format *, struct json_value *, size_t); +typedef void (*json_free_fun)(struct json_value *); + +static void json_format_null(struct json_format *, struct json_value *, + size_t level); +static void json_format_bool(struct json_format *, struct json_value *, + size_t level); +static void json_format_number(struct json_format *, struct json_value *, + size_t level); +static void json_format_string(struct json_format *, struct json_value *, + size_t level); +static void json_format_array(struct json_format *, struct json_value *, + size_t level); +static void json_format_object(struct json_format *, struct json_value *, + size_t level); + + +static void json_free_string(struct json_value *); +static void json_free_array(struct json_value *); +static void json_free_object(struct json_value *); + +static struct json_value_meth { + json_format_fun format_fun; + json_free_fun free_fun; +} json_value_meth[] = { + [json_null] = { json_format_null, NULL }, + [json_bool] = { json_format_bool, NULL }, + [json_number] = { json_format_number, NULL }, + [json_string] = { json_format_string, json_free_string }, + [json_array] = { json_format_array, json_free_array }, + [json_object] = { json_format_object, json_free_object } +}; + +static inline int +is_valid_value(struct json_value *obj) +{ + return obj + && obj->type >= 0 + && obj->type < sizeof(json_value_meth)/sizeof(json_value_meth[0]); +} + +void +json_value_free(struct json_value *obj) +{ + if (is_valid_value(obj)) { + if (json_value_meth[obj->type].free_fun) + json_value_meth[obj->type].free_fun(obj); + free(obj); + } +} + +int +json_value_format(struct json_value *obj, struct json_format *fmt, size_t level) +{ + ++level; + if (!obj) { + json_format_null(fmt, obj, level); + return 0; + } + if (is_valid_value(obj)) { + json_value_meth[obj->type].format_fun(fmt, obj, level); + return 0; + } + errno = EINVAL; + return -1; +} + +/* Null value */ +struct json_value * +json_new_null(void) +{ + return json_value_create(json_null); +} + +static void +json_format_null(struct json_format *fmt, struct json_value *val, size_t level) +{ + json_format_writez(fmt, "null"); +} + +/* Bool value */ +struct json_value * +json_new_bool(int b) +{ + struct json_value *j = json_value_create(json_bool); + j->v.b = b; + return j; +} + +static void +json_format_bool(struct json_format *fmt, struct json_value *val, size_t level) +{ + json_format_writez(fmt, val->v.b ? "true" : "false"); +} + +/* Number value */ +struct json_value * +json_new_number(double n) +{ + struct json_value *j = json_value_create(json_number); + if (j) + j->v.n = n; + return j; +} + +static void +json_format_number(struct json_format *fmt, struct json_value *val, size_t level) +{ + char buffer[128];//FIXME + if (fmt->precision == -1) + snprintf(buffer, sizeof buffer, "%e", val->v.n); + else + snprintf(buffer, sizeof buffer, "%.*f", fmt->precision, + val->v.n); + json_format_writez(fmt, buffer); +} + +/* String value */ +struct json_value * +json_new_string(char const *str) +{ + struct json_value *j = json_value_create(json_string); + j->v.s = strdup(str); + if (!j->v.s) { + free(j); + j = NULL; + } + return j; +} + +static void +json_free_string(struct json_value *val) +{ + free(val->v.s); +} + +enum { ESCAPE, UNESCAPE }; + +static int +escape(char c, char *o, int un) +{ + static char transtab[] = "\\\\\"\"b\bf\fn\nr\rt\t//"; + char *p; + + for (p = transtab; p[2]; p += 2) { + if (p[!un] == c) { + *o = p[un]; + return 0; + } + } + return -1; +} + +static void +json_format_escape(struct json_format *fmt, char const *s) +{ + json_format_writec(fmt, '"'); + for (; *s; s++) { + char c; + if (!escape(*s, &c, ESCAPE)) { + json_format_writec(fmt, '\\'); + json_format_writec(fmt, c); + } else + json_format_writec(fmt, *s); + } + json_format_writec(fmt, '"'); +} + +static void +json_format_string(struct json_format *fmt, struct json_value *val, size_t level) +{ + json_format_escape(fmt, val->v.s); +} + +/* Array value */ +struct json_value * +json_new_array(void) +{ + struct json_value *j = json_value_create(json_array); + j->v.a = malloc(sizeof(*j->v.a)); + if (j->v.a) { + j->v.a->oc = 0; + j->v.a->on = 0; + j->v.a->ov = NULL; + } else { + free(j); + j = NULL; + } + return j; +} + +static void +json_free_array(struct json_value *val) +{ + size_t i; + + for (i = 0; i < val->v.a->oc; i++) + json_value_free(val->v.a->ov[i]); + free(val->v.a->ov); + free(val->v.a); +} + +int +json_array_expand(struct json_value *jv) +{ + size_t i; + size_t n = jv->v.a->on; + struct json_value **p = json_2nrealloc(jv->v.a->ov, + &jv->v.a->on, + sizeof(jv->v.a->ov[0])); + if (!p) + return -1; + jv->v.a->ov = p; + for (i = jv->v.a->on; i < n; i++) + jv->v.a->ov[i] = NULL; + return 0; +} + +int +json_array_insert(struct json_value *jv, size_t idx, struct json_value *v) +{ + if (jv->type != json_array) { + errno = EINVAL; + return -1; + } + + if (jv->v.a->oc <= idx) { + while (jv->v.a->on <= idx) { + if (json_array_expand(jv)) + return -1; + } + jv->v.a->oc = idx + 1; + } + jv->v.a->ov[idx] = v; + return 0; +} + +int +json_array_append(struct json_value *jv, struct json_value *v) +{ + if (jv->type != json_array) { + errno = EINVAL; + return -1; + } + return json_array_insert(jv, jv->v.a->oc, v); +} + +int +json_array_set(struct json_value *jv, size_t idx, struct json_value *v) +{ + if (jv->type != json_array) { + errno = EINVAL; + return -1; + } + if (idx >= json_array_length(jv)) { + errno = ENOENT; + return -1; + } + jv->v.a->ov[idx] = v; + return 0; +} + +int +json_array_get(struct json_value *jv, size_t idx, struct json_value **retval) +{ + if (jv->type != json_array) { + errno = EINVAL; + return -1; + } + if (idx >= json_array_length(jv)) { + errno = ENOENT; + return -1; + } + *retval = jv->v.a->ov[idx]; + return 0; +} + +static void +json_format_array(struct json_format *fmt, struct json_value *obj, + size_t level) +{ + size_t i; + + json_format_writec(fmt, '['); + if (obj->v.a->oc) { + if (fmt->indent) + json_format_writec(fmt, '\n'); + for (i = 0; i < obj->v.a->oc; i++) { + (i ? json_format_delim + : json_format_indent)(fmt, level); + json_value_format(obj->v.a->ov[i], fmt, level); + } + if (fmt->indent) { + json_format_writec(fmt, '\n'); + json_format_indent(fmt, level-1); + } + } + json_format_writec(fmt, ']'); +} + +/* Object value */ +struct json_value * +json_new_object(void) +{ + struct json_value *jv = json_value_create(json_object); + if (!(jv->v.o = calloc(1, sizeof(*jv->v.o)))) { + free(jv); + return NULL; + } + return jv; +} + +static void +json_free_object(struct json_value *val) +{ + struct json_pair *p; + + p = val->v.o->head; + while (p) { + struct json_pair *next = p->next; + free(p->k); + json_value_free(p->v); + p = next; + } +} + +static int +json_object_lookup_or_install(struct json_object *obj, char const *name, + int install, + struct json_pair **retval) +{ + struct json_pair *l, *m; + size_t i, n, count; + + if (obj->count == 0) + l = NULL; + else { + l = obj->head; + + if (strcmp(l->k, name) > 0) { + l = NULL; + } else if (strcmp(obj->tail->k, name) < 0) { + l = obj->tail; + } else { + count = obj->count; + while (count) { + int c; + + n = count / 2; + for (m = l, i = 0; i < n; m = m->next, i++) + ; + + c = strcmp(m->k, name); + if (c == 0) { + *retval = m; + return 0; + } else if (n == 0) { + break; + } else if (c < 0) { + l = m; + count -= n; + } else { + count = n; + } + } + } + } + + if (!install) { + errno = ENOENT; + return -1; + } + + m = malloc(sizeof(*m)); + if (!m) + return -1; + m->next = NULL; + m->k = strdup(name); + if (!m->k) { + free(m); + return -1; + } + m->v = NULL; + + if (!l) { + if (obj->head == NULL) + obj->head = obj->tail = m; + else { + m->next = obj->head; + obj->head = m; + } + } else { + while (l->next && strcmp(l->next->k, name) < 0) + l = l->next; + + m->next = l->next; + l->next = m; + if (!m->next) + obj->tail = m; + } + obj->count++; + + *retval = m; + return 0; +} + +int +json_object_set(struct json_value *obj, char const *name, + struct json_value *val) +{ + struct json_pair *p; + int res; + + if (obj->type != json_object) { + errno = EINVAL; + return -1; + } + + res = json_object_lookup_or_install(obj->v.o, name, 1, &p); + if (res) + return res; + + json_value_free(p->v); + p->v = val; + return 0; +} + +int +json_object_get(struct json_value *obj, char const *name, + struct json_value **retval) +{ + struct json_pair *p; + int res; + + if (obj->type != json_object) { + errno = EINVAL; + return -1; + } + + res = json_object_lookup_or_install(obj->v.o, name, 0, &p); + if (res) + return res; + *retval = p->v; + return 0; +} + +static void +json_format_object(struct json_format *fmt, struct json_value *obj, + size_t level) +{ + struct json_object *op = obj->v.o; + struct json_pair *p; + json_format_writec(fmt, '{'); + if (op->count) { + if (fmt->indent) + json_format_writec(fmt, '\n'); + for (p = op->head; p; p = p->next) { + (p == op->head + ? json_format_indent + : json_format_delim)(fmt, level); + json_format_escape(fmt, p->k); + json_format_writec(fmt, ':'); + if (fmt->indent) + json_format_writec(fmt, ' '); + json_value_format(p->v, fmt, level); + } + if (fmt->indent) { + json_format_writec(fmt, '\n'); + json_format_indent(fmt, level-1); + } + } + json_format_writec(fmt, '}'); +} + +/* Parser */ +#define ISSPACE(c) ((c)==' '||(c)=='\t'||(c)=='\n'||(c)=='\r') +#define ISHEX(c) (strchr("0123456789abcdefABCDEF", c) != NULL) +#define SKIPWS(s) while (*(s) && ISSPACE(*(s))) (s)++; + +struct j_context { + struct j_context *next; + struct json_value *obj; + char *key; +}; + +static inline int +j_context_type(struct j_context *ctx) +{ + return ctx ? ctx->obj->type : json_null; +} + +static int +j_context_push(struct j_context **ctx, int type) +{ + struct j_context *cur = malloc(sizeof(*cur)); + switch (type) { + case json_array: + cur->obj = json_new_array(); + break; + case json_object: + cur->obj = json_new_object(); + break; + default: + abort(); + } + if (!cur->obj) { + free(cur); + return JSON_E_NOMEM; + } + cur->next = *ctx; + cur->key = NULL; + *ctx = cur; + return JSON_E_NOERR; +} + +static struct json_value * +j_context_pop(struct j_context **ctx) +{ + struct json_value *val; + struct j_context *next; + + if (!*ctx) + return NULL; + val = (*ctx)->obj; + free((*ctx)->key); + next = (*ctx)->next; + free(*ctx); + *ctx = next; + + return val; +} + +static int +utf8_wctomb(char const *u, char r[6]) +{ + unsigned int wc = strtoul(u, NULL, 16); + int count; + + if (wc < 0x80) + count = 1; + else if (wc < 0x800) + count = 2; + else if (wc < 0x10000) + count = 3; + else if (wc < 0x200000) + count = 4; + else if (wc < 0x4000000) + count = 5; + else if (wc <= 0x7fffffff) + count = 6; + else + return -1; + + switch (count) { + /* Note: code falls through cases! */ + case 6: + r[5] = 0x80 | (wc & 0x3f); + wc = wc >> 6; + wc |= 0x4000000; + case 5: + r[4] = 0x80 | (wc & 0x3f); + wc = wc >> 6; + wc |= 0x200000; + case 4: + r[3] = 0x80 | (wc & 0x3f); + wc = wc >> 6; + wc |= 0x10000; + case 3: + r[2] = 0x80 | (wc & 0x3f); + wc = wc >> 6; + wc |= 0x800; + case 2: + r[1] = 0x80 | (wc & 0x3f); + wc = wc >> 6; + wc |= 0xc0; + case 1: + r[0] = wc; + } + return count; +} + +static int +j_get_text(char const *input, char **retval, char const **endp) +{ + size_t len; + char const *p; + char *q; + char *str; + + len = 1; + ++input; + for (p = input; *p != '"'; p++) { + if (*p == '\\') { + if (ISHEX(p[1]) + && ISHEX(p[2]) + && ISHEX(p[3]) + && ISHEX(p[4])) { + char r[6]; + int n = utf8_wctomb(p, r); + if (n == -1) { + *endp = p + 1; + return JSON_E_BADSTRING; + } + p += 5; + len += n; + continue; + } else { + p++; + } + } + if (*p == 0) + return JSON_E_BADSTRING; + len++; + } + + str = malloc(len); + if (!str) + return JSON_E_NOMEM; + + p = input; + q = str; + while (*p != '"') { + if (*p == '\\') { + p++; + if (escape(*p, q, UNESCAPE) == 0) { + q++; + p++; + } else if (ISHEX(p[0]) + && ISHEX(p[1]) + && ISHEX(p[2]) + && ISHEX(p[3])) { + char r[6]; + int n = utf8_wctomb(p, r); + memcpy(q, r, n); + p += 4; + q += n; + } else { + *q++ = *p++; + } + } else { + *q++ = *p++; + } + } + *q++ = 0; + *endp = p + 1; + *retval = str; + return 0; +} + +int +json_parse_string(char const *input, struct json_value **retval, char **endp) +{ + struct j_context *ctx = NULL; + struct json_value *val; + int ecode = JSON_E_NOERR; + char *str; + + while (1) { + val = NULL; + + SKIPWS(input); + switch (*input) { + case '[': + j_context_push(&ctx, json_array); + ++input; + continue; + + case ']': + if (j_context_type(ctx) == json_array) { + val = j_context_pop(&ctx); + } else { + ecode = JSON_E_BADTOK; + goto err; + } + ++input; + break; + + case '{': + j_context_push(&ctx, json_object); + ++input; + continue; + + case '}': + if (j_context_type(ctx) == json_object) { + val = j_context_pop(&ctx); + } else { + ecode = JSON_E_BADTOK; + goto err; + } + ++input; + break; + + case '"': + ecode = j_get_text(input, &str, &input); + if (ecode != JSON_E_NOERR) + goto err; + if (j_context_type(ctx) == json_object && !ctx->key) { + ctx->key = str; + + SKIPWS(input); + if (*input == ':') { + ++input; + continue; + } + + ecode = JSON_E_BADDELIM; + goto err; + } else { + val = json_value_create(json_string); + if (!val) + goto err; + val->v.s = str; + } + break; + + case '-': + case '+': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + char *p; + double d = strtod(input, &p); + if ((input[0] == '-' && d == -HUGE_VAL) + || d == HUGE_VAL) { + ecode = JSON_E_BADTOK; + goto err; + } + val = json_new_number(d); + if (!val) { + ecode = JSON_E_NOMEM; + goto err; + } + input = p; + break; + } + + default: + if (strncmp(input, "null", 4) == 0) { + val = json_new_null(); + input += 4; + } else if (strncmp(input, "true", 4) == 0) { + val = json_new_bool(1); + input += 4; + } else if (strncmp(input, "false", 5) == 0) { + val = json_new_bool(0); + input += 5; + } else { + ecode = JSON_E_BADTOK; + goto err; + } + if (!val) { + ecode = JSON_E_NOMEM; + goto err; + } + } + + if (val) { + if (ctx) { + int rc; + + switch (j_context_type(ctx)) { + case json_array: + rc = json_array_append(ctx->obj, val); + break; + + case json_object: + rc = json_object_set(ctx->obj, + ctx->key, val); + free(ctx->key); + ctx->key = NULL; + break; + + default: + abort(); + } + if (rc) { + json_value_free(val); + ecode = JSON_E_NOMEM; + goto err; + } + } else { + *retval = val; + *endp = (char*) input; + return 0; + } + } + + SKIPWS(input); + if (*input == ',') { + ++input; + } else if (*input == ']' || *input == '}') { + continue; + } else { + ecode = JSON_E_BADDELIM; + goto err; + } + } + +err: + while (j_context_pop(&ctx)) + ; + *endp = (char*) input; + return ecode; +} diff --git a/src/json.h b/src/json.h new file mode 100644 index 0000000..8f98368 --- /dev/null +++ b/src/json.h @@ -0,0 +1,86 @@ +#include <stdlib.h> + +enum json_value_type { + json_null, + json_bool, + json_number, + json_string, + json_array, + json_object |