tvheadend/src/misc/json.c

438 lines
7.7 KiB
C

/*
* JSON helpers
* Copyright (C) 2011 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "json.h"
#include "dbl.h"
#include "tvheadend.h"
#define NOT_THIS_TYPE ((void *)-1)
static const char *json_parse_value(const char *s, void *parent,
const char *name,
const json_deserializer_t *jd,
void *opaque,
const char **failp, const char **failmsg);
/**
* Returns a newly allocated string
*/
static char *
json_parse_string(const char *s, const char **endp,
const char **failp, const char **failmsg)
{
const char *start;
char *r, *a, *b;
int l, esc = 0;
while(*s > 0 && *s < 33)
s++;
if(*s != '"')
return NOT_THIS_TYPE;
s++;
start = s;
while(1) {
if(*s == 0) {
*failmsg = "Unexpected end of JSON message";
*failp = s;
return NULL;
}
if(*s == '\\') {
esc = 1;
/* skip the escape */
s++;
if (*s == 'u') s += 4;
// Note: we could detect the lack of support here!
} else if(*s == '"') {
*endp = s + 1;
/* End */
l = s - start;
r = malloc(l + 1);
memcpy(r, start, l);
r[l] = 0;
if(esc) {
/* Do deescaping inplace */
a = b = r;
while(*a) {
if(*a == '\\') {
a++;
if(*a == 'b')
*b++ = '\b';
else if(*a == '\\')
*b++ = '\\';
else if(*a == 'f')
*b++ = '\f';
else if(*a == 'n')
*b++ = '\n';
else if(*a == 'r')
*b++ = '\r';
else if(*a == 't')
*b++ = '\t';
else if(*a == 'u') {
// Unicode character
int i, v = 0;
a++;
for(i = 0; i < 4; i++) {
v = v << 4;
switch(a[i]) {
case '0' ... '9':
v |= a[i] - '0';
break;
case 'a' ... 'f':
v |= a[i] - 'a' + 10;
break;
case 'A' ... 'F':
v |= a[i] - 'F' + 10;
break;
default:
free(r);
*failmsg = "Incorrect escape sequence";
*failp = (a - r) + start;
return NULL;
}
}
a+=3;
b += put_utf8(b, v);
} else {
*b++ = *a;
}
a++;
} else {
*b++ = *a++;
}
}
*b = 0;
}
return r;
}
s++;
}
}
/**
*
*/
static void *
json_parse_map(const char *s, const char **endp, const json_deserializer_t *jd,
void *opaque, const char **failp, const char **failmsg)
{
char *name;
const char *s2;
void *r;
while(*s > 0 && *s < 33)
s++;
if(*s != '{')
return NOT_THIS_TYPE;
s++;
r = jd->jd_create_map(opaque);
while(*s > 0 && *s < 33)
s++;
if(*s != '}') {
while(1) {
name = json_parse_string(s, &s2, failp, failmsg);
if(name == NOT_THIS_TYPE) {
*failmsg = "Expected string";
*failp = s;
return NULL;
}
if(name == NULL)
return NULL;
s = s2;
while(*s > 0 && *s < 33)
s++;
if(*s != ':') {
jd->jd_destroy_obj(opaque, r);
free(name);
*failmsg = "Expected ':'";
*failp = s;
return NULL;
}
s++;
s2 = json_parse_value(s, r, name, jd, opaque, failp, failmsg);
free(name);
if(s2 == NULL) {
jd->jd_destroy_obj(opaque, r);
return NULL;
}
s = s2;
while(*s > 0 && *s < 33)
s++;
if(*s == '}')
break;
if(*s != ',') {
jd->jd_destroy_obj(opaque, r);
*failmsg = "Expected ','";
*failp = s;
return NULL;
}
s++;
}
}
s++;
*endp = s;
return r;
}
/**
*
*/
static void *
json_parse_list(const char *s, const char **endp, const json_deserializer_t *jd,
void *opaque, const char **failp, const char **failmsg)
{
const char *s2;
void *r;
while(*s > 0 && *s < 33)
s++;
if(*s != '[')
return NOT_THIS_TYPE;
s++;
r = jd->jd_create_list(opaque);
while(*s > 0 && *s < 33)
s++;
if(*s != ']') {
while(1) {
s2 = json_parse_value(s, r, NULL, jd, opaque, failp, failmsg);
if(s2 == NULL) {
jd->jd_destroy_obj(opaque, r);
return NULL;
}
s = s2;
while(*s > 0 && *s < 33)
s++;
if(*s == ']')
break;
if(*s != ',') {
jd->jd_destroy_obj(opaque, r);
*failmsg = "Expected ','";
*failp = s;
return NULL;
}
s++;
}
}
s++;
*endp = s;
return r;
}
/**
*
*/
static const char *
json_parse_double(const char *s, double *dp)
{
const char *ep;
while(*s > 0 && *s < 33)
s++;
double d = my_str2double(s, &ep);
if(ep == s)
return NULL;
*dp = d;
return ep;
}
/**
*
*/
static char *
json_parse_integer(const char *s, long *lp)
{
char *ep;
while(*s > 0 && *s < 33)
s++;
const char *s2 = s;
if(*s2 == '-')
s2++;
while(*s2 >= '0' && *s2 <= '9')
s2++;
if(*s2 == 0)
return NULL;
if(s2[0] == '.' || s2[0] == 'e' || s2[0] == 'E')
return NULL; // Is floating point
long v = strtol(s, &ep, 10);
if(v == LONG_MIN || v == LONG_MAX)
return NULL;
if(ep == s)
return NULL;
*lp = v;
return ep;
}
/**
*
*/
static const char *
json_parse_value(const char *s, void *parent, const char *name,
const json_deserializer_t *jd, void *opaque,
const char **failp, const char **failmsg)
{
const char *s2;
char *str;
double d = 0;
long l = 0;
void *c;
if((c = json_parse_map(s, &s2, jd, opaque, failp, failmsg)) == NULL)
return NULL;
if(c != NOT_THIS_TYPE) {
jd->jd_add_obj(opaque, parent, name, c);
return s2;
}
if((c = json_parse_list(s, &s2, jd, opaque, failp, failmsg)) == NULL)
return NULL;
if(c != NOT_THIS_TYPE) {
jd->jd_add_obj(opaque, parent, name, c);
return s2;
}
if((str = json_parse_string(s, &s2, failp, failmsg)) == NULL)
return NULL;
if(str != NOT_THIS_TYPE) {
jd->jd_add_string(opaque, parent, name, str);
return s2;
}
if((s2 = json_parse_integer(s, &l)) != NULL) {
jd->jd_add_long(opaque, parent, name, l);
return s2;
} else if((s2 = json_parse_double(s, &d)) != NULL) {
jd->jd_add_double(opaque, parent, name, d);
return s2;
}
while(*s > 0 && *s < 33)
s++;
if(!strncmp(s, "true", 4)) {
jd->jd_add_bool(opaque, parent, name, 1);
return s + 4;
}
if(!strncmp(s, "false", 5)) {
jd->jd_add_bool(opaque, parent, name, 0);
return s + 5;
}
if(!strncmp(s, "null", 4)) {
jd->jd_add_null(opaque, parent, name);
return s + 4;
}
*failmsg = "Unknown token";
*failp = s;
return NULL;
}
/**
*
*/
void *
json_deserialize(const char *src, const json_deserializer_t *jd, void *opaque,
char *errbuf, size_t errlen)
{
const char *end;
void *c;
const char *errmsg;
const char *errp;
c = json_parse_map(src, &end, jd, opaque, &errp, &errmsg);
if(c == NOT_THIS_TYPE)
c = json_parse_list(src, &end, jd, opaque, &errp, &errmsg);
if(c == NOT_THIS_TYPE) {
snprintf(errbuf, errlen, "Invalid JSON, expected '{' or '['");
return NULL;
}
if(c == NULL) {
size_t len = strlen(src);
ssize_t offset = errp - src;
if(offset > len || offset < 0) {
snprintf(errbuf, errlen, "%s at (bad) offset %d", errmsg, (int)offset);
} else {
offset -= 10;
if(offset < 0)
offset = 0;
snprintf(errbuf, errlen, "%s at offset %d : '%.20s'", errmsg, (int)offset,
src + offset);
}
}
return c;
}