From 01db78aea058d62d7b5bbeaf5192a171899c0fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Mon, 19 Nov 2012 13:16:58 +0100 Subject: [PATCH] Upgrade htsmsg code from Showtime's codebase We do this to get access to floating point in JSON --- Makefile | 2 + src/htsbuf.c | 182 ++++++++++++++++--- src/htsbuf.h | 29 ++- src/htsmsg.c | 174 +++++++++++++----- src/htsmsg.h | 114 +++++++----- src/htsmsg_json.c | 454 ++++++++++------------------------------------ src/htsmsg_json.h | 10 +- src/htsp_server.c | 25 +-- src/misc/dbl.c | 295 ++++++++++++++++++++++++++++++ src/misc/dbl.h | 5 + src/misc/json.c | 432 +++++++++++++++++++++++++++++++++++++++++++ src/misc/json.h | 31 ++++ 12 files changed, 1255 insertions(+), 498 deletions(-) create mode 100644 src/misc/dbl.c create mode 100644 src/misc/dbl.h create mode 100644 src/misc/json.c create mode 100644 src/misc/json.h diff --git a/Makefile b/Makefile index b1ed4b52..01f372df 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,8 @@ SRCS = src/main.c \ src/htsmsg_binary.c \ src/htsmsg_json.c \ src/htsmsg_xml.c \ + src/misc/dbl.c \ + src/misc/json.c \ src/settings.c \ src/htsbuf.c \ src/trap.c \ diff --git a/src/htsbuf.c b/src/htsbuf.c index ff859c18..03f49a3a 100644 --- a/src/htsbuf.c +++ b/src/htsbuf.c @@ -1,6 +1,6 @@ /* * Buffer management functions - * Copyright (C) 2008 Andreas Öman + * Copyright (C) 2008 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 @@ -22,17 +22,9 @@ #include #include #include -#include "htsbuf.h" + #include "tvheadend.h" - -#ifndef MIN -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef MAX -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#endif - +#include "htsbuf.h" /** * @@ -206,6 +198,7 @@ htsbuf_peek(htsbuf_queue_t *hq, void *buf, size_t len) c = MIN(hd->hd_data_len - hd->hd_data_off, len); memcpy(buf, hd->hd_data + hd->hd_data_off, c); + r += c; buf += c; len -= c; @@ -233,7 +226,7 @@ htsbuf_drop(htsbuf_queue_t *hq, size_t len) len -= c; hd->hd_data_off += c; hq->hq_size -= c; - + r += c; if(hd->hd_data_off == hd->hd_data_len) htsbuf_data_free(hq, hd); } @@ -314,19 +307,162 @@ htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src) } -/** - * - */ -uint32_t -htsbuf_crc32(htsbuf_queue_t *hq, uint32_t crc) +void +htsbuf_dump_raw_stderr(htsbuf_queue_t *hq) { htsbuf_data_t *hd; - - TAILQ_FOREACH(hd, &hq->hq_q, hd_link) - crc = tvh_crc32(hd->hd_data + hd->hd_data_off, - hd->hd_data_len - hd->hd_data_off, - crc); - return crc; + char n = '\n'; + + TAILQ_FOREACH(hd, &hq->hq_q, hd_link) { + if(write(2, hd->hd_data + hd->hd_data_off, + hd->hd_data_len - hd->hd_data_off) + != hd->hd_data_len - hd->hd_data_off) + break; + } + if(write(2, &n, 1) != 1) + return; } +void +htsbuf_hexdump(htsbuf_queue_t *hq, const char *prefix) +{ + void *r = malloc(hq->hq_size); + htsbuf_peek(hq, r, hq->hq_size); + hexdump(prefix, r, hq->hq_size); + free(r); +} + + +/** + * + */ +void +htsbuf_append_and_escape_xml(htsbuf_queue_t *hq, const char *s) +{ + const char *c = s; + const char *e = s + strlen(s); + if(e == s) + return; + + while(1) { + const char *esc; + switch(*c++) { + case '<': esc = "<"; break; + case '>': esc = ">"; break; + case '&': esc = "&"; break; + case '\'': esc = "'"; break; + case '"': esc = """; break; + default: esc = NULL; break; + } + + if(esc != NULL) { + htsbuf_append(hq, s, c - s - 1); + htsbuf_append(hq, esc, strlen(esc)); + s = c; + } + + if(c == e) { + htsbuf_append(hq, s, c - s); + break; + } + } +} + + +/** + * + */ +void +htsbuf_append_and_escape_url(htsbuf_queue_t *hq, const char *s) +{ + const char *c = s; + const char *e = s + strlen(s); + char C; + if(e == s) + return; + + while(1) { + const char *esc; + C = *c++; + + if((C >= '0' && C <= '9') || + (C >= 'a' && C <= 'z') || + (C >= 'A' && C <= 'Z') || + C == '_' || + C == '~' || + C == '.' || + C == '-') { + esc = NULL; + } else { + static const char hexchars[16] = "0123456789ABCDEF"; + char buf[4]; + buf[0] = '%'; + buf[1] = hexchars[(C >> 4) & 0xf]; + buf[2] = hexchars[C & 0xf];; + buf[3] = 0; + esc = buf; + } + + if(esc != NULL) { + htsbuf_append(hq, s, c - s - 1); + htsbuf_append(hq, esc, strlen(esc)); + s = c; + } + + if(c == e) { + htsbuf_append(hq, s, c - s); + break; + } + } +} + + +/** + * + */ +void +htsbuf_append_and_escape_jsonstr(htsbuf_queue_t *hq, const char *str) +{ + const char *s = str; + + htsbuf_append(hq, "\"", 1); + + while(*s != 0) { + if(*s == '"' || *s == '\\' || *s == '\n' || *s == '\r' || *s == '\t') { + htsbuf_append(hq, str, s - str); + + if(*s == '"') + htsbuf_append(hq, "\\\"", 2); + else if(*s == '\n') + htsbuf_append(hq, "\\n", 2); + else if(*s == '\r') + htsbuf_append(hq, "\\r", 2); + else if(*s == '\t') + htsbuf_append(hq, "\\t", 2); + else + htsbuf_append(hq, "\\\\", 2); + s++; + str = s; + } else { + s++; + } + } + htsbuf_append(hq, str, s - str); + htsbuf_append(hq, "\"", 1); +} + + + +/** + * + */ +char * +htsbuf_to_string(htsbuf_queue_t *hq) +{ + char *r = malloc(hq->hq_size + 1); + r[hq->hq_size] = 0; + htsbuf_read(hq, r, hq->hq_size); + return r; +} + diff --git a/src/htsbuf.h b/src/htsbuf.h index 13aaedb5..ae9a1322 100644 --- a/src/htsbuf.h +++ b/src/htsbuf.h @@ -1,6 +1,6 @@ /* * Buffer management functions - * Copyright (C) 2008 Andreas Öman + * Copyright (C) 2008 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 @@ -20,10 +20,11 @@ #define HTSBUF_H__ #include -#include -#include "queue.h" #include +#include "queue.h" + + TAILQ_HEAD(htsbuf_data_queue, htsbuf_data); typedef struct htsbuf_data { @@ -48,15 +49,12 @@ void htsbuf_queue_flush(htsbuf_queue_t *hq); void htsbuf_vqprintf(htsbuf_queue_t *hq, const char *fmt, va_list ap); -void htsbuf_qprintf(htsbuf_queue_t *hq, const char *fmt, ...) - __attribute__((format(printf,2,3))); +void htsbuf_qprintf(htsbuf_queue_t *hq, const char *fmt, ...); void htsbuf_append(htsbuf_queue_t *hq, const void *buf, size_t len); void htsbuf_append_prealloc(htsbuf_queue_t *hq, const void *buf, size_t len); -void htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src); - void htsbuf_data_free(htsbuf_queue_t *hq, htsbuf_data_t *hd); size_t htsbuf_read(htsbuf_queue_t *hq, void *buf, size_t len); @@ -67,6 +65,21 @@ size_t htsbuf_drop(htsbuf_queue_t *hq, size_t len); size_t htsbuf_find(htsbuf_queue_t *hq, uint8_t v); -uint32_t htsbuf_crc32(htsbuf_queue_t *q, uint32_t crc); +void htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src); + +void htsbuf_append_and_escape_xml(htsbuf_queue_t *hq, const char *str); + +void htsbuf_append_and_escape_url(htsbuf_queue_t *hq, const char *s); + +void htsbuf_append_and_escape_jsonstr(htsbuf_queue_t *hq, const char *s); + +void htsbuf_dump_raw_stderr(htsbuf_queue_t *hq); + +char *htsbuf_to_string(htsbuf_queue_t *hq); + +struct rstr; +struct rstr *htsbuf_to_rstr(htsbuf_queue_t *hq, const char *prefix); + +void htsbuf_hexdump(htsbuf_queue_t *hq, const char *prefix); #endif /* HTSBUF_H__ */ diff --git a/src/htsmsg.c b/src/htsmsg.c index 5dd67e3a..935c30b6 100644 --- a/src/htsmsg.c +++ b/src/htsmsg.c @@ -1,6 +1,6 @@ /* * Functions for manipulating HTS messages - * Copyright (C) 2007 Andreas Öman + * Copyright (C) 2007 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 @@ -27,10 +27,10 @@ static void htsmsg_clear(htsmsg_t *msg); -/* +/** * */ -static void +void htsmsg_field_destroy(htsmsg_t *msg, htsmsg_field_t *f) { TAILQ_REMOVE(&msg->hm_fields, f, hmf_link); @@ -102,7 +102,7 @@ htsmsg_field_add(htsmsg_t *msg, const char *name, int type, int flags) /* * */ -static htsmsg_field_t * +htsmsg_field_t * htsmsg_field_find(htsmsg_t *msg, const char *name) { htsmsg_field_t *f; @@ -196,16 +196,6 @@ htsmsg_add_s64(htsmsg_t *msg, const char *name, int64_t s64) f->hmf_s64 = s64; } -/* - * - */ -void -htsmsg_add_u64(htsmsg_t *msg, const char *name, uint64_t u64) -{ - htsmsg_field_t *f = htsmsg_field_add(msg, name, HMF_S64, HMF_NAME_ALLOCED); - f->hmf_s64 = u64; -} - /* * */ @@ -217,6 +207,17 @@ htsmsg_add_s32(htsmsg_t *msg, const char *name, int32_t s32) } +/* + * + */ +void +htsmsg_add_dbl(htsmsg_t *msg, const char *name, double dbl) +{ + htsmsg_field_t *f = htsmsg_field_add(msg, name, HMF_DBL, HMF_NAME_ALLOCED); + f->hmf_dbl = dbl; +} + + /* * @@ -267,8 +268,8 @@ htsmsg_add_msg(htsmsg_t *msg, const char *name, htsmsg_t *sub) HMF_NAME_ALLOCED); assert(sub->hm_data == NULL); - TAILQ_MOVE(&f->hmf_msg.hm_fields, &sub->hm_fields, hmf_link); f->hmf_msg.hm_islist = sub->hm_islist; + TAILQ_MOVE(&f->hmf_msg.hm_fields, &sub->hm_fields, hmf_link); free(sub); } @@ -312,10 +313,14 @@ htsmsg_get_s64(htsmsg_t *msg, const char *name, int64_t *s64p) case HMF_S64: *s64p = f->hmf_s64; break; + case HMF_DBL: + *s64p = f->hmf_dbl; + break; } return 0; } + /** * */ @@ -326,30 +331,6 @@ htsmsg_get_s64_or_default(htsmsg_t *msg, const char *name, int64_t def) return htsmsg_get_s64(msg, name, &s64) ? def : s64; } -/** - * - */ -int -htsmsg_get_u64(htsmsg_t *msg, const char *name, uint64_t *u64p) -{ - htsmsg_field_t *f; - - if((f = htsmsg_field_find(msg, name)) == NULL) - return HTSMSG_ERR_FIELD_NOT_FOUND; - - switch(f->hmf_type) { - default: - return HTSMSG_ERR_CONVERSION_IMPOSSIBLE; - case HMF_STR: - *u64p = strtoull(f->hmf_str, NULL, 0); - break; - case HMF_S64: - *u64p = f->hmf_s64; - break; - } - return 0; -} - /* * @@ -373,13 +354,26 @@ htsmsg_get_u32(htsmsg_t *msg, const char *name, uint32_t *u32p) /** * */ -uint32_t +int htsmsg_get_u32_or_default(htsmsg_t *msg, const char *name, uint32_t def) { uint32_t u32; return htsmsg_get_u32(msg, name, &u32) ? def : u32; } + +/** + * + */ +int32_t +htsmsg_get_s32_or_default(htsmsg_t *msg, const char *name, int32_t def) +{ + int32_t s32; + return htsmsg_get_s32(msg, name, &s32) ? def : s32; +} + + + /* * */ @@ -400,6 +394,25 @@ htsmsg_get_s32(htsmsg_t *msg, const char *name, int32_t *s32p) } +/* + * + */ +int +htsmsg_get_dbl(htsmsg_t *msg, const char *name, double *dblp) +{ + htsmsg_field_t *f; + + if((f = htsmsg_field_find(msg, name)) == NULL) + return HTSMSG_ERR_FIELD_NOT_FOUND; + + if(f->hmf_type != HMF_DBL) + return HTSMSG_ERR_CONVERSION_IMPOSSIBLE; + + *dblp = f->hmf_dbl; + return 0; +} + + /* * */ @@ -457,13 +470,6 @@ htsmsg_get_str(htsmsg_t *msg, const char *name) } -const char * -htsmsg_get_str_or_default(htsmsg_t *msg, const char *name, const char *def) -{ - const char *str = htsmsg_get_str(msg, name); - return str ?: def; -} - /* * */ @@ -493,6 +499,32 @@ htsmsg_get_map_multi(htsmsg_t *msg, ...) return msg; } +/** + * + */ +const char * +htsmsg_get_str_multi(htsmsg_t *msg, ...) +{ + va_list ap; + const char *n; + htsmsg_field_t *f; + va_start(ap, msg); + + while((n = va_arg(ap, char *)) != NULL) { + if((f = htsmsg_field_find(msg, n)) == NULL) + return NULL; + else if(f->hmf_type == HMF_STR) + return f->hmf_str; + else if(f->hmf_type == HMF_MAP) + msg = &f->hmf_msg; + else + return NULL; + } + return NULL; +} + + + /* * */ @@ -565,6 +597,10 @@ htsmsg_print0(htsmsg_t *msg, int indent) case HMF_S64: printf("S64) = %" PRId64 "\n", f->hmf_s64); break; + + case HMF_DBL: + printf("DBL) = %f\n", f->hmf_dbl); + break; } } } @@ -611,6 +647,10 @@ htsmsg_copy_i(htsmsg_t *src, htsmsg_t *dst) case HMF_BIN: htsmsg_add_bin(dst, f->hmf_name, f->hmf_bin, f->hmf_binsize); break; + + case HMF_DBL: + htsmsg_add_dbl(dst, f->hmf_name, f->hmf_dbl); + break; } } } @@ -623,6 +663,45 @@ htsmsg_copy(htsmsg_t *src) return dst; } +/** + * + */ +htsmsg_t * +htsmsg_get_map_in_list(htsmsg_t *m, int num) +{ + htsmsg_field_t *f; + + HTSMSG_FOREACH(f, m) { + if(!--num) + return htsmsg_get_map_by_field(f); + } + return NULL; +} + + +/** + * + */ +htsmsg_t * +htsmsg_get_map_by_field_if_name(htsmsg_field_t *f, const char *name) +{ + if(f->hmf_type != HMF_MAP) + return NULL; + if(strcmp(f->hmf_name, name)) + return NULL; + return &f->hmf_msg; +} + + +/** + * + */ +const char * +htsmsg_get_cdata(htsmsg_t *m, const char *field) +{ + return htsmsg_get_str_multi(m, field, "cdata", NULL); +} + /** * @@ -633,3 +712,4 @@ htsmsg_dtor(htsmsg_t **mp) if(*mp != NULL) htsmsg_destroy(*mp); } + diff --git a/src/htsmsg.h b/src/htsmsg.h index d7d2bb05..82fb7fda 100644 --- a/src/htsmsg.h +++ b/src/htsmsg.h @@ -1,6 +1,6 @@ /* * Functions for manipulating HTS messages - * Copyright (C) 2007 Andreas Öman + * Copyright (C) 2007 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 @@ -16,9 +16,8 @@ * along with this program. If not, see . */ -#ifndef HTSMSG_H_ -#define HTSMSG_H_ - +#pragma once +#include #include #include "queue.h" @@ -50,6 +49,7 @@ typedef struct htsmsg { #define HMF_STR 3 #define HMF_BIN 4 #define HMF_LIST 5 +#define HMF_DBL 6 typedef struct htsmsg_field { TAILQ_ENTRY(htsmsg_field) hmf_link; @@ -68,6 +68,7 @@ typedef struct htsmsg_field { size_t len; } bin; htsmsg_t msg; + double dbl; } u; } htsmsg_field_t; @@ -76,11 +77,12 @@ typedef struct htsmsg_field { #define hmf_str u.str #define hmf_bin u.bin.data #define hmf_binsize u.bin.len +#define hmf_dbl u.dbl #define htsmsg_get_map_by_field(f) \ ((f)->hmf_type == HMF_MAP ? &(f)->hmf_msg : NULL) #define htsmsg_get_list_by_field(f) \ - ((f)->hmf_type == HMF_LIST ? &(f)->hmf_msg : NULL) + ((f)->hmf_type == HMF_LIST ? &(f)->hmf_msg : NULL) #define HTSMSG_FOREACH(f, msg) TAILQ_FOREACH(f, &(msg)->hm_fields, hmf_link) @@ -94,6 +96,11 @@ htsmsg_t *htsmsg_create_map(void); */ htsmsg_t *htsmsg_create_list(void); +/** + * Remove a given field from a msg + */ +void htsmsg_field_destroy(htsmsg_t *msg, htsmsg_field_t *f); + /** * Destroys a message (map or list) */ @@ -109,11 +116,6 @@ void htsmsg_add_u32(htsmsg_t *msg, const char *name, uint32_t u32); */ void htsmsg_add_s32(htsmsg_t *msg, const char *name, int32_t s32); -/** - * Add an integer field where source is unsigned 64 bit. - */ -void htsmsg_add_u64(htsmsg_t *msg, const char *name, uint64_t u64); - /** * Add an integer field where source is signed 64 bit. */ @@ -129,6 +131,11 @@ void htsmsg_add_str(htsmsg_t *msg, const char *name, const char *str); */ void htsmsg_add_msg(htsmsg_t *msg, const char *name, htsmsg_t *sub); +/** + * Add an field where source is a double + */ +void htsmsg_add_dbl(htsmsg_t *msg, const char *name, double dbl); + /** * Add an field where source is a list or map message. * @@ -160,15 +167,6 @@ void htsmsg_add_binptr(htsmsg_t *msg, const char *name, const void *bin, */ int htsmsg_get_u32(htsmsg_t *msg, const char *name, uint32_t *u32p); -/** - * Return the field \p name as an u32. - * - * @return An unsigned 32 bit integer or def if the field can not be found - * or if conversion is not possible. - */ -uint32_t htsmsg_get_u32_or_default - (htsmsg_t *msg, const char *name, uint32_t def); - /** * Get an integer as an signed 32 bit integer. * @@ -187,24 +185,11 @@ int htsmsg_get_s32(htsmsg_t *msg, const char *name, int32_t *s32p); */ int htsmsg_get_s64(htsmsg_t *msg, const char *name, int64_t *s64p); - -/** +/* * Return the field \p name as an s64. - * - * @return A signed 64 bit integer or def if the field can not be found - * or if conversion is not possible. */ -int64_t htsmsg_get_s64_or_default - (htsmsg_t *msg, const char *name, int64_t def); +int64_t htsmsg_get_s64_or_default(htsmsg_t *msg, const char *name, int64_t def); -/** - * Get an integer as an unsigned 64 bit integer. - * - * @return HTSMSG_ERR_FIELD_NOT_FOUND - Field does not exist - * HTSMSG_ERR_CONVERSION_IMPOSSIBLE - Field is not an integer or - * out of range for the requested storage. - */ -int htsmsg_get_u64(htsmsg_t *msg, const char *name, uint64_t *u64p); /** * Get pointer to a binary field. No copying of data is performed. @@ -236,15 +221,6 @@ htsmsg_t *htsmsg_get_list(htsmsg_t *msg, const char *name); */ const char *htsmsg_get_str(htsmsg_t *msg, const char *name); -/** - * Get a field of type 'string'. No copying is done. - * - * @return def if the field can not be found or not of string type. - * Otherwise a pointer to the data is returned. - */ -const char *htsmsg_get_str_or_default - (htsmsg_t *msg, const char *name, const char *def); - /** * Get a field of type 'map'. No copying is done. * @@ -256,7 +232,23 @@ htsmsg_t *htsmsg_get_map(htsmsg_t *msg, const char *name); /** * Traverse a hierarchy of htsmsg's to find a specific child. */ -htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...); +htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...) + __attribute__((__sentinel__(0))); + +/** + * Traverse a hierarchy of htsmsg's to find a specific child. + */ +const char *htsmsg_get_str_multi(htsmsg_t *msg, ...) + __attribute__((__sentinel__(0))); + +/** + * Get a field of type 'double'. + * + * @return HTSMSG_ERR_FIELD_NOT_FOUND - Field does not exist + * HTSMSG_ERR_CONVERSION_IMPOSSIBLE - Field is not an integer or + * out of range for the requested storage. + */ +int htsmsg_get_dbl(htsmsg_t *msg, const char *name, double *dblp); /** * Given the field \p f, return a string if it is of type string, otherwise @@ -264,6 +256,23 @@ htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...); */ const char *htsmsg_field_get_string(htsmsg_field_t *f); +/** + * Return the field \p name as an u32. + * + * @return An unsigned 32 bit integer or NULL if the field can not be found + * or if conversion is not possible. + */ +int htsmsg_get_u32_or_default(htsmsg_t *msg, const char *name, uint32_t def); + +/** + * Return the field \p name as an s32. + * + * @return A signed 32 bit integer or NULL if the field can not be found + * or if conversion is not possible. + */ +int32_t htsmsg_get_s32_or_default(htsmsg_t *msg, const char *name, + int32_t def); + /** * Remove the given field called \p name from the message \p msg. */ @@ -288,6 +297,12 @@ void htsmsg_print(htsmsg_t *msg); htsmsg_field_t *htsmsg_field_add(htsmsg_t *msg, const char *name, int type, int flags); +/** + * Get a field, return NULL if it does not exist + */ +htsmsg_field_t *htsmsg_field_find(htsmsg_t *msg, const char *name); + + /** * Clone a message. */ @@ -296,8 +311,15 @@ htsmsg_t *htsmsg_copy(htsmsg_t *src); #define HTSMSG_FOREACH(f, msg) TAILQ_FOREACH(f, &(msg)->hm_fields, hmf_link) +/** + * Misc + */ +htsmsg_t *htsmsg_get_map_in_list(htsmsg_t *m, int num); + +htsmsg_t *htsmsg_get_map_by_field_if_name(htsmsg_field_t *f, const char *name); + +const char *htsmsg_get_cdata(htsmsg_t *m, const char *field); + extern void htsmsg_dtor(htsmsg_t **mp); #define htsmsg_autodtor(n) htsmsg_t *n __attribute__((cleanup(htsmsg_dtor))) - -#endif /* HTSMSG_H_ */ diff --git a/src/htsmsg_json.c b/src/htsmsg_json.c index ff1aa6f7..3fda47c1 100644 --- a/src/htsmsg_json.c +++ b/src/htsmsg_json.c @@ -25,54 +25,19 @@ #include "htsmsg_json.h" #include "htsbuf.h" +#include "misc/json.h" +#include "misc/dbl.h" + /** * */ static void -htsmsg_json_encode_string(const char *str, htsbuf_queue_t *hq) -{ - const char *s = str; - - htsbuf_append(hq, "\"", 1); - - while(*s != 0) { - if(*s == '"' || *s == '\\' || *s == '\n' || *s == '\t' || *s == '\r') { - htsbuf_append(hq, str, s - str); - - if(*s == '"') - htsbuf_append(hq, "\\\"", 2); - else if(*s == '\n') - htsbuf_append(hq, "\\n", 2); - else if(*s == '\t') - htsbuf_append(hq, "\\t", 2); - else if(*s == '\r') - htsbuf_append(hq, "\\r", 2); - else - htsbuf_append(hq, "\\\\", 2); - s++; - str = s; - } else { - s++; - } - } - htsbuf_append(hq, str, s - str); - htsbuf_append(hq, "\"", 1); -} - - -/* - * Note to future: - * If your about to add support for numbers with decimal point, - * remember to always serialize them with '.' as decimal point character - * no matter what current locale says. This is according to the JSON spec. - */ -static void htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray, int indent, int pretty) { htsmsg_field_t *f; - char buf[30]; + char buf[100]; static const char *indentor = "\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; htsbuf_append(hq, isarray ? "[" : "{", 1); @@ -83,7 +48,7 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray, htsbuf_append(hq, indentor, indent < 16 ? indent : 16); if(!isarray) { - htsmsg_json_encode_string(f->hmf_name ?: "noname", hq); + htsbuf_append_and_escape_jsonstr(hq, f->hmf_name ?: "noname"); htsbuf_append(hq, ": ", 2); } @@ -97,11 +62,11 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray, break; case HMF_STR: - htsmsg_json_encode_string(f->hmf_str, hq); + htsbuf_append_and_escape_jsonstr(hq, f->hmf_str); break; case HMF_BIN: - htsmsg_json_encode_string("binary", hq); + htsbuf_append_and_escape_jsonstr(hq, "binary"); break; case HMF_S64: @@ -109,6 +74,11 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray, htsbuf_append(hq, buf, strlen(buf)); break; + case HMF_DBL: + my_double2str(buf, sizeof(buf), f->hmf_dbl); + htsbuf_append(hq, buf, strlen(buf)); + break; + default: abort(); } @@ -125,343 +95,109 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray, /** * */ -int +void htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty) { htsmsg_json_write(msg, hq, msg->hm_islist, 2, pretty); if(pretty) htsbuf_append(hq, "\n", 1); - return 0; -} - - - -static const char *htsmsg_json_parse_value(const char *s, - htsmsg_t *parent, char *name); - -/** - * - */ -static char * -htsmsg_json_parse_string(const char *s, const char **endp) -{ - const char *start; - char *r, *a, *b; - int l, esc = 0; - - while(*s > 0 && *s < 33) - s++; - - if(*s != '"') - return NULL; - - s++; - start = s; - - while(1) { - if(*s == 0) - 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') { - /* 4 hexdigits: Not supported */ - free(r); - return NULL; - } else { - *b++ = *a; - } - a++; - } else { - *b++ = *a++; - } - } - *b = 0; - } - return r; - } - s++; - } } /** * */ -static htsmsg_t * -htsmsg_json_parse_object(const char *s, const char **endp) +char * +htsmsg_json_serialize_to_str(htsmsg_t *msg, int pretty) { - char *name; - const char *s2; - htsmsg_t *r; - - while(*s > 0 && *s < 33) - s++; - - if(*s != '{') - return NULL; - - s++; - - r = htsmsg_create_map(); - - while(*s > 0 && *s < 33) - s++; - - if(*s != '}') while(1) { - - if((name = htsmsg_json_parse_string(s, &s2)) == NULL) { - htsmsg_destroy(r); - return NULL; - } - - s = s2; - - while(*s > 0 && *s < 33) - s++; - - if(*s != ':') { - htsmsg_destroy(r); - free(name); - return NULL; - } - s++; - - s2 = htsmsg_json_parse_value(s, r, name); - free(name); - - if(s2 == NULL) { - htsmsg_destroy(r); - return NULL; - } - - s = s2; - - while(*s > 0 && *s < 33) - s++; - - if(*s == '}') - break; - - if(*s != ',') { - htsmsg_destroy(r); - return NULL; - } - s++; - } - - s++; - *endp = s; - return r; -} - - -/** - * - */ -static htsmsg_t * -htsmsg_json_parse_array(const char *s, const char **endp) -{ - const char *s2; - htsmsg_t *r; - - while(*s > 0 && *s < 33) - s++; - - if(*s != '[') - return NULL; - - s++; - - r = htsmsg_create_list(); - - while(*s > 0 && *s < 33) - s++; - - if(*s != ']') { - - while(1) { - - s2 = htsmsg_json_parse_value(s, r, NULL); - - if(s2 == NULL) { - htsmsg_destroy(r); - return NULL; - } - - s = s2; - - while(*s > 0 && *s < 33) - s++; - - if(*s == ']') - break; - - if(*s != ',') { - htsmsg_destroy(r); - return NULL; - } - s++; - } - } - s++; - *endp = s; - return r; -} - -/* - * locale independent strtod. - * does not support hex floats as the standard strtod - */ -static double -_strntod(const char *s, char decimal_point_char, char **ep) -{ - static char locale_decimal_point_char = 0; - char buf[64]; - const char *c; - double d; - - /* ugly but very portable way to get decimal point char */ - if(locale_decimal_point_char == 0) { - snprintf(buf, sizeof(buf), "%f", 0.0); - locale_decimal_point_char = buf[1]; - assert(locale_decimal_point_char != 0); - } - - for(c = s; - *c != '\0' && - ((*c > 0 && *c < 33) || /* skip whitespace */ - (*c == decimal_point_char || strchr("+-0123456789", *c) != NULL)); c++) - ; - - strncpy(buf, s, c - s); - buf[c - s] = '\0'; - - /* replace if specified char is not same as current locale */ - if(decimal_point_char != locale_decimal_point_char) { - char *r = strchr(buf, decimal_point_char); - if(r != NULL) - *r = locale_decimal_point_char; - } - - d = strtod(buf, ep); - - /* figure out offset in original string */ - if(ep != NULL) - *ep = (char *)s + (*ep - buf); - - return d; -} - -/** - * - */ -static char * -htsmsg_json_parse_number(const char *s, double *dp) -{ - char *ep; - double d = _strntod(s, '.', &ep); - - if(ep == s) - return NULL; - - *dp = d; - return ep; -} - -/** - * - */ -static const char * -htsmsg_json_parse_value(const char *s, htsmsg_t *parent, char *name) -{ - const char *s2; + htsbuf_queue_t hq; char *str; - double d = 0; - htsmsg_t *c; - - if((c = htsmsg_json_parse_object(s, &s2)) != NULL) { - htsmsg_add_msg(parent, name, c); - return s2; - } else if((c = htsmsg_json_parse_array(s, &s2)) != NULL) { - htsmsg_add_msg(parent, name, c); - return s2; - } else if((str = htsmsg_json_parse_string(s, &s2)) != NULL) { - htsmsg_add_str(parent, name, str); - free(str); - return s2; - } else if((s2 = htsmsg_json_parse_number(s, &d)) != NULL) { - htsmsg_add_s64(parent, name, d); - return s2; - } - - if(!strncmp(s, "true", 4)) { - htsmsg_add_u32(parent, name, 1); - return s + 4; - } - - if(!strncmp(s, "false", 5)) { - htsmsg_add_u32(parent, name, 0); - return s + 5; - } - - if(!strncmp(s, "null", 4)) { - /* Don't add anything */ - return s + 4; - } - return NULL; + htsbuf_queue_init(&hq, 0); + htsmsg_json_serialize(msg, &hq, pretty); + str = htsbuf_to_string(&hq); + htsbuf_queue_flush(&hq); + return str; } +/** + * + */ +static void * +create_map(void *opaque) +{ + return htsmsg_create_map(); +} + +static void * +create_list(void *opaque) +{ + return htsmsg_create_list(); +} + +static void +destroy_obj(void *opaque, void *obj) +{ + return htsmsg_destroy(obj); +} + +static void +add_obj(void *opaque, void *parent, const char *name, void *child) +{ + htsmsg_add_msg(parent, name, child); +} + +static void +add_string(void *opaque, void *parent, const char *name, char *str) +{ + htsmsg_add_str(parent, name, str); + free(str); +} + +static void +add_long(void *opaque, void *parent, const char *name, long v) +{ + htsmsg_add_s64(parent, name, v); +} + +static void +add_double(void *opaque, void *parent, const char *name, double v) +{ + htsmsg_add_dbl(parent, name, v); +} + +static void +add_bool(void *opaque, void *parent, const char *name, int v) +{ + htsmsg_add_u32(parent, name, v); +} + +static void +add_null(void *opaque, void *parent, const char *name) +{ +} + +/** + * + */ +static const json_deserializer_t json_to_htsmsg = { + .jd_create_map = create_map, + .jd_create_list = create_list, + .jd_destroy_obj = destroy_obj, + .jd_add_obj = add_obj, + .jd_add_string = add_string, + .jd_add_long = add_long, + .jd_add_double = add_double, + .jd_add_bool = add_bool, + .jd_add_null = add_null, +}; + + /** * */ htsmsg_t * htsmsg_json_deserialize(const char *src) { - const char *end; - htsmsg_t *c; - - if((c = htsmsg_json_parse_object(src, &end)) != NULL) - return c; - - if((c = htsmsg_json_parse_array(src, &end)) != NULL) { - c->hm_islist = 1; - return c; - } - return NULL; + return json_deserialize(src, &json_to_htsmsg, NULL, NULL, 0); } diff --git a/src/htsmsg_json.h b/src/htsmsg_json.h index da6b2504..fae3c457 100644 --- a/src/htsmsg_json.h +++ b/src/htsmsg_json.h @@ -1,6 +1,6 @@ /* * Functions converting HTSMSGs to/from JSON - * Copyright (C) 2008 Andreas Öman + * Copyright (C) 2008 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 @@ -21,12 +21,16 @@ #include "htsmsg.h" #include "htsbuf.h" - +struct rstr; /** * htsmsg_binary_deserialize */ htsmsg_t *htsmsg_json_deserialize(const char *src); -int htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty); +void htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty); + +char *htsmsg_json_serialize_to_str(htsmsg_t *msg, int pretty); + +struct rstr *htsmsg_json_serialize_to_rstr(htsmsg_t *msg, const char *prefix); #endif /* HTSMSG_JSON_H_ */ diff --git a/src/htsp_server.c b/src/htsp_server.c index 05ff068f..d13b59de 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -381,14 +381,14 @@ htsp_file_open(htsp_connection_t *htsp, const char *path) hf->hf_fd = fd; hf->hf_id = ++htsp->htsp_file_id; hf->hf_path = strdup(path); - LIST_INSERT_HEAD(&htsp->htsp_files, hf, hf_link); + LIST_INSERT_HEAD(&htsp->htsp_files, hf, hf_link); htsmsg_t *rep = htsmsg_create_map(); htsmsg_add_u32(rep, "id", hf->hf_id); if(!fstat(hf->hf_fd, &st)) { - htsmsg_add_u64(rep, "size", st.st_size); - htsmsg_add_u64(rep, "mtime", st.st_mtime); + htsmsg_add_s64(rep, "size", st.st_size); + htsmsg_add_s64(rep, "mtime", st.st_mtime); } return rep; @@ -837,7 +837,7 @@ htsp_method_getEvent(htsp_connection_t *htsp, htsmsg_t *in) if(htsmsg_get_u32(in, "eventId", &eventId)) return htsp_error("Missing argument 'eventId'"); - lang = htsmsg_get_str_or_default(in, "language", htsp->htsp_language); + lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language; if((e = epg_broadcast_find_by_id(eventId, NULL)) == NULL) return htsp_error("Event does not exist"); @@ -871,7 +871,7 @@ htsp_method_getEvents(htsp_connection_t *htsp, htsmsg_t *in) maxTime = htsmsg_get_s64_or_default(in, "maxTime", 0); lang - = htsmsg_get_str_or_default(in, "language", htsp->htsp_language); + = htsmsg_get_str(in, "language") ?: htsp->htsp_language; /* Use event as starting point */ if (e || ch) { @@ -940,7 +940,7 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in) genre.code = u32; eg = &genre; } - lang = htsmsg_get_str_or_default(in, "language", htsp->htsp_language); + lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language; full = htsmsg_get_u32_or_default(in, "full", 0); //do the query @@ -1324,6 +1324,7 @@ htsp_method_change_weight(htsp_connection_t *htsp, htsmsg_t *in) return NULL; } + /** * Open file */ @@ -1358,17 +1359,17 @@ static htsmsg_t * htsp_method_file_read(htsp_connection_t *htsp, htsmsg_t *in) { htsp_file_t *hf = htsp_file_find(htsp, in); - uint64_t off; - uint64_t size; + int64_t off; + int64_t size; if(hf == NULL) return htsp_error("Unknown file id"); - if(htsmsg_get_u64(in, "size", &size)) + if(htsmsg_get_s64(in, "size", &size)) return htsp_error("Missing field 'size'"); /* Seek (optional) */ - if (!htsmsg_get_u64(in, "offset", &off)) + if (!htsmsg_get_s64(in, "offset", &off)) if(lseek(hf->hf_fd, off, SEEK_SET) != off) return htsp_error("Seek error"); @@ -1418,8 +1419,8 @@ htsp_method_file_stat(htsp_connection_t *htsp, htsmsg_t *in) htsmsg_t *rep = htsmsg_create_map(); if(!fstat(hf->hf_fd, &st)) { - htsmsg_add_u64(rep, "size", st.st_size); - htsmsg_add_u64(rep, "mtime", st.st_mtime); + htsmsg_add_s64(rep, "size", st.st_size); + htsmsg_add_s64(rep, "mtime", st.st_mtime); } return rep; } diff --git a/src/misc/dbl.c b/src/misc/dbl.c new file mode 100644 index 00000000..05e5a6dd --- /dev/null +++ b/src/misc/dbl.c @@ -0,0 +1,295 @@ +/* + * Floating point conversion functions. + * Not accurate but should be enough for Showtime's needs + * + * 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 . + */ + +#define _ISOC99_SOURCE + +#include +#include +#include +#include + +#include "dbl.h" + + +double +my_str2double(const char *str, const char **endp) +{ + double ret = 1.0f; + int n = 0, m = 0, o = 0, e = 0; + + if(*str == '-') { + ret = -1.0f; + str++; + } + + while(*str >= '0' && *str <= '9') + n = n * 10 + *str++ - '0'; + + if(*str != '.') { + ret *= n; + } else { + + str++; + + while(*str >= '0' && *str <= '9') { + o = o * 10 + *str++ - '0'; + m--; + } + + ret *= (n + pow(10, m) * o); + } + + if(*str == 'e' || *str == 'E') { + int esign = 1; + str++; + + if(*str == '+') + str++; + else if(*str == '-') { + str++; + esign = -1; + } + + while(*str >= '0' && *str <= '9') + e = e * 10 + *str++ - '0'; + ret *= pow(10, e * esign); + } + + if(endp != NULL) + *endp = str; + + return ret; +} + + + + +/* +** The code that follow is based on "printf" code that dates from the +** 1980s. It is in the public domain. The original comments are +** included here for completeness. They are very out-of-date but +** might be useful as an historical reference. +** +************************************************************************** +** +** The following modules is an enhanced replacement for the "printf" subroutines +** found in the standard C library. The following enhancements are +** supported: +** +** + Additional functions. The standard set of "printf" functions +** includes printf, fprintf, sprintf, vprintf, vfprintf, and +** vsprintf. This module adds the following: +** +** * snprintf -- Works like sprintf, but has an extra argument +** which is the size of the buffer written to. +** +** * mprintf -- Similar to sprintf. Writes output to memory +** obtained from malloc. +** +** * xprintf -- Calls a function to dispose of output. +** +** * nprintf -- No output, but returns the number of characters +** that would have been output by printf. +** +** * A v- version (ex: vsnprintf) of every function is also +** supplied. +** +** + A few extensions to the formatting notation are supported: +** +** * The "=" flag (similar to "-") causes the output to be +** be centered in the appropriately sized field. +** +** * The %b field outputs an integer in binary notation. +** +** * The %c field now accepts a precision. The character output +** is repeated by the number of times the precision specifies. +** +** * The %' field works like %c, but takes as its character the +** next character of the format string, instead of the next +** argument. For example, printf("%.78'-") prints 78 minus +** signs, the same as printf("%.78c",'-'). +** +** + When compiled using GCC on a SPARC, this version of printf is +** faster than the library printf for SUN OS 4.1. +** +** + All functions are fully reentrant. +** +*/ + + +static char +getdigit(double *val, int *cnt) +{ + int digit; + double d; + if( (*cnt)++ >= 16 ) return '0'; + digit = (int)*val; + d = digit; + digit += '0'; + *val = (*val - d)*10.0; + return (char)digit; +} + +#define xGENERIC 0 +#define xFLOAT 1 +#define xEXP 2 + + +int +my_double2str(char *buf, size_t bufsize, double realvalue) +{ + int precision = -1; + char *bufpt; + char prefix; + char xtype = xGENERIC; + int idx, exp, e2; + double rounder; + char flag_exp; + char flag_rtz; + char flag_dp; + char flag_alternateform = 0; + char flag_altform2 = 0; + int nsd; + + if(bufsize < 8) + return -1; + + if( precision<0 ) precision = 20; /* Set default precision */ + if( precision>bufsize/2-10 ) precision = bufsize/2-10; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + prefix = 0; + } + if( xtype==xGENERIC && precision>0 ) precision--; + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){} + + if( xtype==xFLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + + if(isnan(realvalue)) { + strcpy(buf, "NaN"); + return 0; + } + + if( realvalue>0.0 ){ + while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; } + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } + if( exp>350 ){ + if( prefix=='-' ){ + strcpy(buf, "-Inf"); + }else{ + strcpy(buf, "Inf"); + } + return 0; + } + } + bufpt = buf; + + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==xEXP; + if( xtype != xFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==xGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = xEXP; + }else{ + precision = precision - exp; + xtype = xFLOAT; + } + }else{ + flag_rtz = 0; + } + if( xtype==xEXP ){ + e2 = 0; + }else{ + e2 = exp; + } + nsd = 0; + flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; + /* The sign in front of the number */ + if( prefix ){ + *(bufpt++) = prefix; + } + /* Digits prior to the decimal point */ + if( e2<0 ){ + *(bufpt++) = '0'; + }else{ + for(; e2>=0; e2--){ + *(bufpt++) = getdigit(&realvalue,&nsd); + } + } + /* The decimal point */ + if( flag_dp ){ + *(bufpt++) = '.'; + } + /* "0" digits after the decimal point but before the first + ** significant digit of the number */ + for(e2++; e2<0; precision--, e2++){ + assert( precision>0 ); + *(bufpt++) = '0'; + } + /* Significant digits after the decimal point */ + while( (precision--)>0 ){ + *(bufpt++) = getdigit(&realvalue,&nsd); + } + + /* Remove trailing zeros and the "." if no digits follow the "." */ + if( flag_rtz && flag_dp ){ + while( bufpt[-1]=='0' ) *(--bufpt) = 0; + assert( bufpt>buf ); + if( bufpt[-1]=='.' ){ + if( flag_altform2 ){ + *(bufpt++) = '0'; + }else{ + *(--bufpt) = 0; + } + } + } + /* Add the "eNNN" suffix */ + if( flag_exp || xtype==xEXP ){ + *(bufpt++) = 'e'; + if( exp<0 ){ + *(bufpt++) = '-'; exp = -exp; + }else{ + *(bufpt++) = '+'; + } + if( exp>=100 ){ + *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ + *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ + } + *bufpt = 0; + return 0; +} + diff --git a/src/misc/dbl.h b/src/misc/dbl.h new file mode 100644 index 00000000..fc05259c --- /dev/null +++ b/src/misc/dbl.h @@ -0,0 +1,5 @@ +#pragma once + +double my_str2double(const char *str, const char **endp); + +int my_double2str(char *buf, size_t bufsize, double realvalue); diff --git a/src/misc/json.c b/src/misc/json.c new file mode 100644 index 00000000..59f40ebb --- /dev/null +++ b/src/misc/json.c @@ -0,0 +1,432 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#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; + } else if(*s == '"' && s[-1] != '\\') { + + *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 == '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; +} diff --git a/src/misc/json.h b/src/misc/json.h new file mode 100644 index 00000000..8fc1bf26 --- /dev/null +++ b/src/misc/json.h @@ -0,0 +1,31 @@ +#pragma once + +typedef struct json_deserializer { + void *(*jd_create_map)(void *jd_opaque); + void *(*jd_create_list)(void *jd_opaque); + + void (*jd_destroy_obj)(void *jd_opaque, void *obj); + + void (*jd_add_obj)(void *jd_opaque, void *parent, + const char *name, void *child); + + // str must be free'd by callee + void (*jd_add_string)(void *jd_opaque, void *parent, + const char *name, char *str); + + void (*jd_add_long)(void *jd_opaque, void *parent, + const char *name, long v); + + void (*jd_add_double)(void *jd_opaque, void *parent, + const char *name, double d); + + void (*jd_add_bool)(void *jd_opaque, void *parent, + const char *name, int v); + + void (*jd_add_null)(void *jd_opaque, void *parent, + const char *name); + +} json_deserializer_t; + +void *json_deserialize(const char *src, const json_deserializer_t *jd, + void *opaque, char *errbuf, size_t errlen);