diff --git a/Makefile b/Makefile index f3ff00b3..60f2debf 100644 --- a/Makefile +++ b/Makefile @@ -93,18 +93,6 @@ TINYCBOR_SOURCES = \ src/cbortojson.c \ src/cborvalidation.c \ # -# if open_memstream is unavailable on the system, try to implement our own -# version using funopen or fopencookie -ifeq ($(open_memstream-pass),) - ifeq ($(funopen-pass)$(fopencookie-pass),) - CFLAGS += -DWITHOUT_OPEN_MEMSTREAM - ifeq ($(wildcard .config),.config) - $(warning warning: funopen and fopencookie unavailable, open_memstream can not be implemented and conversion to JSON will not work properly!) - endif - else - TINYCBOR_SOURCES += src/open_memstream.c - endif -endif endif # json2cbor depends on an external library (cjson) diff --git a/Makefile.configure b/Makefile.configure index c2f51eea..e68d277b 100644 --- a/Makefile.configure +++ b/Makefile.configure @@ -1,9 +1,8 @@ -ALLTESTS = open_memstream funopen fopencookie gc_sections \ +ALLTESTS = funopen fopencookie gc_sections \ system-cjson cjson freestanding MAKEFILE := $(lastword $(MAKEFILE_LIST)) OUT := -PROGRAM-open_memstream = extern int open_memstream(); int main() { return open_memstream(); } PROGRAM-funopen = extern int funopen(); int main() { return funopen(); } PROGRAM-fopencookie = extern int fopencookie(); int main() { return fopencookie(); } PROGRAM-gc_sections = int main() {} diff --git a/Makefile.nmake b/Makefile.nmake index 04b58ab4..13f27845 100644 --- a/Makefile.nmake +++ b/Makefile.nmake @@ -5,6 +5,7 @@ TINYCBOR_SOURCES = \ src\cborerrorstrings.c \ src\cborencoder.c \ src\cborencoder_close_container_checked.c \ + src\cbortojson.c \ src\cborparser.c \ src\cborparser_dup_string.c \ src\cborpretty.c \ @@ -14,6 +15,7 @@ TINYCBOR_OBJS = \ src\cborerrorstrings.obj \ src\cborencoder.obj \ src\cborencoder_close_container_checked.obj \ + src\cbortojson.obj \ src\cborparser.obj \ src\cborparser_dup_string.obj \ src\cborpretty.obj \ diff --git a/src/cborjson.h b/src/cborjson.h index 8ff27b92..4a861eaa 100644 --- a/src/cborjson.h +++ b/src/cborjson.h @@ -47,6 +47,7 @@ enum CborToJsonFlags CborConvertDefaultFlags = 0 }; +CBOR_API CborError cbor_value_to_json_stream(CborStreamFunction stream, void* token, CborValue *it, int flags); CBOR_API CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags); CBOR_INLINE_API CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) { diff --git a/src/cbortojson.c b/src/cbortojson.c index 4b11d319..e5627dc1 100644 --- a/src/cbortojson.c +++ b/src/cbortojson.c @@ -39,6 +39,7 @@ #include #include #include +#include /** * \defgroup CborToJson Converting CBOR to JSON @@ -146,7 +147,7 @@ * the keys for the metadata clash with existing keys in the JSON map. */ -extern FILE *open_memstream(char **bufptr, size_t *sizeptr); +#define IF_NO_ERROR(e, proc) do { if(!e) { e = proc; } } while(0) enum ConversionStatusFlags { TypeWasNotNative = 0x100, /* anything but strings, boolean, null, arrays and maps */ @@ -165,7 +166,7 @@ typedef struct ConversionStatus { int flags; } ConversionStatus; -static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); +static CborError value_to_json(CborStreamFunction stream, void *token, CborValue *it, int flags, CborType type, ConversionStatus *status); static CborError dump_bytestring_base16(char **result, CborValue *it) { @@ -290,7 +291,7 @@ static CborError dump_bytestring_base64url(char **result, CborValue *it) return generic_dump_base64(result, it, alphabet); } -static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status) +static CborError add_value_metadata(CborStreamFunction stream, void *out, CborType type, const ConversionStatus *status) { int flags = status->flags; if (flags & TypeWasTagged) { @@ -298,8 +299,8 @@ static CborError add_value_metadata(FILE *out, CborType type, const ConversionSt type = flags & FinalTypeMask; flags &= ~(FinalTypeMask | TypeWasTagged); - if (fprintf(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, - flags & ~TypeWasTagged ? "," : "") < 0) + if (stream(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, + flags & ~TypeWasTagged ? "," : "")) return CborErrorIO; } @@ -307,21 +308,21 @@ static CborError add_value_metadata(FILE *out, CborType type, const ConversionSt return CborNoError; /* print at least the type */ - if (fprintf(out, "\"t\":%d", type) < 0) + if (stream(out, "\"t\":%d", type)) return CborErrorIO; if (flags & NumberWasNaN) - if (fprintf(out, ",\"v\":\"nan\"") < 0) + if (stream(out, ",\"v\":\"nan\"")) return CborErrorIO; if (flags & NumberWasInfinite) - if (fprintf(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "") < 0) + if (stream(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "")) return CborErrorIO; if (flags & NumberPrecisionWasLost) - if (fprintf(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', - status->originalNumber) < 0) + if (stream(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', + status->originalNumber)) return CborErrorIO; if (type == CborSimpleType) - if (fprintf(out, ",\"v\":%d", (int)status->originalNumber) < 0) + if (stream(out, ",\"v\":%d", (int)status->originalNumber)) return CborErrorIO; return CborNoError; } @@ -341,7 +342,7 @@ static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) return err; } -static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError tagged_value_to_json(CborStreamFunction stream, void *out, CborValue *it, int flags, ConversionStatus *status) { CborTag tag; CborError err; @@ -352,20 +353,20 @@ static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, Conve if (err) return err; - if (fprintf(out, "{\"tag%" PRIu64 "\":", tag) < 0) + if (stream(out, "{\"tag%" PRIu64 "\":", tag)) return CborErrorIO; CborType type = cbor_value_get_type(it); - err = value_to_json(out, it, flags, type, status); + err = value_to_json(stream, out, it, flags, type, status); if (err) return err; if (flags & CborConvertAddMetadata && status->flags) { - if (fprintf(out, ",\"tag%" PRIu64 "$cbor\":{", tag) < 0 || - add_value_metadata(out, type, status) != CborNoError || - fputc('}', out) < 0) + if (stream(out, ",\"tag%" PRIu64 "$cbor\":{", tag) || + add_value_metadata(stream, out, type, status) != CborNoError || + stream(out, "%c", '}')) return CborErrorIO; } - if (fputc('}', out) < 0) + if (stream(out, "%c", '}')) return CborErrorIO; status->flags = TypeWasNotNative | CborTagType; return CborNoError; @@ -393,109 +394,117 @@ static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, Conve } if (err) return err; - err = fprintf(out, "\"%s%s\"", pre, str) < 0 ? CborErrorIO : CborNoError; + err = stream(out, "\"%s%s\"", pre, str); free(str); status->flags = TypeWasNotNative | TypeWasTagged | CborByteStringType; return err; } /* no special handling */ - err = value_to_json(out, it, flags, type, status); + err = value_to_json(stream, out, it, flags, type, status); status->flags |= TypeWasTagged | type; return err; } -static CborError stringify_map_key(char **key, CborValue *it, int flags, CborType type) -{ - (void)flags; /* unused */ - (void)type; /* unused */ -#ifdef WITHOUT_OPEN_MEMSTREAM - (void)key; /* unused */ - (void)it; /* unused */ - return CborErrorJsonNotImplemented; -#else - size_t size; - - FILE *memstream = open_memstream(key, &size); - if (memstream == NULL) - return CborErrorOutOfMemory; /* could also be EMFILE, but it's unlikely */ - CborError err = cbor_value_to_pretty_advance(memstream, it); - - if (unlikely(fclose(memstream) < 0 || *key == NULL)) - return CborErrorInternalError; - return err; -#endif -} - -static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError array_to_json(CborStreamFunction stream, void *out, CborValue *it, int flags, ConversionStatus *status) { const char *comma = ""; while (!cbor_value_at_end(it)) { - if (fprintf(out, "%s", comma) < 0) + if (stream(out, "%s", comma)) return CborErrorIO; comma = ","; - CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); + CborError err = value_to_json(stream, out, it, flags, cbor_value_get_type(it), status); if (err) return err; } return CborNoError; } -static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError put_string_to_stream(CborStreamFunction stream, void* out, CborValue *it) +{ + char *string = NULL; + size_t n = 0; + CborError err = cbor_value_dup_text_string(it, &string, &n, it); + if (err) + return err; + + err = stream(out, "%s", string); + free(string); + + return err; +} + +static CborError map_to_json(CborStreamFunction stream, void *out, CborValue *it, int flags, ConversionStatus *status) { const char *comma = ""; - CborError err; + CborError err = CborNoError; while (!cbor_value_at_end(it)) { - char *key; - if (fprintf(out, "%s", comma) < 0) + /* Remember the iterator position for re-read the key value */ + CborValue it_key = *it; + + if (stream(out, "%s", comma)) return CborErrorIO; comma = ","; + /* first, print the key */ CborType keyType = cbor_value_get_type(it); if (likely(keyType == CborTextStringType)) { - size_t n = 0; - err = cbor_value_dup_text_string(it, &key, &n, it); + IF_NO_ERROR(err, stream(out, "\"")); + IF_NO_ERROR(err, put_string_to_stream(stream, out, it) ); + IF_NO_ERROR(err, stream(out, "\":")); + + if (err) + return CborErrorIO; } else if (flags & CborConvertStringifyMapKeys) { - err = stringify_map_key(&key, it, flags, keyType); + IF_NO_ERROR(err, stream(out, "\"")); + IF_NO_ERROR(err, cbor_value_to_pretty_stream(stream, out, it, CborPrettyDefaultFlags)); + IF_NO_ERROR(err, stream(out, "\":")); + if (err) + return err; } else { return CborErrorJsonObjectKeyNotString; } - if (err) - return err; - - /* first, print the key */ - if (fprintf(out, "\"%s\":", key) < 0) { - free(key); - return CborErrorIO; - } /* then, print the value */ CborType valueType = cbor_value_get_type(it); - err = value_to_json(out, it, flags, valueType, status); + err = value_to_json(stream, out, it, flags, valueType, status); /* finally, print any metadata we may have */ if (flags & CborConvertAddMetadata) { if (!err && keyType != CborTextStringType) { - if (fprintf(out, ",\"%s$keycbordump\":true", key) < 0) + /* Might be read the key again in the next if-clause, keep this. */ + CborValue tmpval = it_key; + IF_NO_ERROR(err, stream(out, ",\"")); + IF_NO_ERROR(err, cbor_value_to_pretty_stream(stream, out, &tmpval, CborPrettyDefaultFlags)); + IF_NO_ERROR(err, stream(out, "$keycbordump\":true")); + + if(err) err = CborErrorIO; } if (!err && status->flags) { - if (fprintf(out, ",\"%s$cbor\":{", key) < 0 || - add_value_metadata(out, valueType, status) != CborNoError || - fputc('}', out) < 0) + IF_NO_ERROR(err, stream(out, ",\"")); + if (keyType == CborTextStringType) { + IF_NO_ERROR(err, put_string_to_stream(stream, out, &it_key)); + } else { + IF_NO_ERROR(err, cbor_value_to_pretty_stream(stream, out, &it_key, CborPrettyDefaultFlags)); + } + IF_NO_ERROR(err, stream(out, "$cbor\":{")); + IF_NO_ERROR(err, add_value_metadata(stream, out, valueType, status)); + IF_NO_ERROR(err, stream(out, "%c", '}')); + + if(err) err = CborErrorIO; } } - free(key); if (err) return err; } return CborNoError; } -static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) +static CborError value_to_json(CborStreamFunction stream, void *out, CborValue *it, int flags, CborType type, ConversionStatus *status) { CborError err; status->flags = 0; @@ -510,18 +519,18 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ it->ptr = recursed.ptr; return err; /* parse error */ } - if (fputc(type == CborArrayType ? '[' : '{', out) < 0) + if (stream(out, "%c", (type == CborArrayType ? '[' : '{') )) return CborErrorIO; err = (type == CborArrayType) ? - array_to_json(out, &recursed, flags, status) : - map_to_json(out, &recursed, flags, status); + array_to_json(stream, out, &recursed, flags, status) : + map_to_json(stream, out, &recursed, flags, status); if (err) { it->ptr = recursed.ptr; return err; /* parse error */ } - if (fputc(type == CborArrayType ? ']' : '}', out) < 0) + if (stream(out, "%c", (type == CborArrayType ? ']' : '}') )) return CborErrorIO; err = cbor_value_leave_container(it, &recursed); if (err) @@ -549,7 +558,7 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ status->originalNumber = val; } } - if (fprintf(out, "%.0f", num) < 0) /* this number has no fraction, so no decimal points please */ + if (stream(out, "%.0f", num)) /* this number has no fraction, so no decimal points please */ return CborErrorIO; break; } @@ -566,39 +575,39 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ } if (err) return err; - err = (fprintf(out, "\"%s\"", str) < 0) ? CborErrorIO : CborNoError; + err = (stream(out, "\"%s\"", str)); free(str); return err; } case CborTagType: - return tagged_value_to_json(out, it, flags, status); + return tagged_value_to_json(stream, out, it, flags, status); case CborSimpleType: { uint8_t simple_type; cbor_value_get_simple_type(it, &simple_type); /* can't fail */ status->flags = TypeWasNotNative; status->originalNumber = simple_type; - if (fprintf(out, "\"simple(%" PRIu8 ")\"", simple_type) < 0) + if (stream(out, "\"simple(%" PRIu8 ")\"", simple_type)) return CborErrorIO; break; } case CborNullType: - if (fprintf(out, "null") < 0) + if (stream(out, "null")) return CborErrorIO; break; case CborUndefinedType: status->flags = TypeWasNotNative; - if (fprintf(out, "\"undefined\"") < 0) + if (stream(out, "\"undefined\"")) return CborErrorIO; break; case CborBooleanType: { bool val; cbor_value_get_boolean(it, &val); /* can't fail */ - if (fprintf(out, val ? "true" : "false") < 0) + if (stream(out, val ? "true" : "false")) return CborErrorIO; break; } @@ -630,7 +639,7 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ int r = fpclassify(val); if (r == FP_NAN || r == FP_INFINITE) { - if (fprintf(out, "null") < 0) + if (stream(out, "null")) return CborErrorIO; status->flags |= r == FP_NAN ? NumberWasNaN : NumberWasInfinite | (val < 0 ? NumberWasNegative : 0); @@ -638,13 +647,13 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ uint64_t ival = (uint64_t)fabs(val); if ((double)ival == fabs(val)) { /* print as integer so we get the full precision */ - r = fprintf(out, "%s%" PRIu64, val < 0 ? "-" : "", ival); + r = stream(out, "%s%" PRIu64, val < 0 ? "-" : "", ival); status->flags |= TypeWasNotNative; /* mark this integer number as a double */ } else { /* this number is definitely not a 64-bit integer */ - r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g", val); + r = stream(out, "%." DBL_DECIMAL_DIG_STR "g", val); } - if (r < 0) + if (r) return CborErrorIO; } break; @@ -689,6 +698,40 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ * \sa cbor_value_to_json_advance(), cbor_value_to_pretty() */ +/** + * Converts the current CBOR type pointed by \a value to JSON and writes that + * to the stream by calling the \a streamFunction. If an error occurs, + * this function returns an error code similar to \ref CborParsing. + * The \a flags parameter indicates one or more of the flags from CborToJsonFlags + * that control the conversion. + * + * If no error ocurred, this function advances \a value to the next element. + * + * The \a streamFunction function will be called with the \a token value as the + * first parameter and a printf-style format string as the second, with a variable + * number of further parameters. + * + * \sa cbor_value_to_pretty_stream(), cbor_value_to_json_advance() + */ + +CborError cbor_value_to_json_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags) +{ + ConversionStatus status; + return value_to_json(streamFunction, token, value, flags, cbor_value_get_type(value), &status); +} + +static CborError cborjson_fprintf(void *out, const char *fmt, ...) +{ + int n; + + va_list list; + va_start(list, fmt); + n = vfprintf((FILE *)out, fmt, list); + va_end(list); + + return n < 0 ? CborErrorIO : CborNoError; +} + /** * Converts the current CBOR type pointed to by \a value to JSON and writes that * to the \a out stream. If an error occurs, this function returns an error @@ -702,7 +745,7 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags) { ConversionStatus status; - return value_to_json(out, value, flags, cbor_value_get_type(value), &status); + return value_to_json(cborjson_fprintf, out, value, flags, cbor_value_get_type(value), &status); } /** @} */ diff --git a/src/open_memstream.c b/src/open_memstream.c deleted file mode 100644 index 33653784..00000000 --- a/src/open_memstream.c +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#define _GNU_SOURCE 1 - -#include -#include -#include -#include -#include - -#if defined(__unix__) || defined(__APPLE__) -# include -#endif -#ifdef __APPLE__ -typedef int RetType; -typedef int LenType; -#elif __linux__ -typedef ssize_t RetType; -typedef size_t LenType; -#else -# error "Cannot implement open_memstream!" -#endif - -#include "compilersupport_p.h" - -struct Buffer -{ - char **ptr; - size_t *len; - size_t alloc; -}; - -static RetType write_to_buffer(void *cookie, const char *data, LenType len) -{ - struct Buffer *b = (struct Buffer *)cookie; - char *ptr = *b->ptr; - size_t newsize; - - errno = EFBIG; - if (unlikely(add_check_overflow(*b->len, len, &newsize))) - return -1; - - if (newsize >= b->alloc) { // NB! one extra byte is needed to avoid buffer overflow at close_buffer - // make room - size_t newalloc = newsize + newsize / 2 + 1; // give 50% more room - ptr = realloc(ptr, newalloc); - if (ptr == NULL) - return -1; - b->alloc = newalloc; - *b->ptr = ptr; - } - - memcpy(ptr + *b->len, data, len); - *b->len = newsize; - return len; -} - -static int close_buffer(void *cookie) -{ - struct Buffer *b = (struct Buffer *)cookie; - if (*b->ptr) - (*b->ptr)[*b->len] = '\0'; - free(b); - return 0; -} - -FILE *open_memstream(char **bufptr, size_t *lenptr) -{ - struct Buffer *b = (struct Buffer *)malloc(sizeof(struct Buffer)); - if (b == NULL) - return NULL; - b->alloc = 0; - b->len = lenptr; - b->ptr = bufptr; - *bufptr = NULL; - *lenptr = 0; - -#ifdef __APPLE__ - return funopen(b, NULL, write_to_buffer, NULL, close_buffer); -#elif __linux__ - static const cookie_io_functions_t vtable = { - NULL, - write_to_buffer, - NULL, - close_buffer - }; - return fopencookie(b, "w", vtable); -#endif -} - diff --git a/tests/tests.pro b/tests/tests.pro index 6036f0f9..03b94d97 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -1,3 +1,2 @@ TEMPLATE = subdirs SUBDIRS = parser encoder c90 cpp tojson -msvc: SUBDIRS -= tojson diff --git a/tests/tojson/tst_tojson.cpp b/tests/tojson/tst_tojson.cpp index 8bf24efb..d1e0e23c 100644 --- a/tests/tojson/tst_tojson.cpp +++ b/tests/tojson/tst_tojson.cpp @@ -27,7 +27,25 @@ #include "cborjson.h" #include -extern "C" FILE *open_memstream(char **bufptr, size_t *sizeptr); +static CborError qstring_stream(void *out, const char *fmt, ...) +{ + int n; + + QString* qstr = (QString*)out; + + QString str; + + va_list list; + va_start(list, fmt); + str.vsprintf(fmt, list); + va_end(list); + + qstr->append(str); + n = str.length(); + + return n < 0 ? CborErrorIO : CborNoError; +} + class tst_ToJson : public QObject { @@ -197,15 +215,11 @@ void addEmptyContainersData() CborError parseOne(CborValue *it, QString *parsed, int flags) { - char *buffer; - size_t size; + QString buffer; + CborError err = cbor_value_to_json_stream(qstring_stream, &buffer, it, flags); - FILE *f = open_memstream(&buffer, &size); - CborError err = cbor_value_to_json_advance(f, it, flags); - fclose(f); + *parsed = buffer; - *parsed = QString::fromLatin1(buffer); - free(buffer); return err; }