| /* 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(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(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; |
| } |
| |
| /**************************************************************************************************/ |
| /* 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 = apr_table_get(res->headers, "content-type"); |
| if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) { |
| rv = md_json_readb(pjson, pool, res->body); |
| } |
| 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; |
| } |