Reviewed-by: Alejandro Colomar <alx@nginx.com> Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2648 lines
58 KiB
C
2648 lines
58 KiB
C
|
|
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) Valentin V. Bartenev
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
#include <nxt_conf.h>
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
|
|
|
|
#define NXT_CONF_MAX_SHORT_STRING 14
|
|
#define NXT_CONF_MAX_NUMBER_LEN 14
|
|
#define NXT_CONF_MAX_STRING NXT_INT32_T_MAX
|
|
|
|
#define NXT_CONF_MAX_TOKEN_LEN 256
|
|
|
|
|
|
typedef enum {
|
|
NXT_CONF_VALUE_NULL = 0,
|
|
NXT_CONF_VALUE_BOOLEAN,
|
|
NXT_CONF_VALUE_INTEGER,
|
|
NXT_CONF_VALUE_NUMBER,
|
|
NXT_CONF_VALUE_SHORT_STRING,
|
|
NXT_CONF_VALUE_STRING,
|
|
NXT_CONF_VALUE_ARRAY,
|
|
NXT_CONF_VALUE_OBJECT,
|
|
} nxt_conf_value_type_t;
|
|
|
|
|
|
typedef enum {
|
|
NXT_CONF_OP_PASS = 0,
|
|
NXT_CONF_OP_CREATE,
|
|
NXT_CONF_OP_REPLACE,
|
|
NXT_CONF_OP_DELETE,
|
|
} nxt_conf_op_action_t;
|
|
|
|
|
|
typedef struct nxt_conf_array_s nxt_conf_array_t;
|
|
typedef struct nxt_conf_object_s nxt_conf_object_t;
|
|
|
|
|
|
struct nxt_conf_value_s {
|
|
union {
|
|
uint8_t boolean; /* 1 bit. */
|
|
u_char number[NXT_CONF_MAX_NUMBER_LEN + 1];
|
|
|
|
struct {
|
|
u_char start[NXT_CONF_MAX_SHORT_STRING];
|
|
uint8_t length;
|
|
} str;
|
|
|
|
struct {
|
|
u_char *start;
|
|
uint32_t length;
|
|
} nxt_packed string;
|
|
|
|
nxt_conf_array_t *array;
|
|
nxt_conf_object_t *object;
|
|
} nxt_packed u;
|
|
|
|
uint8_t type; /* 3 bits. */
|
|
} nxt_aligned(8);
|
|
|
|
|
|
struct nxt_conf_array_s {
|
|
nxt_uint_t count;
|
|
nxt_conf_value_t elements[];
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
nxt_conf_value_t name;
|
|
nxt_conf_value_t value;
|
|
} nxt_conf_object_member_t;
|
|
|
|
|
|
struct nxt_conf_object_s {
|
|
nxt_uint_t count;
|
|
nxt_conf_object_member_t members[];
|
|
};
|
|
|
|
|
|
struct nxt_conf_op_s {
|
|
uint32_t index;
|
|
uint32_t action; /* nxt_conf_op_action_t */
|
|
void *ctx;
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
u_char *start;
|
|
u_char *end;
|
|
nxt_bool_t last;
|
|
u_char buf[NXT_CONF_MAX_TOKEN_LEN];
|
|
} nxt_conf_path_parse_t;
|
|
|
|
|
|
static nxt_int_t nxt_conf_path_next_token(nxt_conf_path_parse_t *parse,
|
|
nxt_str_t *token);
|
|
|
|
static u_char *nxt_conf_json_skip_space(u_char *start, const u_char *end);
|
|
static u_char *nxt_conf_json_parse_value(nxt_mp_t *mp, nxt_conf_value_t *value,
|
|
u_char *start, u_char *end, nxt_conf_json_error_t *error);
|
|
static u_char *nxt_conf_json_parse_object(nxt_mp_t *mp, nxt_conf_value_t *value,
|
|
u_char *start, u_char *end, nxt_conf_json_error_t *error);
|
|
static nxt_int_t nxt_conf_object_hash_add(nxt_mp_t *mp,
|
|
nxt_lvlhsh_t *lvlhsh, nxt_conf_object_member_t *member);
|
|
static nxt_int_t nxt_conf_object_hash_test(nxt_lvlhsh_query_t *lhq,
|
|
void *data);
|
|
static void *nxt_conf_object_hash_alloc(void *data, size_t size);
|
|
static void nxt_conf_object_hash_free(void *data, void *p);
|
|
static u_char *nxt_conf_json_parse_array(nxt_mp_t *mp, nxt_conf_value_t *value,
|
|
u_char *start, u_char *end, nxt_conf_json_error_t *error);
|
|
static u_char *nxt_conf_json_parse_string(nxt_mp_t *mp, nxt_conf_value_t *value,
|
|
u_char *start, u_char *end, nxt_conf_json_error_t *error);
|
|
static u_char *nxt_conf_json_parse_number(nxt_mp_t *mp, nxt_conf_value_t *value,
|
|
u_char *start, u_char *end, nxt_conf_json_error_t *error);
|
|
static void nxt_conf_json_parse_error(nxt_conf_json_error_t *error, u_char *pos,
|
|
const char *detail);
|
|
|
|
static nxt_int_t nxt_conf_copy_value(nxt_mp_t *mp, nxt_conf_op_t *op,
|
|
nxt_conf_value_t *dst, nxt_conf_value_t *src);
|
|
static nxt_int_t nxt_conf_copy_array(nxt_mp_t *mp, nxt_conf_op_t *op,
|
|
nxt_conf_value_t *dst, nxt_conf_value_t *src);
|
|
static nxt_int_t nxt_conf_copy_object(nxt_mp_t *mp, nxt_conf_op_t *op,
|
|
nxt_conf_value_t *dst, nxt_conf_value_t *src);
|
|
|
|
static size_t nxt_conf_json_string_length(nxt_conf_value_t *value);
|
|
static u_char *nxt_conf_json_print_string(u_char *p, nxt_conf_value_t *value);
|
|
static size_t nxt_conf_json_array_length(nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty);
|
|
static u_char *nxt_conf_json_print_array(u_char *p, nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty);
|
|
static size_t nxt_conf_json_object_length(nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty);
|
|
static u_char *nxt_conf_json_print_object(u_char *p, nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty);
|
|
|
|
static size_t nxt_conf_json_escape_length(u_char *p, size_t size);
|
|
static u_char *nxt_conf_json_escape(u_char *dst, u_char *src, size_t size);
|
|
|
|
|
|
#define nxt_conf_json_newline(p) \
|
|
((p)[0] = '\r', (p)[1] = '\n', (p) + 2)
|
|
|
|
|
|
nxt_inline u_char *
|
|
nxt_conf_json_indentation(u_char *p, uint32_t level)
|
|
{
|
|
while (level) {
|
|
*p++ = '\t';
|
|
level--;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_get_string(nxt_conf_value_t *value, nxt_str_t *str)
|
|
{
|
|
if (value->type == NXT_CONF_VALUE_SHORT_STRING) {
|
|
str->length = value->u.str.length;
|
|
str->start = value->u.str.start;
|
|
|
|
} else {
|
|
str->length = value->u.string.length;
|
|
str->start = value->u.string.start;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_string(nxt_conf_value_t *value, nxt_str_t *str)
|
|
{
|
|
if (str->length > NXT_CONF_MAX_SHORT_STRING) {
|
|
value->type = NXT_CONF_VALUE_STRING;
|
|
value->u.string.length = str->length;
|
|
value->u.string.start = str->start;
|
|
|
|
} else {
|
|
value->type = NXT_CONF_VALUE_SHORT_STRING;
|
|
value->u.str.length = str->length;
|
|
|
|
nxt_memcpy(value->u.str.start, str->start, str->length);
|
|
}
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_conf_set_string_dup(nxt_conf_value_t *value, nxt_mp_t *mp,
|
|
const nxt_str_t *str)
|
|
{
|
|
nxt_str_t tmp, *ptr;
|
|
|
|
if (str->length > NXT_CONF_MAX_SHORT_STRING) {
|
|
value->type = NXT_CONF_VALUE_STRING;
|
|
|
|
ptr = nxt_str_dup(mp, &tmp, str);
|
|
if (nxt_slow_path(ptr == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
value->u.string.length = tmp.length;
|
|
value->u.string.start = tmp.start;
|
|
|
|
} else {
|
|
value->type = NXT_CONF_VALUE_SHORT_STRING;
|
|
value->u.str.length = str->length;
|
|
|
|
nxt_memcpy(value->u.str.start, str->start, str->length);
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
double
|
|
nxt_conf_get_number(nxt_conf_value_t *value)
|
|
{
|
|
return nxt_strtod(value->u.number, NULL);
|
|
}
|
|
|
|
|
|
uint8_t
|
|
nxt_conf_get_boolean(nxt_conf_value_t *value)
|
|
{
|
|
return value->u.boolean;
|
|
}
|
|
|
|
|
|
nxt_uint_t
|
|
nxt_conf_object_members_count(nxt_conf_value_t *value)
|
|
{
|
|
return value->u.object->count;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_create_object(nxt_mp_t *mp, nxt_uint_t count)
|
|
{
|
|
size_t size;
|
|
nxt_conf_value_t *value;
|
|
|
|
size = sizeof(nxt_conf_value_t)
|
|
+ sizeof(nxt_conf_object_t)
|
|
+ count * sizeof(nxt_conf_object_member_t);
|
|
|
|
value = nxt_mp_get(mp, size);
|
|
if (nxt_slow_path(value == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
value->u.object = nxt_pointer_to(value, sizeof(nxt_conf_value_t));
|
|
value->u.object->count = count;
|
|
|
|
value->type = NXT_CONF_VALUE_OBJECT;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_member(nxt_conf_value_t *object, nxt_str_t *name,
|
|
const nxt_conf_value_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
nxt_conf_set_string(&member->name, name);
|
|
|
|
member->value = *value;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_conf_set_member_dup(nxt_conf_value_t *object, nxt_mp_t *mp, nxt_str_t *name,
|
|
nxt_conf_value_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
member->value = *value;
|
|
|
|
return nxt_conf_set_string_dup(&member->name, mp, name);
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_member_string(nxt_conf_value_t *object, nxt_str_t *name,
|
|
nxt_str_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
nxt_conf_set_string(&member->name, name);
|
|
|
|
nxt_conf_set_string(&member->value, value);
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_conf_set_member_string_dup(nxt_conf_value_t *object, nxt_mp_t *mp,
|
|
nxt_str_t *name, nxt_str_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
nxt_conf_set_string(&member->name, name);
|
|
|
|
return nxt_conf_set_string_dup(&member->value, mp, value);
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_member_integer(nxt_conf_value_t *object, nxt_str_t *name,
|
|
int64_t value, uint32_t index)
|
|
{
|
|
u_char *p, *end;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
nxt_conf_set_string(&member->name, name);
|
|
|
|
p = member->value.u.number;
|
|
end = p + NXT_CONF_MAX_NUMBER_LEN;
|
|
|
|
end = nxt_sprintf(p, end, "%L", value);
|
|
*end = '\0';
|
|
|
|
member->value.type = NXT_CONF_VALUE_INTEGER;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_member_null(nxt_conf_value_t *object, nxt_str_t *name,
|
|
uint32_t index)
|
|
{
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = &object->u.object->members[index];
|
|
|
|
nxt_conf_set_string(&member->name, name);
|
|
|
|
member->value.type = NXT_CONF_VALUE_NULL;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_create_array(nxt_mp_t *mp, nxt_uint_t count)
|
|
{
|
|
size_t size;
|
|
nxt_conf_value_t *value;
|
|
|
|
size = sizeof(nxt_conf_value_t)
|
|
+ sizeof(nxt_conf_array_t)
|
|
+ count * sizeof(nxt_conf_value_t);
|
|
|
|
value = nxt_mp_get(mp, size);
|
|
if (nxt_slow_path(value == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
value->u.array = nxt_pointer_to(value, sizeof(nxt_conf_value_t));
|
|
value->u.array->count = count;
|
|
|
|
value->type = NXT_CONF_VALUE_ARRAY;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index,
|
|
const nxt_conf_value_t *value)
|
|
{
|
|
array->u.array->elements[index] = *value;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp,
|
|
nxt_uint_t index, nxt_str_t *value)
|
|
{
|
|
nxt_conf_value_t *element;
|
|
|
|
element = &array->u.array->elements[index];
|
|
|
|
return nxt_conf_set_string_dup(element, mp, value);
|
|
}
|
|
|
|
|
|
nxt_uint_t
|
|
nxt_conf_array_elements_count(nxt_conf_value_t *value)
|
|
{
|
|
return value->u.array->count;
|
|
}
|
|
|
|
|
|
nxt_uint_t
|
|
nxt_conf_array_elements_count_or_1(nxt_conf_value_t *value)
|
|
{
|
|
return (value->type == NXT_CONF_VALUE_ARRAY) ? value->u.array->count : 1;
|
|
}
|
|
|
|
|
|
nxt_uint_t
|
|
nxt_conf_type(nxt_conf_value_t *value)
|
|
{
|
|
switch (value->type) {
|
|
|
|
case NXT_CONF_VALUE_NULL:
|
|
return NXT_CONF_NULL;
|
|
|
|
case NXT_CONF_VALUE_BOOLEAN:
|
|
return NXT_CONF_BOOLEAN;
|
|
|
|
case NXT_CONF_VALUE_INTEGER:
|
|
return NXT_CONF_INTEGER;
|
|
|
|
case NXT_CONF_VALUE_NUMBER:
|
|
return NXT_CONF_NUMBER;
|
|
|
|
case NXT_CONF_VALUE_SHORT_STRING:
|
|
case NXT_CONF_VALUE_STRING:
|
|
return NXT_CONF_STRING;
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
return NXT_CONF_ARRAY;
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
return NXT_CONF_OBJECT;
|
|
}
|
|
|
|
nxt_unreachable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_get_path(nxt_conf_value_t *value, nxt_str_t *path)
|
|
{
|
|
nxt_str_t token;
|
|
nxt_int_t ret, index;
|
|
nxt_conf_path_parse_t parse;
|
|
|
|
parse.start = path->start;
|
|
parse.end = path->start + path->length;
|
|
parse.last = 0;
|
|
|
|
do {
|
|
ret = nxt_conf_path_next_token(&parse, &token);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (token.length == 0) {
|
|
|
|
if (parse.last) {
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
switch (value->type) {
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
value = nxt_conf_get_object_member(value, &token, NULL);
|
|
break;
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
index = nxt_int_parse(token.start, token.length);
|
|
|
|
if (index < 0 || index > NXT_INT32_T_MAX) {
|
|
return NULL;
|
|
}
|
|
|
|
value = nxt_conf_get_array_element(value, index);
|
|
break;
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
} while (parse.last == 0);
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_path_next_token(nxt_conf_path_parse_t *parse, nxt_str_t *token)
|
|
{
|
|
u_char *p, *start, *end;
|
|
size_t length;
|
|
|
|
start = parse->start + 1;
|
|
|
|
p = start;
|
|
|
|
while (p < parse->end && *p != '/') {
|
|
p++;
|
|
}
|
|
|
|
parse->start = p;
|
|
parse->last = (p >= parse->end);
|
|
|
|
length = p - start;
|
|
|
|
if (nxt_slow_path(length > NXT_CONF_MAX_TOKEN_LEN)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
end = nxt_decode_uri(parse->buf, start, length);
|
|
if (nxt_slow_path(end == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
token->length = end - parse->buf;
|
|
token->start = parse->buf;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_get_object_member(nxt_conf_value_t *value, nxt_str_t *name,
|
|
uint32_t *index)
|
|
{
|
|
nxt_str_t str;
|
|
nxt_uint_t n;
|
|
nxt_conf_object_t *object;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
if (value->type != NXT_CONF_VALUE_OBJECT) {
|
|
return NULL;
|
|
}
|
|
|
|
object = value->u.object;
|
|
|
|
for (n = 0; n < object->count; n++) {
|
|
member = &object->members[n];
|
|
|
|
nxt_conf_get_string(&member->name, &str);
|
|
|
|
if (nxt_strstr_eq(&str, name)) {
|
|
|
|
if (index != NULL) {
|
|
*index = n;
|
|
}
|
|
|
|
return &member->value;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_conf_map_object(nxt_mp_t *mp, nxt_conf_value_t *value, nxt_conf_map_t *map,
|
|
nxt_uint_t n, void *data)
|
|
{
|
|
double num;
|
|
nxt_str_t str, *s;
|
|
nxt_uint_t i;
|
|
nxt_conf_value_t *v;
|
|
|
|
union {
|
|
uint8_t ui8;
|
|
int32_t i32;
|
|
int64_t i64;
|
|
int i;
|
|
ssize_t size;
|
|
off_t off;
|
|
nxt_msec_t msec;
|
|
double dbl;
|
|
nxt_str_t str;
|
|
char *cstrz;
|
|
void *v;
|
|
} *ptr;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
v = nxt_conf_get_object_member(value, &map[i].name, NULL);
|
|
|
|
if (v == NULL || v->type == NXT_CONF_VALUE_NULL) {
|
|
continue;
|
|
}
|
|
|
|
ptr = nxt_pointer_to(data, map[i].offset);
|
|
|
|
switch (map[i].type) {
|
|
|
|
case NXT_CONF_MAP_INT8:
|
|
|
|
if (v->type == NXT_CONF_VALUE_BOOLEAN) {
|
|
ptr->ui8 = v->u.boolean;
|
|
}
|
|
|
|
break;
|
|
|
|
case NXT_CONF_MAP_INT32:
|
|
case NXT_CONF_MAP_INT64:
|
|
case NXT_CONF_MAP_INT:
|
|
case NXT_CONF_MAP_SIZE:
|
|
case NXT_CONF_MAP_OFF:
|
|
case NXT_CONF_MAP_MSEC:
|
|
|
|
if (v->type != NXT_CONF_VALUE_INTEGER) {
|
|
break;
|
|
}
|
|
|
|
num = nxt_strtod(v->u.number, NULL);
|
|
|
|
switch (map[i].type) {
|
|
|
|
case NXT_CONF_MAP_INT32:
|
|
ptr->i32 = num;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_INT64:
|
|
ptr->i64 = num;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_INT:
|
|
ptr->i = num;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_SIZE:
|
|
ptr->size = num;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_OFF:
|
|
ptr->off = num;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_MSEC:
|
|
ptr->msec = (nxt_msec_t) num * 1000;
|
|
break;
|
|
|
|
default:
|
|
nxt_unreachable();
|
|
}
|
|
|
|
break;
|
|
|
|
case NXT_CONF_MAP_DOUBLE:
|
|
|
|
if (v->type == NXT_CONF_VALUE_NUMBER) {
|
|
ptr->dbl = nxt_strtod(v->u.number, NULL);
|
|
}
|
|
|
|
break;
|
|
|
|
case NXT_CONF_MAP_STR:
|
|
case NXT_CONF_MAP_STR_COPY:
|
|
case NXT_CONF_MAP_CSTRZ:
|
|
|
|
if (v->type != NXT_CONF_VALUE_SHORT_STRING
|
|
&& v->type != NXT_CONF_VALUE_STRING)
|
|
{
|
|
break;
|
|
}
|
|
|
|
nxt_conf_get_string(v, &str);
|
|
|
|
switch (map[i].type) {
|
|
|
|
case NXT_CONF_MAP_STR:
|
|
ptr->str = str;
|
|
break;
|
|
|
|
case NXT_CONF_MAP_STR_COPY:
|
|
|
|
s = nxt_str_dup(mp, &ptr->str, &str);
|
|
|
|
if (nxt_slow_path(s == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
case NXT_CONF_MAP_CSTRZ:
|
|
|
|
ptr->cstrz = nxt_str_cstrz(mp, &str);
|
|
|
|
if (nxt_slow_path(ptr->cstrz == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
nxt_unreachable();
|
|
}
|
|
|
|
break;
|
|
|
|
case NXT_CONF_MAP_PTR:
|
|
|
|
ptr->v = v;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_next_object_member(nxt_conf_value_t *value, nxt_str_t *name,
|
|
uint32_t *next)
|
|
{
|
|
uint32_t n;
|
|
nxt_conf_object_t *object;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
if (value->type != NXT_CONF_VALUE_OBJECT) {
|
|
return NULL;
|
|
}
|
|
|
|
n = *next;
|
|
object = value->u.object;
|
|
|
|
if (n >= object->count) {
|
|
return NULL;
|
|
}
|
|
|
|
member = &object->members[n];
|
|
*next = n + 1;
|
|
|
|
nxt_conf_get_string(&member->name, name);
|
|
|
|
return &member->value;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_get_array_element(nxt_conf_value_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_array_t *array;
|
|
|
|
if (value->type != NXT_CONF_VALUE_ARRAY) {
|
|
return NULL;
|
|
}
|
|
|
|
array = value->u.array;
|
|
|
|
if (index >= array->count) {
|
|
return NULL;
|
|
}
|
|
|
|
return &array->elements[index];
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_get_array_element_or_itself(nxt_conf_value_t *value, uint32_t index)
|
|
{
|
|
nxt_conf_array_t *array;
|
|
|
|
if (value->type != NXT_CONF_VALUE_ARRAY) {
|
|
return (index == 0) ? value : NULL;
|
|
}
|
|
|
|
array = value->u.array;
|
|
|
|
if (index >= array->count) {
|
|
return NULL;
|
|
}
|
|
|
|
return &array->elements[index];
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_array_qsort(nxt_conf_value_t *value,
|
|
int (*compare)(const void *, const void *))
|
|
{
|
|
nxt_conf_array_t *array;
|
|
|
|
if (value->type != NXT_CONF_VALUE_ARRAY) {
|
|
return;
|
|
}
|
|
|
|
array = value->u.array;
|
|
|
|
nxt_qsort(array->elements, array->count, sizeof(nxt_conf_value_t), compare);
|
|
}
|
|
|
|
|
|
nxt_conf_op_ret_t
|
|
nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root,
|
|
nxt_str_t *path, nxt_conf_value_t *value, nxt_bool_t add)
|
|
{
|
|
nxt_str_t token;
|
|
nxt_int_t ret, index;
|
|
nxt_conf_op_t *op, **parent;
|
|
nxt_conf_value_t *node;
|
|
nxt_conf_path_parse_t parse;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
parse.start = path->start;
|
|
parse.end = path->start + path->length;
|
|
parse.last = 0;
|
|
|
|
parent = ops;
|
|
|
|
for ( ;; ) {
|
|
op = nxt_mp_zget(mp, sizeof(nxt_conf_op_t));
|
|
if (nxt_slow_path(op == NULL)) {
|
|
return NXT_CONF_OP_ERROR;
|
|
}
|
|
|
|
*parent = op;
|
|
parent = (nxt_conf_op_t **) &op->ctx;
|
|
|
|
ret = nxt_conf_path_next_token(&parse, &token);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NXT_CONF_OP_ERROR;
|
|
}
|
|
|
|
switch (root->type) {
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
node = nxt_conf_get_object_member(root, &token, &op->index);
|
|
break;
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
index = nxt_int_parse(token.start, token.length);
|
|
|
|
if (index < 0 || index > NXT_INT32_T_MAX) {
|
|
return NXT_CONF_OP_NOT_FOUND;
|
|
}
|
|
|
|
op->index = index;
|
|
|
|
node = nxt_conf_get_array_element(root, index);
|
|
break;
|
|
|
|
default:
|
|
node = NULL;
|
|
}
|
|
|
|
if (parse.last) {
|
|
break;
|
|
}
|
|
|
|
if (node == NULL) {
|
|
return NXT_CONF_OP_NOT_FOUND;
|
|
}
|
|
|
|
op->action = NXT_CONF_OP_PASS;
|
|
root = node;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
|
|
if (node == NULL) {
|
|
return NXT_CONF_OP_NOT_FOUND;
|
|
}
|
|
|
|
op->action = NXT_CONF_OP_DELETE;
|
|
|
|
return NXT_CONF_OP_OK;
|
|
}
|
|
|
|
if (add) {
|
|
if (node == NULL) {
|
|
return NXT_CONF_OP_NOT_FOUND;
|
|
}
|
|
|
|
if (node->type != NXT_CONF_VALUE_ARRAY) {
|
|
return NXT_CONF_OP_NOT_ALLOWED;
|
|
}
|
|
|
|
op->action = NXT_CONF_OP_PASS;
|
|
|
|
op = nxt_mp_zget(mp, sizeof(nxt_conf_op_t));
|
|
if (nxt_slow_path(op == NULL)) {
|
|
return NXT_CONF_OP_ERROR;
|
|
}
|
|
|
|
*parent = op;
|
|
|
|
op->index = node->u.array->count;
|
|
op->action = NXT_CONF_OP_CREATE;
|
|
op->ctx = value;
|
|
|
|
return NXT_CONF_OP_OK;
|
|
}
|
|
|
|
if (node != NULL) {
|
|
op->action = NXT_CONF_OP_REPLACE;
|
|
op->ctx = value;
|
|
|
|
return NXT_CONF_OP_OK;
|
|
}
|
|
|
|
op->action = NXT_CONF_OP_CREATE;
|
|
|
|
if (root->type == NXT_CONF_VALUE_ARRAY) {
|
|
if (op->index > root->u.array->count) {
|
|
return NXT_CONF_OP_NOT_FOUND;
|
|
}
|
|
|
|
op->ctx = value;
|
|
|
|
} else {
|
|
member = nxt_mp_zget(mp, sizeof(nxt_conf_object_member_t));
|
|
if (nxt_slow_path(member == NULL)) {
|
|
return NXT_CONF_OP_ERROR;
|
|
}
|
|
|
|
ret = nxt_conf_set_string_dup(&member->name, mp, &token);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NXT_CONF_OP_ERROR;
|
|
}
|
|
|
|
member->value = *value;
|
|
|
|
op->index = root->u.object->count;
|
|
op->ctx = member;
|
|
}
|
|
|
|
return NXT_CONF_OP_OK;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_clone(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *value)
|
|
{
|
|
nxt_int_t rc;
|
|
nxt_conf_value_t *copy;
|
|
|
|
copy = nxt_mp_get(mp, sizeof(nxt_conf_value_t));
|
|
if (nxt_slow_path(copy == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
rc = nxt_conf_copy_value(mp, op, copy, value);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NULL;
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_copy_value(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst,
|
|
nxt_conf_value_t *src)
|
|
{
|
|
if (op != NULL
|
|
&& src->type != NXT_CONF_VALUE_ARRAY
|
|
&& src->type != NXT_CONF_VALUE_OBJECT)
|
|
{
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
switch (src->type) {
|
|
|
|
case NXT_CONF_VALUE_STRING:
|
|
|
|
dst->u.string.start = nxt_mp_nget(mp, src->u.string.length);
|
|
if (nxt_slow_path(dst->u.string.start == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
nxt_memcpy(dst->u.string.start, src->u.string.start,
|
|
src->u.string.length);
|
|
|
|
dst->u.string.length = src->u.string.length;
|
|
|
|
break;
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
return nxt_conf_copy_array(mp, op, dst, src);
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
return nxt_conf_copy_object(mp, op, dst, src);
|
|
|
|
default:
|
|
dst->u = src->u;
|
|
}
|
|
|
|
dst->type = src->type;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_copy_array(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst,
|
|
nxt_conf_value_t *src)
|
|
{
|
|
size_t size;
|
|
nxt_int_t rc;
|
|
nxt_uint_t s, d, count, index;
|
|
nxt_conf_op_t *pass_op;
|
|
nxt_conf_value_t *value;
|
|
|
|
count = src->u.array->count;
|
|
|
|
if (op != NULL) {
|
|
if (op->action == NXT_CONF_OP_CREATE) {
|
|
count++;
|
|
|
|
} else if (op->action == NXT_CONF_OP_DELETE) {
|
|
count--;
|
|
}
|
|
}
|
|
|
|
size = sizeof(nxt_conf_array_t) + count * sizeof(nxt_conf_value_t);
|
|
|
|
dst->u.array = nxt_mp_get(mp, size);
|
|
if (nxt_slow_path(dst->u.array == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
dst->u.array->count = count;
|
|
|
|
s = 0;
|
|
d = 0;
|
|
|
|
pass_op = NULL;
|
|
|
|
/*
|
|
* This initialization is needed only to
|
|
* suppress a warning on GCC 4.8 and older.
|
|
*/
|
|
index = 0;
|
|
|
|
do {
|
|
if (pass_op == NULL) {
|
|
index = (op == NULL) ? src->u.array->count : op->index;
|
|
}
|
|
|
|
while (s != index) {
|
|
rc = nxt_conf_copy_value(mp, pass_op, &dst->u.array->elements[d],
|
|
&src->u.array->elements[s]);
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
s++;
|
|
d++;
|
|
}
|
|
|
|
if (pass_op != NULL) {
|
|
pass_op = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (op != NULL) {
|
|
switch (op->action) {
|
|
case NXT_CONF_OP_PASS:
|
|
pass_op = op->ctx;
|
|
index++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_CREATE:
|
|
value = op->ctx;
|
|
dst->u.array->elements[d] = *value;
|
|
|
|
d++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_REPLACE:
|
|
value = op->ctx;
|
|
dst->u.array->elements[d] = *value;
|
|
|
|
s++;
|
|
d++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_DELETE:
|
|
s++;
|
|
break;
|
|
}
|
|
|
|
op = NULL;
|
|
}
|
|
|
|
} while (d != count);
|
|
|
|
dst->type = src->type;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_copy_object(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst,
|
|
nxt_conf_value_t *src)
|
|
{
|
|
size_t size;
|
|
nxt_int_t rc;
|
|
nxt_uint_t s, d, count, index;
|
|
nxt_conf_op_t *pass_op;
|
|
nxt_conf_value_t *value;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
count = src->u.object->count;
|
|
|
|
if (op != NULL) {
|
|
if (op->action == NXT_CONF_OP_CREATE) {
|
|
count++;
|
|
|
|
} else if (op->action == NXT_CONF_OP_DELETE) {
|
|
count--;
|
|
}
|
|
}
|
|
|
|
size = sizeof(nxt_conf_object_t)
|
|
+ count * sizeof(nxt_conf_object_member_t);
|
|
|
|
dst->u.object = nxt_mp_get(mp, size);
|
|
if (nxt_slow_path(dst->u.object == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
dst->u.object->count = count;
|
|
|
|
s = 0;
|
|
d = 0;
|
|
|
|
pass_op = NULL;
|
|
|
|
/*
|
|
* This initialization is needed only to
|
|
* suppress a warning on GCC 4.8 and older.
|
|
*/
|
|
index = 0;
|
|
|
|
do {
|
|
if (pass_op == NULL) {
|
|
index = (op == NULL) ? src->u.object->count : op->index;
|
|
}
|
|
|
|
while (s != index) {
|
|
rc = nxt_conf_copy_value(mp, NULL,
|
|
&dst->u.object->members[d].name,
|
|
&src->u.object->members[s].name);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
rc = nxt_conf_copy_value(mp, pass_op,
|
|
&dst->u.object->members[d].value,
|
|
&src->u.object->members[s].value);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
s++;
|
|
d++;
|
|
}
|
|
|
|
if (pass_op != NULL) {
|
|
pass_op = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (op != NULL) {
|
|
switch (op->action) {
|
|
case NXT_CONF_OP_PASS:
|
|
pass_op = op->ctx;
|
|
index++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_CREATE:
|
|
member = op->ctx;
|
|
|
|
rc = nxt_conf_copy_value(mp, NULL,
|
|
&dst->u.object->members[d].name,
|
|
&member->name);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
dst->u.object->members[d].value = member->value;
|
|
|
|
d++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_REPLACE:
|
|
rc = nxt_conf_copy_value(mp, NULL,
|
|
&dst->u.object->members[d].name,
|
|
&src->u.object->members[s].name);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
value = op->ctx;
|
|
|
|
dst->u.object->members[d].value = *value;
|
|
|
|
s++;
|
|
d++;
|
|
break;
|
|
|
|
case NXT_CONF_OP_DELETE:
|
|
s++;
|
|
break;
|
|
}
|
|
|
|
op = NULL;
|
|
}
|
|
|
|
} while (d != count);
|
|
|
|
dst->type = src->type;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_conf_value_t *
|
|
nxt_conf_json_parse(nxt_mp_t *mp, u_char *start, u_char *end,
|
|
nxt_conf_json_error_t *error)
|
|
{
|
|
u_char *p;
|
|
nxt_conf_value_t *value;
|
|
|
|
value = nxt_mp_get(mp, sizeof(nxt_conf_value_t));
|
|
if (nxt_slow_path(value == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(start, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, start,
|
|
"An empty JSON payload isn't allowed. It must be either a literal "
|
|
"(null, true, or false), a number, a string (in double quotes "
|
|
"\"\"), an array (with brackets []), or an object (with braces {})."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
p = nxt_conf_json_parse_value(mp, value, p, end, error);
|
|
|
|
if (nxt_slow_path(p == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(p, end);
|
|
|
|
if (nxt_slow_path(p != end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected character after the end of a valid JSON value."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_skip_space(u_char *start, const u_char *end)
|
|
{
|
|
u_char *p, ch;
|
|
|
|
enum {
|
|
sw_normal = 0,
|
|
sw_after_slash,
|
|
sw_single_comment,
|
|
sw_multi_comment,
|
|
sw_after_asterisk,
|
|
} state;
|
|
|
|
state = sw_normal;
|
|
|
|
for (p = start; nxt_fast_path(p != end); p++) {
|
|
ch = *p;
|
|
|
|
switch (state) {
|
|
|
|
case sw_normal:
|
|
switch (ch) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
continue;
|
|
case '/':
|
|
start = p;
|
|
state = sw_after_slash;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_after_slash:
|
|
switch (ch) {
|
|
case '/':
|
|
state = sw_single_comment;
|
|
continue;
|
|
case '*':
|
|
state = sw_multi_comment;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_single_comment:
|
|
if (ch == '\n') {
|
|
state = sw_normal;
|
|
}
|
|
|
|
continue;
|
|
|
|
case sw_multi_comment:
|
|
if (ch == '*') {
|
|
state = sw_after_asterisk;
|
|
}
|
|
|
|
continue;
|
|
|
|
case sw_after_asterisk:
|
|
switch (ch) {
|
|
case '/':
|
|
state = sw_normal;
|
|
continue;
|
|
case '*':
|
|
continue;
|
|
}
|
|
|
|
state = sw_multi_comment;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (nxt_slow_path(state != sw_normal)) {
|
|
return start;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_parse_value(nxt_mp_t *mp, nxt_conf_value_t *value, u_char *start,
|
|
u_char *end, nxt_conf_json_error_t *error)
|
|
{
|
|
u_char ch, *p;
|
|
|
|
ch = *start;
|
|
|
|
switch (ch) {
|
|
case '{':
|
|
return nxt_conf_json_parse_object(mp, value, start, end, error);
|
|
|
|
case '[':
|
|
return nxt_conf_json_parse_array(mp, value, start, end, error);
|
|
|
|
case '"':
|
|
return nxt_conf_json_parse_string(mp, value, start, end, error);
|
|
|
|
case 't':
|
|
if (nxt_fast_path(end - start >= 4
|
|
&& memcmp(start, "true", 4) == 0))
|
|
{
|
|
value->u.boolean = 1;
|
|
value->type = NXT_CONF_VALUE_BOOLEAN;
|
|
|
|
return start + 4;
|
|
}
|
|
|
|
goto error;
|
|
|
|
case 'f':
|
|
if (nxt_fast_path(end - start >= 5
|
|
&& memcmp(start, "false", 5) == 0))
|
|
{
|
|
value->u.boolean = 0;
|
|
value->type = NXT_CONF_VALUE_BOOLEAN;
|
|
|
|
return start + 5;
|
|
}
|
|
|
|
goto error;
|
|
|
|
case 'n':
|
|
if (nxt_fast_path(end - start >= 4
|
|
&& memcmp(start, "null", 4) == 0))
|
|
{
|
|
value->type = NXT_CONF_VALUE_NULL;
|
|
return start + 4;
|
|
}
|
|
|
|
goto error;
|
|
|
|
case '-':
|
|
if (nxt_fast_path(end - start > 1)) {
|
|
ch = start[1];
|
|
break;
|
|
}
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (nxt_fast_path((ch - '0') <= 9)) {
|
|
p = nxt_conf_json_parse_number(mp, value, start, end, error);
|
|
|
|
if (nxt_slow_path(p == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (p == end) {
|
|
return end;
|
|
}
|
|
|
|
switch (*p) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
case '\n':
|
|
case ',':
|
|
case '}':
|
|
case ']':
|
|
case '{':
|
|
case '[':
|
|
case '"':
|
|
case '/':
|
|
return p;
|
|
}
|
|
}
|
|
|
|
error:
|
|
|
|
nxt_conf_json_parse_error(error, start,
|
|
"A valid JSON value is expected here. It must be either a literal "
|
|
"(null, true, or false), a number, a string (in double quotes \"\"), "
|
|
"an array (with brackets []), or an object (with braces {})."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const nxt_lvlhsh_proto_t nxt_conf_object_hash_proto
|
|
nxt_aligned(64) =
|
|
{
|
|
NXT_LVLHSH_DEFAULT,
|
|
nxt_conf_object_hash_test,
|
|
nxt_conf_object_hash_alloc,
|
|
nxt_conf_object_hash_free,
|
|
};
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_parse_object(nxt_mp_t *mp, nxt_conf_value_t *value, u_char *start,
|
|
u_char *end, nxt_conf_json_error_t *error)
|
|
{
|
|
u_char *p, *name;
|
|
nxt_mp_t *mp_temp;
|
|
nxt_int_t rc;
|
|
nxt_uint_t count;
|
|
nxt_lvlhsh_t hash;
|
|
nxt_lvlhsh_each_t lhe;
|
|
nxt_conf_object_t *object;
|
|
nxt_conf_object_member_t *member, *element;
|
|
|
|
mp_temp = nxt_mp_create(1024, 128, 256, 32);
|
|
if (nxt_slow_path(mp_temp == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
nxt_lvlhsh_init(&hash);
|
|
|
|
count = 0;
|
|
p = start;
|
|
|
|
for ( ;; ) {
|
|
p = nxt_conf_json_skip_space(p + 1, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an object without "
|
|
"a closing brace (})."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (*p != '"') {
|
|
if (nxt_fast_path(*p == '}')) {
|
|
break;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"A double quote (\") is expected here. There must be a valid "
|
|
"JSON object member starts with a name, which is a string "
|
|
"enclosed in double quotes."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
name = p;
|
|
|
|
count++;
|
|
|
|
member = nxt_mp_get(mp_temp, sizeof(nxt_conf_object_member_t));
|
|
if (nxt_slow_path(member == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_parse_string(mp, &member->name, p, end, error);
|
|
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
rc = nxt_conf_object_hash_add(mp_temp, &hash, member);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
|
|
if (rc == NXT_DECLINED) {
|
|
nxt_conf_json_parse_error(error, name,
|
|
"Duplicate object member. All JSON object members must "
|
|
"have unique names."
|
|
);
|
|
}
|
|
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(p, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an object member "
|
|
"without a value."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (nxt_slow_path(*p != ':')) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"A colon (:) is expected here. There must be a colon after "
|
|
"a JSON member name."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(p + 1, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an object member "
|
|
"without a value."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_parse_value(mp, &member->value, p, end, error);
|
|
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(p, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an object without "
|
|
"a closing brace (})."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (*p != ',') {
|
|
if (nxt_fast_path(*p == '}')) {
|
|
break;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Either a closing brace (}) or a comma (,) is expected here. "
|
|
"Each JSON object must be enclosed in braces and its members "
|
|
"must be separated by commas."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
object = nxt_mp_get(mp, sizeof(nxt_conf_object_t)
|
|
+ count * sizeof(nxt_conf_object_member_t));
|
|
if (nxt_slow_path(object == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
value->u.object = object;
|
|
value->type = NXT_CONF_VALUE_OBJECT;
|
|
|
|
object->count = count;
|
|
member = object->members;
|
|
|
|
nxt_lvlhsh_each_init(&lhe, &nxt_conf_object_hash_proto);
|
|
|
|
for ( ;; ) {
|
|
element = nxt_lvlhsh_each(&hash, &lhe);
|
|
|
|
if (element == NULL) {
|
|
break;
|
|
}
|
|
|
|
*member++ = *element;
|
|
}
|
|
|
|
nxt_mp_destroy(mp_temp);
|
|
|
|
return p + 1;
|
|
|
|
error:
|
|
|
|
nxt_mp_destroy(mp_temp);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_object_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *lvlhsh,
|
|
nxt_conf_object_member_t *member)
|
|
{
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
nxt_conf_get_string(&member->name, &lhq.key);
|
|
|
|
lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
|
|
lhq.replace = 0;
|
|
lhq.value = member;
|
|
lhq.proto = &nxt_conf_object_hash_proto;
|
|
lhq.pool = mp;
|
|
|
|
return nxt_lvlhsh_insert(lvlhsh, &lhq);
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_conf_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
nxt_str_t str;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
member = data;
|
|
|
|
nxt_conf_get_string(&member->name, &str);
|
|
|
|
return nxt_strstr_eq(&lhq->key, &str) ? NXT_OK : NXT_DECLINED;
|
|
}
|
|
|
|
|
|
static void *
|
|
nxt_conf_object_hash_alloc(void *data, size_t size)
|
|
{
|
|
return nxt_mp_align(data, size, size);
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_conf_object_hash_free(void *data, void *p)
|
|
{
|
|
nxt_mp_free(data, p);
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_parse_array(nxt_mp_t *mp, nxt_conf_value_t *value, u_char *start,
|
|
u_char *end, nxt_conf_json_error_t *error)
|
|
{
|
|
u_char *p;
|
|
nxt_mp_t *mp_temp;
|
|
nxt_uint_t count;
|
|
nxt_list_t *list;
|
|
nxt_conf_array_t *array;
|
|
nxt_conf_value_t *element;
|
|
|
|
mp_temp = nxt_mp_create(1024, 128, 256, 32);
|
|
if (nxt_slow_path(mp_temp == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
list = nxt_list_create(mp_temp, 8, sizeof(nxt_conf_value_t));
|
|
if (nxt_slow_path(list == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
count = 0;
|
|
p = start;
|
|
|
|
for ( ;; ) {
|
|
p = nxt_conf_json_skip_space(p + 1, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an array without "
|
|
"a closing bracket (])."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (*p == ']') {
|
|
break;
|
|
}
|
|
|
|
count++;
|
|
|
|
element = nxt_list_add(list);
|
|
if (nxt_slow_path(element == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_parse_value(mp, element, p, end, error);
|
|
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
p = nxt_conf_json_skip_space(p, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's an array without "
|
|
"a closing bracket (])."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (*p != ',') {
|
|
if (nxt_fast_path(*p == ']')) {
|
|
break;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Either a closing bracket (]) or a comma (,) is expected "
|
|
"here. Each array must be enclosed in brackets and its "
|
|
"members must be separated by commas."
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
array = nxt_mp_get(mp, sizeof(nxt_conf_array_t)
|
|
+ count * sizeof(nxt_conf_value_t));
|
|
if (nxt_slow_path(array == NULL)) {
|
|
goto error;
|
|
}
|
|
|
|
value->u.array = array;
|
|
value->type = NXT_CONF_VALUE_ARRAY;
|
|
|
|
array->count = count;
|
|
element = array->elements;
|
|
|
|
nxt_list_each(value, list) {
|
|
*element++ = *value;
|
|
} nxt_list_loop;
|
|
|
|
nxt_mp_destroy(mp_temp);
|
|
|
|
return p + 1;
|
|
|
|
error:
|
|
|
|
nxt_mp_destroy(mp_temp);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_parse_string(nxt_mp_t *mp, nxt_conf_value_t *value, u_char *start,
|
|
u_char *end, nxt_conf_json_error_t *error)
|
|
{
|
|
u_char *p, ch, *last, *s;
|
|
size_t size, surplus;
|
|
uint32_t utf, utf_high;
|
|
nxt_uint_t i;
|
|
enum {
|
|
sw_usual = 0,
|
|
sw_escape,
|
|
sw_encoded1,
|
|
sw_encoded2,
|
|
sw_encoded3,
|
|
sw_encoded4,
|
|
} state;
|
|
|
|
start++;
|
|
|
|
state = 0;
|
|
surplus = 0;
|
|
|
|
for (p = start; nxt_fast_path(p != end); p++) {
|
|
ch = *p;
|
|
|
|
switch (state) {
|
|
|
|
case sw_usual:
|
|
|
|
if (ch == '"') {
|
|
break;
|
|
}
|
|
|
|
if (ch == '\\') {
|
|
state = sw_escape;
|
|
continue;
|
|
}
|
|
|
|
if (nxt_fast_path(ch >= ' ')) {
|
|
continue;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected character. All control characters in a JSON "
|
|
"string must be escaped."
|
|
);
|
|
|
|
return NULL;
|
|
|
|
case sw_escape:
|
|
|
|
switch (ch) {
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
case 'n':
|
|
case 'r':
|
|
case 't':
|
|
case 'b':
|
|
case 'f':
|
|
surplus++;
|
|
state = sw_usual;
|
|
continue;
|
|
|
|
case 'u':
|
|
/*
|
|
* Basic unicode 6 bytes "\uXXXX" in JSON
|
|
* and up to 3 bytes in UTF-8.
|
|
*
|
|
* Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON
|
|
* and 3 or 4 bytes in UTF-8.
|
|
*/
|
|
surplus += 3;
|
|
state = sw_encoded1;
|
|
continue;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p - 1,
|
|
"Unexpected backslash. A literal backslash in a JSON string "
|
|
"must be escaped with a second backslash (\\\\)."
|
|
);
|
|
|
|
return NULL;
|
|
|
|
case sw_encoded1:
|
|
case sw_encoded2:
|
|
case sw_encoded3:
|
|
case sw_encoded4:
|
|
|
|
if (nxt_fast_path((ch >= '0' && ch <= '9')
|
|
|| (ch >= 'A' && ch <= 'F')
|
|
|| (ch >= 'a' && ch <= 'f')))
|
|
{
|
|
state = (state == sw_encoded4) ? sw_usual : state + 1;
|
|
continue;
|
|
}
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Invalid escape sequence. An escape sequence in a JSON "
|
|
"string must start with a backslash, followed by the lowercase "
|
|
"letter u, followed by four hexadecimal digits (\\uXXXX)."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
|
|
nxt_conf_json_parse_error(error, p,
|
|
"Unexpected end of JSON payload. There's a string without "
|
|
"a final double quote (\")."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Points to the ending quote mark. */
|
|
last = p;
|
|
|
|
size = last - start - surplus;
|
|
|
|
if (size > NXT_CONF_MAX_SHORT_STRING) {
|
|
|
|
if (nxt_slow_path(size > NXT_CONF_MAX_STRING)) {
|
|
|
|
nxt_conf_json_parse_error(error, start,
|
|
"The string is too long. Such a long JSON string value "
|
|
"is not supported."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
value->type = NXT_CONF_VALUE_STRING;
|
|
|
|
value->u.string.start = nxt_mp_nget(mp, size);
|
|
if (nxt_slow_path(value->u.string.start == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
value->u.string.length = size;
|
|
|
|
s = value->u.string.start;
|
|
|
|
} else {
|
|
value->type = NXT_CONF_VALUE_SHORT_STRING;
|
|
value->u.str.length = size;
|
|
|
|
s = value->u.str.start;
|
|
}
|
|
|
|
if (surplus == 0) {
|
|
nxt_memcpy(s, start, size);
|
|
return last + 1;
|
|
}
|
|
|
|
p = start;
|
|
|
|
do {
|
|
ch = *p++;
|
|
|
|
if (ch != '\\') {
|
|
*s++ = ch;
|
|
continue;
|
|
}
|
|
|
|
ch = *p++;
|
|
|
|
switch (ch) {
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
*s++ = ch;
|
|
continue;
|
|
|
|
case 'n':
|
|
*s++ = '\n';
|
|
continue;
|
|
|
|
case 'r':
|
|
*s++ = '\r';
|
|
continue;
|
|
|
|
case 't':
|
|
*s++ = '\t';
|
|
continue;
|
|
|
|
case 'b':
|
|
*s++ = '\b';
|
|
continue;
|
|
|
|
case 'f':
|
|
*s++ = '\f';
|
|
continue;
|
|
}
|
|
|
|
utf = 0;
|
|
utf_high = 0;
|
|
|
|
for ( ;; ) {
|
|
for (i = 0; i < 4; i++) {
|
|
utf = (utf << 4) | (p[i] >= 'A' ? 10 + ((p[i] & ~0x20) - 'A')
|
|
: p[i] - '0');
|
|
}
|
|
|
|
p += 4;
|
|
|
|
if (utf_high != 0) {
|
|
if (nxt_slow_path(utf < 0xDC00 || utf > 0xDFFF)) {
|
|
|
|
nxt_conf_json_parse_error(error, p - 12,
|
|
"Invalid JSON encoding sequence. This 12-byte "
|
|
"sequence composes an illegal UTF-16 surrogate pair."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
utf = ((utf_high - 0xD800) << 10) + (utf - 0xDC00) + 0x10000;
|
|
|
|
break;
|
|
}
|
|
|
|
if (utf < 0xD800 || utf > 0xDFFF) {
|
|
break;
|
|
}
|
|
|
|
if (utf > 0xDBFF || p[0] != '\\' || p[1] != 'u') {
|
|
|
|
nxt_conf_json_parse_error(error, p - 6,
|
|
"Invalid JSON encoding sequence. This 6-byte sequence "
|
|
"does not represent a valid UTF character."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
p += 2;
|
|
|
|
utf_high = utf;
|
|
utf = 0;
|
|
}
|
|
|
|
s = nxt_utf8_encode(s, utf);
|
|
|
|
} while (p != last);
|
|
|
|
if (size > NXT_CONF_MAX_SHORT_STRING) {
|
|
value->u.string.length = s - value->u.string.start;
|
|
|
|
} else {
|
|
value->u.str.length = s - value->u.str.start;
|
|
}
|
|
|
|
return last + 1;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_parse_number(nxt_mp_t *mp, nxt_conf_value_t *value, u_char *start,
|
|
u_char *end, nxt_conf_json_error_t *error)
|
|
{
|
|
u_char *p, *s, ch, c, *dot_pos;
|
|
size_t size;
|
|
double num;
|
|
|
|
s = start;
|
|
ch = *s;
|
|
|
|
if (ch == '-') {
|
|
s++;
|
|
}
|
|
|
|
dot_pos = NULL;
|
|
|
|
for (p = s; nxt_fast_path(p != end); p++) {
|
|
ch = *p;
|
|
|
|
/* Values below '0' become >= 208. */
|
|
c = ch - '0';
|
|
|
|
if (c > 9) {
|
|
if (ch == '.' && nxt_fast_path(dot_pos == NULL)) {
|
|
dot_pos = p;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dot_pos != NULL) {
|
|
if (nxt_slow_path(p - dot_pos <= 1)) {
|
|
nxt_conf_json_parse_error(error, s,
|
|
"The number is invalid. A fraction part in JSON numbers "
|
|
"must contain at least one digit."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
} else {
|
|
dot_pos = p;
|
|
}
|
|
|
|
if (nxt_slow_path(dot_pos - s > 1 && *start == '0')) {
|
|
nxt_conf_json_parse_error(error, s,
|
|
"The number is invalid. Leading zeros are not allowed in JSON "
|
|
"numbers."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (ch == 'e' || ch == 'E') {
|
|
p++;
|
|
s = p;
|
|
|
|
if (nxt_fast_path(s != end)) {
|
|
ch = *s;
|
|
|
|
if (ch == '-' || ch == '+') {
|
|
s++;
|
|
}
|
|
|
|
for (p = s; nxt_fast_path(p != end); p++) {
|
|
ch = *p;
|
|
|
|
/* Values below '0' become >= 208. */
|
|
c = ch - '0';
|
|
|
|
if (c > 9) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nxt_slow_path(p == s)) {
|
|
nxt_conf_json_parse_error(error, start,
|
|
"The number is invalid. An exponent part in JSON numbers "
|
|
"must contain at least one digit."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
size = p - start;
|
|
|
|
if (size > NXT_CONF_MAX_NUMBER_LEN) {
|
|
nxt_conf_json_parse_error(error, start,
|
|
"The number is too long. Such a long JSON number value "
|
|
"is not supported."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
nxt_memcpy(value->u.number, start, size);
|
|
value->u.number[size] = '\0';
|
|
|
|
nxt_errno = 0;
|
|
end = NULL;
|
|
|
|
num = nxt_strtod(value->u.number, &end);
|
|
|
|
if (nxt_slow_path(nxt_errno == NXT_ERANGE
|
|
|| fabs(num) > (double) NXT_INT64_T_MAX))
|
|
{
|
|
nxt_conf_json_parse_error(error, start,
|
|
"The number is out of representable range. Such JSON number "
|
|
"value is not supported."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (nxt_slow_path(end == NULL || *end != '\0')) {
|
|
nxt_thread_log_alert("strtod(\"%s\", %s) failed %E", value->u.number,
|
|
end == NULL ? (u_char *) "NULL" : end, nxt_errno);
|
|
return NULL;
|
|
}
|
|
|
|
value->type = (num == trunc(num)) ? NXT_CONF_VALUE_INTEGER
|
|
: NXT_CONF_VALUE_NUMBER;
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_conf_json_parse_error(nxt_conf_json_error_t *error, u_char *pos,
|
|
const char *detail)
|
|
{
|
|
if (error == NULL) {
|
|
return;
|
|
}
|
|
|
|
error->pos = pos;
|
|
error->detail = (u_char *) detail;
|
|
}
|
|
|
|
|
|
size_t
|
|
nxt_conf_json_length(nxt_conf_value_t *value, nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
switch (value->type) {
|
|
|
|
case NXT_CONF_VALUE_NULL:
|
|
return nxt_length("null");
|
|
|
|
case NXT_CONF_VALUE_BOOLEAN:
|
|
return value->u.boolean ? nxt_length("true") : nxt_length("false");
|
|
|
|
case NXT_CONF_VALUE_INTEGER:
|
|
case NXT_CONF_VALUE_NUMBER:
|
|
return nxt_strlen(value->u.number);
|
|
|
|
case NXT_CONF_VALUE_SHORT_STRING:
|
|
case NXT_CONF_VALUE_STRING:
|
|
return nxt_conf_json_string_length(value);
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
return nxt_conf_json_array_length(value, pretty);
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
return nxt_conf_json_object_length(value, pretty);
|
|
}
|
|
|
|
nxt_unreachable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
u_char *
|
|
nxt_conf_json_print(u_char *p, nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
switch (value->type) {
|
|
|
|
case NXT_CONF_VALUE_NULL:
|
|
return nxt_cpymem(p, "null", 4);
|
|
|
|
case NXT_CONF_VALUE_BOOLEAN:
|
|
return value->u.boolean ? nxt_cpymem(p, "true", 4)
|
|
: nxt_cpymem(p, "false", 5);
|
|
|
|
case NXT_CONF_VALUE_INTEGER:
|
|
case NXT_CONF_VALUE_NUMBER:
|
|
return nxt_cpystr(p, value->u.number);
|
|
|
|
case NXT_CONF_VALUE_SHORT_STRING:
|
|
case NXT_CONF_VALUE_STRING:
|
|
return nxt_conf_json_print_string(p, value);
|
|
|
|
case NXT_CONF_VALUE_ARRAY:
|
|
return nxt_conf_json_print_array(p, value, pretty);
|
|
|
|
case NXT_CONF_VALUE_OBJECT:
|
|
return nxt_conf_json_print_object(p, value, pretty);
|
|
}
|
|
|
|
nxt_unreachable();
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static size_t
|
|
nxt_conf_json_string_length(nxt_conf_value_t *value)
|
|
{
|
|
nxt_str_t str;
|
|
|
|
nxt_conf_get_string(value, &str);
|
|
|
|
return 2 + nxt_conf_json_escape_length(str.start, str.length);
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_print_string(u_char *p, nxt_conf_value_t *value)
|
|
{
|
|
nxt_str_t str;
|
|
|
|
nxt_conf_get_string(value, &str);
|
|
|
|
*p++ = '"';
|
|
|
|
p = nxt_conf_json_escape(p, str.start, str.length);
|
|
|
|
*p++ = '"';
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static size_t
|
|
nxt_conf_json_array_length(nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
size_t len;
|
|
nxt_uint_t n;
|
|
nxt_conf_array_t *array;
|
|
|
|
array = value->u.array;
|
|
|
|
/* [] */
|
|
len = 2;
|
|
|
|
if (pretty != NULL) {
|
|
pretty->level++;
|
|
}
|
|
|
|
value = array->elements;
|
|
|
|
for (n = 0; n < array->count; n++) {
|
|
len += nxt_conf_json_length(&value[n], pretty);
|
|
|
|
if (pretty != NULL) {
|
|
/* Indentation and new line. */
|
|
len += pretty->level + 2;
|
|
}
|
|
}
|
|
|
|
if (pretty != NULL) {
|
|
pretty->level--;
|
|
|
|
if (n != 0) {
|
|
/* Indentation and new line. */
|
|
len += pretty->level + 2;
|
|
}
|
|
}
|
|
|
|
/* Reserve space for "n" commas. */
|
|
return len + n;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_print_array(u_char *p, nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
nxt_uint_t n;
|
|
nxt_conf_array_t *array;
|
|
|
|
array = value->u.array;
|
|
|
|
*p++ = '[';
|
|
|
|
if (array->count != 0) {
|
|
value = array->elements;
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
|
|
pretty->level++;
|
|
p = nxt_conf_json_indentation(p, pretty->level);
|
|
}
|
|
|
|
p = nxt_conf_json_print(p, &value[0], pretty);
|
|
|
|
for (n = 1; n < array->count; n++) {
|
|
*p++ = ',';
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
p = nxt_conf_json_indentation(p, pretty->level);
|
|
|
|
pretty->more_space = 0;
|
|
}
|
|
|
|
p = nxt_conf_json_print(p, &value[n], pretty);
|
|
}
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
|
|
pretty->level--;
|
|
p = nxt_conf_json_indentation(p, pretty->level);
|
|
|
|
pretty->more_space = 1;
|
|
}
|
|
}
|
|
|
|
*p++ = ']';
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static size_t
|
|
nxt_conf_json_object_length(nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
size_t len;
|
|
nxt_uint_t n;
|
|
nxt_conf_object_t *object;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
object = value->u.object;
|
|
|
|
/* {} */
|
|
len = 2;
|
|
|
|
if (pretty != NULL) {
|
|
pretty->level++;
|
|
}
|
|
|
|
member = object->members;
|
|
|
|
for (n = 0; n < object->count; n++) {
|
|
len += nxt_conf_json_string_length(&member[n].name) + 1
|
|
+ nxt_conf_json_length(&member[n].value, pretty) + 1;
|
|
|
|
if (pretty != NULL) {
|
|
/*
|
|
* Indentation, space after ":", new line, and possible
|
|
* additional empty line between non-empty objects.
|
|
*/
|
|
len += pretty->level + 1 + 2 + 2;
|
|
}
|
|
}
|
|
|
|
if (pretty != NULL) {
|
|
pretty->level--;
|
|
|
|
/* Indentation and new line. */
|
|
len += pretty->level + 2;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_print_object(u_char *p, nxt_conf_value_t *value,
|
|
nxt_conf_json_pretty_t *pretty)
|
|
{
|
|
nxt_uint_t n;
|
|
nxt_conf_object_t *object;
|
|
nxt_conf_object_member_t *member;
|
|
|
|
object = value->u.object;
|
|
|
|
*p++ = '{';
|
|
|
|
if (object->count != 0) {
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
pretty->level++;
|
|
}
|
|
|
|
member = object->members;
|
|
|
|
n = 0;
|
|
|
|
for ( ;; ) {
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_indentation(p, pretty->level);
|
|
}
|
|
|
|
p = nxt_conf_json_print_string(p, &member[n].name);
|
|
|
|
*p++ = ':';
|
|
|
|
if (pretty != NULL) {
|
|
*p++ = ' ';
|
|
}
|
|
|
|
p = nxt_conf_json_print(p, &member[n].value, pretty);
|
|
|
|
n++;
|
|
|
|
if (n == object->count) {
|
|
break;
|
|
}
|
|
|
|
*p++ = ',';
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
|
|
if (pretty->more_space) {
|
|
pretty->more_space = 0;
|
|
p = nxt_conf_json_newline(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pretty != NULL) {
|
|
p = nxt_conf_json_newline(p);
|
|
|
|
pretty->level--;
|
|
p = nxt_conf_json_indentation(p, pretty->level);
|
|
|
|
pretty->more_space = 1;
|
|
}
|
|
}
|
|
|
|
*p++ = '}';
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static size_t
|
|
nxt_conf_json_escape_length(u_char *p, size_t size)
|
|
{
|
|
u_char ch;
|
|
size_t len;
|
|
|
|
len = size;
|
|
|
|
while (size) {
|
|
ch = *p++;
|
|
|
|
if (ch == '\\' || ch == '"') {
|
|
len++;
|
|
|
|
} else if (ch <= 0x1F) {
|
|
|
|
switch (ch) {
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
case '\b':
|
|
case '\f':
|
|
len++;
|
|
break;
|
|
|
|
default:
|
|
len += sizeof("\\u001F") - 2;
|
|
}
|
|
}
|
|
|
|
size--;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_conf_json_escape(u_char *dst, u_char *src, size_t size)
|
|
{
|
|
u_char ch;
|
|
|
|
while (size) {
|
|
ch = *src++;
|
|
|
|
if (ch > 0x1F) {
|
|
|
|
if (ch == '\\' || ch == '"') {
|
|
*dst++ = '\\';
|
|
}
|
|
|
|
*dst++ = ch;
|
|
|
|
} else {
|
|
*dst++ = '\\';
|
|
|
|
switch (ch) {
|
|
case '\n':
|
|
*dst++ = 'n';
|
|
break;
|
|
|
|
case '\r':
|
|
*dst++ = 'r';
|
|
break;
|
|
|
|
case '\t':
|
|
*dst++ = 't';
|
|
break;
|
|
|
|
case '\b':
|
|
*dst++ = 'b';
|
|
break;
|
|
|
|
case '\f':
|
|
*dst++ = 'f';
|
|
break;
|
|
|
|
default:
|
|
*dst++ = 'u'; *dst++ = '0'; *dst++ = '0';
|
|
*dst++ = '0' + (ch >> 4);
|
|
|
|
ch &= 0xF;
|
|
|
|
*dst++ = (ch < 10) ? ('0' + ch) : ('A' + ch - 10);
|
|
}
|
|
}
|
|
|
|
size--;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_conf_json_position(u_char *start, const u_char *pos, nxt_uint_t *line,
|
|
nxt_uint_t *column)
|
|
{
|
|
u_char *p;
|
|
ssize_t symbols;
|
|
nxt_uint_t lines;
|
|
|
|
lines = 1;
|
|
|
|
for (p = start; p != pos; p++) {
|
|
|
|
if (*p != '\n') {
|
|
continue;
|
|
}
|
|
|
|
lines++;
|
|
start = p + 1;
|
|
}
|
|
|
|
symbols = nxt_utf8_length(start, p - start);
|
|
|
|
if (symbols != -1) {
|
|
*line = lines;
|
|
*column = 1 + symbols;
|
|
}
|
|
}
|