blob: e0f977ea564b5dbf021e9b610d98cd3cec0afc35 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <apr_lib.h>
#include <apr_strings.h>
#include <apr_buckets.h>
#include <apr_date.h>
#include "md_json.h"
#include "md_log.h"
#include "md_http.h"
#include "md_time.h"
#include "md_util.h"
/* jansson thinks everyone compiles with the platform's cc in its fullest capabilities
* when undefining their INLINEs, we get static, unused functions, arg
*/
#if defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunreachable-code"
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#endif
#include <jansson_config.h>
#undef JSON_INLINE
#define JSON_INLINE
#include <jansson.h>
#if defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#elif defined(__clang__)
#pragma clang diagnostic pop
#endif
struct md_json_t {
apr_pool_t *p;
json_t *j;
};
/**************************************************************************************************/
/* lifecycle */
static apr_status_t json_pool_cleanup(void *data)
{
md_json_t *json = data;
if (json) {
md_json_destroy(json);
}
return APR_SUCCESS;
}
static md_json_t *json_create(apr_pool_t *pool, json_t *j)
{
md_json_t *json;
if (!j) {
apr_abortfunc_t abfn = apr_pool_abort_get(pool);
if (abfn) {
abfn(APR_ENOMEM);
}
assert(j != NULL); /* failsafe in case abort is unset */
}
json = apr_pcalloc(pool, sizeof(*json));
json->p = pool;
json->j = j;
apr_pool_cleanup_register(pool, json, json_pool_cleanup, apr_pool_cleanup_null);
return json;
}
md_json_t *md_json_create(apr_pool_t *pool)
{
return json_create(pool, json_object());
}
md_json_t *md_json_create_s(apr_pool_t *pool, const char *s)
{
return json_create(pool, json_string(s));
}
void md_json_destroy(md_json_t *json)
{
if (json && json->j) {
assert(json->j->refcount > 0);
json_decref(json->j);
json->j = NULL;
}
}
md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json)
{
return json_create(pool, json_copy(json->j));
}
md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json)
{
return json_create(pool, json_deep_copy(json->j));
}
/**************************************************************************************************/
/* selectors */
static json_t *jselect(const md_json_t *json, va_list ap)
{
json_t *j;
const char *key;
j = json->j;
key = va_arg(ap, char *);
while (key && j) {
j = json_object_get(j, key);
key = va_arg(ap, char *);
}
return j;
}
static json_t *jselect_parent(const char **child_key, int create, md_json_t *json, va_list ap)
{
const char *key, *next;
json_t *j, *jn;
*child_key = NULL;
j = json->j;
key = va_arg(ap, char *);
while (key && j) {
next = va_arg(ap, char *);
if (next) {
jn = json_object_get(j, key);
if (!jn && create) {
jn = json_object();
json_object_set_new(j, key, jn);
}
j = jn;
}
else {
*child_key = key;
}
key = next;
}
return j;
}
static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
{
const char *key;
json_t *j, *aj;
j = jselect_parent(&key, 1, json, ap);
if (!j || !json_is_object(j)) {
return APR_EINVAL;
}
aj = json_object_get(j, key);
if (!aj) {
aj = json_array();
json_object_set_new(j, key, aj);
}
if (!json_is_array(aj)) {
return APR_EINVAL;
}
json_array_append(aj, val);
return APR_SUCCESS;
}
static apr_status_t jselect_insert(json_t *val, size_t index, md_json_t *json, va_list ap)
{
const char *key;
json_t *j, *aj;
j = jselect_parent(&key, 1, json, ap);
if (!j || !json_is_object(j)) {
json_decref(val);
return APR_EINVAL;
}
aj = json_object_get(j, key);
if (!aj) {
aj = json_array();
json_object_set_new(j, key, aj);
}
if (!json_is_array(aj)) {
json_decref(val);
return APR_EINVAL;
}
if (json_array_size(aj) <= index) {
json_array_append(aj, val);
}
else {
json_array_insert(aj, index, val);
}
return APR_SUCCESS;
}
static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
{
const char *key;
json_t *j;
j = jselect_parent(&key, 1, json, ap);
if (!j) {
return APR_EINVAL;
}
if (key) {
if (!json_is_object(j)) {
return APR_EINVAL;
}
json_object_set(j, key, val);
}
else {
/* replace */
if (json->j) {
json_decref(json->j);
}
json_incref(val);
json->j = val;
}
return APR_SUCCESS;
}
static apr_status_t jselect_set_new(json_t *val, md_json_t *json, va_list ap)
{
const char *key;
json_t *j;
j = jselect_parent(&key, 1, json, ap);
if (!j) {
json_decref(val);
return APR_EINVAL;
}
if (key) {
if (!json_is_object(j)) {
json_decref(val);
return APR_EINVAL;
}
json_object_set_new(j, key, val);
}
else {
/* replace */
if (json->j) {
json_decref(json->j);
}
json->j = val;
}
return APR_SUCCESS;
}
int md_json_has_key(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return j != NULL;
}
/**************************************************************************************************/
/* type things */
int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
switch (jtype) {
case MD_JSON_TYPE_OBJECT: return (j && json_is_object(j));
case MD_JSON_TYPE_ARRAY: return (j && json_is_array(j));
case MD_JSON_TYPE_STRING: return (j && json_is_string(j));
case MD_JSON_TYPE_REAL: return (j && json_is_real(j));
case MD_JSON_TYPE_INT: return (j && json_is_integer(j));
case MD_JSON_TYPE_BOOL: return (j && (json_is_true(j) || json_is_false(j)));
case MD_JSON_TYPE_NULL: return (j == NULL);
}
return 0;
}
static const char *md_json_type_name(const md_json_t *json)
{
json_t *j = json->j;
if (json_is_object(j)) return "object";
if (json_is_array(j)) return "array";
if (json_is_string(j)) return "string";
if (json_is_real(j)) return "real";
if (json_is_integer(j)) return "integer";
if (json_is_true(j)) return "true";
if (json_is_false(j)) return "false";
return "unknown";
}
/**************************************************************************************************/
/* booleans */
int md_json_getb(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return j? json_is_true(j) : 0;
}
apr_status_t md_json_setb(int value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_set_new(json_boolean(value), json, ap);
va_end(ap);
return rv;
}
/**************************************************************************************************/
/* numbers */
double md_json_getn(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return (j && json_is_number(j))? json_number_value(j) : 0.0;
}
apr_status_t md_json_setn(double value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_set_new(json_real(value), json, ap);
va_end(ap);
return rv;
}
/**************************************************************************************************/
/* longs */
long md_json_getl(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return (long)((j && json_is_number(j))? json_integer_value(j) : 0L);
}
apr_status_t md_json_setl(long value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_set_new(json_integer(value), json, ap);
va_end(ap);
return rv;
}
/**************************************************************************************************/
/* strings */
const char *md_json_gets(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return (j && json_is_string(j))? json_string_value(j) : NULL;
}
const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
return (j && json_is_string(j))? apr_pstrdup(p, json_string_value(j)) : NULL;
}
apr_status_t md_json_sets(const char *value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_set_new(json_string(value), json, ap);
va_end(ap);
return rv;
}
/**************************************************************************************************/
/* time */
apr_time_t md_json_get_time(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_string(j)) return 0;
return apr_date_parse_rfc(json_string_value(j));
}
apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...)
{
char ts[APR_RFC822_DATE_LEN];
va_list ap;
apr_status_t rv;
apr_rfc822_date(ts, value);
va_start(ap, json);
rv = jselect_set_new(json_string(ts), json, ap);
va_end(ap);
return rv;
}
/**************************************************************************************************/
/* json itself */
md_json_t *md_json_getj(md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j) {
if (j == json->j) {
return json;
}
json_incref(j);
return json_create(json->p, j);
}
return NULL;
}
md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j) {
json_incref(j);
return json_create(p, j);
}
return NULL;
}
const md_json_t *md_json_getcj(const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j) {
if (j == json->j) {
return json;
}
json_incref(j);
return json_create(json->p, j);
}
return NULL;
}
apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
const char *key;
json_t *j;
if (value) {
va_start(ap, json);
rv = jselect_set(value->j, json, ap);
va_end(ap);
}
else {
va_start(ap, json);
j = jselect_parent(&key, 1, json, ap);
va_end(ap);
if (key && j && !json_is_object(j)) {
json_object_del(j, key);
rv = APR_SUCCESS;
}
else {
rv = APR_EINVAL;
}
}
return rv;
}
apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_add(value->j, json, ap);
va_end(ap);
return rv;
}
apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
va_start(ap, json);
rv = jselect_insert(value->j, index, json, ap);
va_end(ap);
return rv;
}
apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...)
{
json_t *j;
va_list ap;
apr_size_t n = 0;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j && json_is_array(j)) {
n = json_array_size(j);
while (n > max_elements) {
json_array_remove(j, n-1);
n = json_array_size(j);
}
}
return n;
}
/**************************************************************************************************/
/* arrays / objects */
apr_status_t md_json_clr(md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j && json_is_object(j)) {
json_object_clear(j);
}
else if (j && json_is_array(j)) {
json_array_clear(j);
}
return APR_SUCCESS;
}
apr_status_t md_json_del(md_json_t *json, ...)
{
const char *key;
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect_parent(&key, 0, json, ap);
va_end(ap);
if (key && j && json_is_object(j)) {
json_object_del(j, key);
}
return APR_SUCCESS;
}
/**************************************************************************************************/
/* object strings */
apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j && json_is_object(j)) {
const char *key;
json_t *val;
json_object_foreach(j, key, val) {
if (json_is_string(val)) {
apr_table_set(dict, key, json_string_value(val));
}
}
return APR_SUCCESS;
}
return APR_ENOENT;
}
static int object_set(void *data, const char *key, const char *val)
{
json_t *j = data, *nj = json_string(val);
json_object_set(j, key, nj);
json_decref(nj);
return 1;
}
apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...)
{
json_t *nj, *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_object(j)) {
const char *key;
va_start(ap, json);
j = jselect_parent(&key, 1, json, ap);
va_end(ap);
if (!key || !j || !json_is_object(j)) {
return APR_EINVAL;
}
nj = json_object();
json_object_set_new(j, key, nj);
j = nj;
}
apr_table_do(object_set, j, dict, NULL);
return APR_SUCCESS;
}
/**************************************************************************************************/
/* conversions */
apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
(void)p;
(void)baton;
return md_json_setj(value, json, NULL);
}
apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
{
(void)p;
(void)baton;
*pvalue = json;
return APR_SUCCESS;
}
apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
(void)baton;
return md_json_setj(md_json_clone(p, value), json, NULL);
}
apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton)
{
(void)baton;
*pvalue = md_json_clone(p, json);
return APR_SUCCESS;
}
/**************************************************************************************************/
/* array generic */
apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, void *baton,
const md_json_t *json, ...)
{
json_t *j;
va_list ap;
apr_status_t rv = APR_SUCCESS;
size_t index;
json_t *val;
md_json_t wrap;
void *element;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_array(j)) {
return APR_ENOENT;
}
wrap.p = a->pool;
json_array_foreach(j, index, val) {
wrap.j = val;
if (APR_SUCCESS == (rv = cb(&element, &wrap, wrap.p, baton))) {
if (element) {
APR_ARRAY_PUSH(a, void*) = element;
}
}
else if (APR_ENOENT == rv) {
rv = APR_SUCCESS;
}
else {
break;
}
}
return rv;
}
apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, void *baton,
md_json_t *json, ...)
{
json_t *j, *nj;
md_json_t wrap;
apr_status_t rv = APR_SUCCESS;
va_list ap;
int i;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_array(j)) {
const char *key;
va_start(ap, json);
j = jselect_parent(&key, 1, json, ap);
va_end(ap);
if (!key || !j || !json_is_object(j)) {
return APR_EINVAL;
}
nj = json_array();
json_object_set_new(j, key, nj);
j = nj;
}
json_array_clear(j);
wrap.p = json->p;
for (i = 0; i < a->nelts; ++i) {
if (!cb) {
return APR_EINVAL;
}
wrap.j = json_string("");
if (APR_SUCCESS == (rv = cb(APR_ARRAY_IDX(a, i, void*), &wrap, json->p, baton))) {
json_array_append_new(j, wrap.j);
}
}
return rv;
}
int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
{
json_t *j;
va_list ap;
size_t index;
json_t *val;
md_json_t wrap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_array(j)) {
return 0;
}
wrap.p = json->p;
json_array_foreach(j, index, val) {
wrap.j = val;
if (!cb(baton, index, &wrap)) {
return 0;
}
}
return 1;
}
int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...)
{
json_t *j;
va_list ap;
const char *key;
json_t *val;
md_json_t wrap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_object(j)) {
return 0;
}
wrap.p = json->p;
json_object_foreach(j, key, val) {
wrap.j = val;
if (!cb(baton, key, &wrap)) {
return 0;
}
}
return 1;
}
/**************************************************************************************************/
/* array strings */
apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j && json_is_array(j)) {
size_t index;
json_t *val;
json_array_foreach(j, index, val) {
if (json_is_string(val)) {
APR_ARRAY_PUSH(a, const char *) = json_string_value(val);
}
}
return APR_SUCCESS;
}
return APR_ENOENT;
}
apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...)
{
json_t *j;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (j && json_is_array(j)) {
size_t index;
json_t *val;
apr_array_clear(a);
json_array_foreach(j, index, val) {
if (json_is_string(val)) {
APR_ARRAY_PUSH(a, const char *) = apr_pstrdup(p, json_string_value(val));
}
}
return APR_SUCCESS;
}
return APR_ENOENT;
}
apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
{
json_t *nj, *j;
va_list ap;
int i;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
if (!j || !json_is_array(j)) {
const char *key;
va_start(ap, json);
j = jselect_parent(&key, 1, json, ap);
va_end(ap);
if (!key || !j || !json_is_object(j)) {
return APR_EINVAL;
}
nj = json_array();
json_object_set_new(j, key, nj);
j = nj;
}
json_array_clear(j);
for (i = 0; i < a->nelts; ++i) {
json_array_append_new(j, json_string(APR_ARRAY_IDX(a, i, const char*)));
}
return APR_SUCCESS;
}
/**************************************************************************************************/
/* formatting, parsing */
typedef struct {
const md_json_t *json;
md_json_fmt_t fmt;
const char *fname;
apr_file_t *f;
} j_write_ctx;
/* Convert from md_json_fmt_t to the Jansson json_dumpX flags. */
static size_t fmt_to_flags(md_json_fmt_t fmt)
{
/* NOTE: JSON_PRESERVE_ORDER is off by default before Jansson 2.8. It
* doesn't have any semantic effect on the protocol, but it does let the
* md_json_writeX unit tests run deterministically. */
return JSON_PRESERVE_ORDER |
((fmt == MD_JSON_FMT_COMPACT) ? JSON_COMPACT : JSON_INDENT(2));
}
static int dump_cb(const char *buffer, size_t len, void *baton)
{
apr_bucket_brigade *bb = baton;
apr_status_t rv;
rv = apr_brigade_write(bb, NULL, NULL, buffer, len);
return (rv == APR_SUCCESS)? 0 : -1;
}
apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
{
int rv = json_dump_callback(json->j, dump_cb, bb, fmt_to_flags(fmt));
return rv? APR_EGENERAL : APR_SUCCESS;
}
static int chunk_cb(const char *buffer, size_t len, void *baton)
{
apr_array_header_t *chunks = baton;
char *chunk;
if (len > 0) {
chunk = apr_palloc(chunks->pool, len+1);
memcpy(chunk, buffer, len);
chunk[len] = '\0';
APR_ARRAY_PUSH(chunks, const char*) = chunk;
}
return 0;
}
const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
{
apr_array_header_t *chunks;
int rv;
chunks = apr_array_make(p, 10, sizeof(char *));
rv = json_dump_callback(json->j, chunk_cb, chunks, fmt_to_flags(fmt));
if (APR_SUCCESS != rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
"md_json_writep failed to dump JSON");
return NULL;
}
switch (chunks->nelts) {
case 0:
return "";
case 1:
return APR_ARRAY_IDX(chunks, 0, const char*);
default:
return apr_array_pstrcat(p, chunks, 0);
}
}
apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
{
apr_status_t rv;
const char *s;
if ((s = md_json_writep(json, p, fmt))) {
rv = apr_file_write_full(f, s, strlen(s), NULL);
if (APR_SUCCESS != rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error writing file");
}
}
else {
rv = APR_EINVAL;
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p,
"md_json_writef: error dumping json (%s)", md_json_dump_state(json, p));
}
return rv;
}
apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms)
{
apr_status_t rv;
apr_file_t *f;
rv = md_util_fcreatex(&f, fpath, perms, p);
if (APR_SUCCESS == rv) {
rv = md_json_writef(json, p, fmt, f);
apr_file_close(f);
}
return rv;
}
static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
{
j_write_ctx *ctx = baton;
apr_status_t rv = md_json_writef(ctx->json, p, ctx->fmt, f);
if (APR_SUCCESS != rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "freplace json in %s", ctx->fname);
}
return rv;
}
apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms)
{
j_write_ctx ctx;
ctx.json = json;
ctx.fmt = fmt;
ctx.fname = fpath;
return md_util_freplace(fpath, perms, p, write_json, &ctx);
}
apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len)
{
json_error_t error;
json_t *j;
j = json_loadb(data, data_len, 0, &error);
if (!j) {
return APR_EINVAL;
}
*pjson = json_create(pool, j);
return APR_SUCCESS;
}
static size_t load_cb(void *data, size_t max_len, void *baton)
{
apr_bucket_brigade *body = baton;
size_t blen, read_len = 0;
const char *bdata;
char *dest = data;
apr_bucket *b;
apr_status_t rv;
while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
b = APR_BRIGADE_FIRST(body);
if (APR_BUCKET_IS_METADATA(b)) {
if (APR_BUCKET_IS_EOS(b)) {
body = NULL;
}
}
else {
rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
if (rv == APR_SUCCESS) {
if (blen > max_len) {
apr_bucket_split(b, max_len);
blen = max_len;
}
memcpy(dest, bdata, blen);
read_len += blen;
max_len -= blen;
dest += blen;
}
else {
body = NULL;
if (!APR_STATUS_IS_EOF(rv)) {
/* everything beside EOF is an error */
read_len = (size_t)-1;
}
}
}
APR_BUCKET_REMOVE(b);
apr_bucket_delete(b);
}
return read_len;
}
apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, apr_bucket_brigade *bb)
{
json_error_t error;
json_t *j;
j = json_load_callback(load_cb, bb, 0, &error);
if (j) {
*pjson = json_create(pool, j);
} else {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, pool,
"failed to load JSON file: %s (line %d:%d)",
error.text, error.line, error.column);
}
return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
}
static size_t load_file_cb(void *data, size_t max_len, void *baton)
{
apr_file_t *f = baton;
apr_size_t len = max_len;
apr_status_t rv;
rv = apr_file_read(f, data, &len);
if (APR_SUCCESS == rv) {
return len;
}
else if (APR_EOF == rv) {
return 0;
}
return (size_t)-1;
}
apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
{
apr_file_t *f;
json_t *j;
apr_status_t rv;
json_error_t error;
rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p);
if (rv != APR_SUCCESS) {
return rv;
}
j = json_load_callback(load_file_cb, f, 0, &error);
if (j) {
*pjson = json_create(p, j);
}
else {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
"failed to load JSON file %s: %s (line %d:%d)",
fpath, error.text, error.line, error.column);
}
apr_file_close(f);
return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
}
/**************************************************************************************************/
/* http get */
apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
{
apr_status_t rv = APR_ENOENT;
const char *ctype, *p;
*pjson = NULL;
if (!res->body) goto cleanup;
ctype = md_util_parse_ct(res->req->pool, apr_table_get(res->headers, "content-type"));
if (!ctype) goto cleanup;
p = ctype + strlen(ctype) +1;
if (!strcmp(p - sizeof("/json"), "/json")
|| !strcmp(p - sizeof("+json"), "+json")) {
rv = md_json_readb(pjson, pool, res->body);
}
cleanup:
return rv;
}
typedef struct {
apr_status_t rv;
apr_pool_t *pool;
md_json_t *json;
} resp_data;
static apr_status_t json_resp_cb(const md_http_response_t *res, void *data)
{
resp_data *resp = data;
return md_json_read_http(&resp->json, resp->pool, res);
}
apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
struct md_http_t *http, const char *url)
{
apr_status_t rv;
resp_data resp;
memset(&resp, 0, sizeof(resp));
resp.pool = pool;
rv = md_http_GET_perform(http, url, NULL, json_resp_cb, &resp);
if (rv == APR_SUCCESS) {
*pjson = resp.json;
return resp.rv;
}
*pjson = NULL;
return rv;
}
apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
{
json_t *j;
va_list ap;
apr_status_t rv = APR_SUCCESS;
va_start(ap, src);
j = jselect(src, ap);
va_end(ap);
if (j) {
va_start(ap, src);
rv = jselect_set(j, dest, ap);
va_end(ap);
}
return rv;
}
const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p)
{
if (!json) return "NULL";
return apr_psprintf(p, "%s, refc=%ld", md_json_type_name(json), (long)json->j->refcount);
}
apr_status_t md_json_set_timeperiod(const md_timeperiod_t *tp, md_json_t *json, ...)
{
char ts[APR_RFC822_DATE_LEN];
json_t *jn, *j;
va_list ap;
const char *key;
apr_status_t rv;
if (tp && tp->start && tp->end) {
jn = json_object();
apr_rfc822_date(ts, tp->start);
json_object_set_new(jn, "from", json_string(ts));
apr_rfc822_date(ts, tp->end);
json_object_set_new(jn, "until", json_string(ts));
va_start(ap, json);
rv = jselect_set_new(jn, json, ap);
va_end(ap);
return rv;
}
else {
va_start(ap, json);
j = jselect_parent(&key, 0, json, ap);
va_end(ap);
if (key && j && json_is_object(j)) {
json_object_del(j, key);
}
return APR_SUCCESS;
}
}
apr_status_t md_json_get_timeperiod(md_timeperiod_t *tp, md_json_t *json, ...)
{
json_t *j, *jts;
va_list ap;
va_start(ap, json);
j = jselect(json, ap);
va_end(ap);
memset(tp, 0, sizeof(*tp));
if (!j) goto not_found;
jts = json_object_get(j, "from");
if (!jts || !json_is_string(jts)) goto not_found;
tp->start = apr_date_parse_rfc(json_string_value(jts));
jts = json_object_get(j, "until");
if (!jts || !json_is_string(jts)) goto not_found;
tp->end = apr_date_parse_rfc(json_string_value(jts));
return APR_SUCCESS;
not_found:
return APR_ENOENT;
}