From 95ceba0645dc23f149941ec27933bddabe8dc584 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sat, 6 May 2023 19:23:13 +0000 Subject: [PATCH] Add length calculations to JsonEncode() so we can set Content-Length. --- TODO.txt | 36 +++---------------------- src/HeaderParser.c | 3 ++- src/Json.c | 67 +++++++++++++++++++++++++++++++++++----------- src/Matrix.c | 5 ++++ src/Str.c | 18 +++++++++++++ src/Stream.c | 9 ++++--- src/include/Json.h | 16 +++++++++-- src/include/Str.h | 8 ++++++ tools/src/hdoc.c | 2 +- 9 files changed, 108 insertions(+), 56 deletions(-) diff --git a/TODO.txt b/TODO.txt index d83cc6e..63410b9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,40 +11,12 @@ Key: Milestone: v0.3.0 ----------------- -[~] Move configuration to database - [x] Token permissions - [x] Initial configuration - [x] If no config, create one-time use registration token that - grants user admin privileges. - [~] /_telodendria/admin/config endpoint - [x] JsonDuplicate() - [x] Refactor TelodendriaConfig to just Config +[~] /_telodendria/admin/config endpoint + [x] Replace entire config + [ ] Update only certain values [ ] Documentation - [x] Array - [x] Io - [x] Stream - [x] Tls - [x] HttpClient - [x] Uri - [x] td - [x] tt - [x] http-debug-server - [x] tp - [x] send-patch - [x] Log - [x] TelodendriaConfig -> Config - [x] HashMap - [x] HttpRouter - [x] Html - [x] Str - [x] HeaderParser [ ] hdoc(1) and hdoc(5) - [x] telodendria-admin - [x] telodendria-setup - [x] telodendria-config - [x] Refactor dev pages so function description and - return value are in the same location. [x] Debug memory leaks with caching [ ] Debug OpenSSL @@ -53,12 +25,10 @@ Milestone: v0.3.0 [~] 4: Account management [~] Deactivate [x] Make sure UserLogin() fails if user is deactivated. - [x] Change password [~] Whoami [ ] Attach device id to user object [ ] Use UserAuthenticate() [~] 9: User Data - [x] 5: Capabilities negotiation [ ] 10: Security (Rate Limiting) Milestone: v0.4.0 diff --git a/src/HeaderParser.c b/src/HeaderParser.c index 7fb2c56..b53b573 100644 --- a/src/HeaderParser.c +++ b/src/HeaderParser.c @@ -652,7 +652,8 @@ HeaderParse(Stream * stream, HeaderExpr * expr) } else { - /* Cope with preprocessor macro expansions at the top level. */ + /* Cope with preprocessor macro expansions at the top + * level. */ expr->type = HP_UNKNOWN; strncpy(expr->data.text, word, HEADER_EXPR_MAX); } diff --git a/src/Json.c b/src/Json.c index c9a0f37..670ef24 100644 --- a/src/Json.c +++ b/src/Json.c @@ -333,13 +333,15 @@ JsonValueFree(JsonValue * value) Free(value); } -void +int JsonEncodeString(const char *str, Stream * out) { size_t i; char c; + int length = 0; StreamPutc(out, '"'); + length++; i = 0; while ((c = str[i]) != '\0') @@ -351,21 +353,27 @@ JsonEncodeString(const char *str, Stream * out) case '/': StreamPutc(out, '\\'); StreamPutc(out, c); + length += 2; break; case '\b': StreamPuts(out, "\\b"); + length += 2; break; case '\t': StreamPuts(out, "\\t"); + length += 2; break; case '\n': StreamPuts(out, "\\n"); + length += 2; break; case '\f': StreamPuts(out, "\\f"); + length += 2; break; case '\r': StreamPuts(out, "\\r"); + length += 2; break; default: /* Assume UTF-8 input */ /* @@ -381,11 +389,12 @@ JsonEncodeString(const char *str, Stream * out) */ if (c <= 0x001F) { - StreamPrintf(out, "\\u%04x", c); + length += StreamPrintf(out, "\\u%04x", c); } else { StreamPutc(out, c); + length++; } break; } @@ -393,6 +402,9 @@ JsonEncodeString(const char *str, Stream * out) } StreamPutc(out, '"'); + length++; + + return length; } static char * @@ -567,67 +579,76 @@ JsonDecodeString(Stream * in) return NULL; } -void +int JsonEncodeValue(JsonValue * value, Stream * out, int level) { size_t i; size_t len; Array *arr; + int length = 0; switch (value->type) { case JSON_OBJECT: - JsonEncode(value->as.object, out, level >= 0 ? level : level); + length += JsonEncode(value->as.object, out, level >= 0 ? level : level); break; case JSON_ARRAY: arr = value->as.array; len = ArraySize(arr); StreamPutc(out, '['); + length++; for (i = 0; i < len; i++) { if (level >= 0) { - StreamPrintf(out, "\n%*s", level + 2, ""); + length += StreamPrintf(out, "\n%*s", level + 2, ""); } - JsonEncodeValue(ArrayGet(arr, i), out, level >= 0 ? level + 2 : level); + length += JsonEncodeValue(ArrayGet(arr, i), out, level >= 0 ? level + 2 : level); if (i < len - 1) { StreamPutc(out, ','); + length++; } } if (level >= 0) { - StreamPrintf(out, "\n%*s", level, ""); + length += StreamPrintf(out, "\n%*s", level, ""); } StreamPutc(out, ']'); + length++; break; case JSON_STRING: - JsonEncodeString(value->as.string, out); + length += JsonEncodeString(value->as.string, out); break; case JSON_INTEGER: - StreamPrintf(out, "%ld", value->as.integer); + length += StreamPrintf(out, "%ld", value->as.integer); break; case JSON_FLOAT: - StreamPrintf(out, "%f", value->as.floating); + length += StreamPrintf(out, "%f", value->as.floating); break; case JSON_BOOLEAN: if (value->as.boolean) { StreamPuts(out, "true"); + length += 4; } else { StreamPuts(out, "false"); + length += 5; } break; case JSON_NULL: StreamPuts(out, "null"); + length += 4; break; default: - return; + return -1; } + + return length; } int @@ -637,10 +658,11 @@ JsonEncode(HashMap * object, Stream * out, int level) size_t count; char *key; JsonValue *value; + int length; - if (!object || !out) + if (!object) { - return 0; + return -1; } count = 0; @@ -649,10 +671,16 @@ JsonEncode(HashMap * object, Stream * out, int level) count++; } + /* The total number of bytes written */ + length = 0; + StreamPutc(out, '{'); + length++; + if (level >= 0) { StreamPutc(out, '\n'); + length++; } index = 0; @@ -661,26 +689,31 @@ JsonEncode(HashMap * object, Stream * out, int level) if (level >= 0) { StreamPrintf(out, "%*s", level + 2, ""); + length += level + 2; } - JsonEncodeString(key, out); + length += JsonEncodeString(key, out); StreamPutc(out, ':'); + length++; if (level >= 0) { StreamPutc(out, ' '); + length++; } - JsonEncodeValue(value, out, level >= 0 ? level + 2 : level); + length += JsonEncodeValue(value, out, level >= 0 ? level + 2 : level); if (index < count - 1) { StreamPutc(out, ','); + length++; } if (level >= 0) { StreamPutc(out, '\n'); + length++; } index++; @@ -689,10 +722,12 @@ JsonEncode(HashMap * object, Stream * out, int level) if (level >= 0) { StreamPrintf(out, "%*s", level, ""); + length += level; } StreamPutc(out, '}'); + length++; - return 1; + return length; } void diff --git a/src/Matrix.c b/src/Matrix.c index a8548cc..db6b7a4 100644 --- a/src/Matrix.c +++ b/src/Matrix.c @@ -93,9 +93,14 @@ MatrixHttpHandler(HttpServerContext * context, void *argp) */ if (response) { + char *contentLen = StrInt(JsonEncode(response, NULL, JSON_DEFAULT)); + HttpResponseHeader(context, "Content-Type", "application/json"); + HttpResponseHeader(context, "Content-Length", contentLen); HttpSendHeaders(context); + Free(contentLen); + stream = HttpServerStream(context); JsonEncode(response, stream, JSON_DEFAULT); JsonFree(response); diff --git a/src/Str.c b/src/Str.c index ea4e2a9..12b7f03 100644 --- a/src/Str.c +++ b/src/Str.c @@ -258,3 +258,21 @@ StrRandom(size_t len) str[len] = '\0'; return str; } + +char * +StrInt(long i) +{ + char *str; + int len; + + len = snprintf(NULL, 0, "%ld", i); + str = Malloc(len + 1); + if (!str) + { + return NULL; + } + + snprintf(str, len + 1, "%ld", i); + + return str; +} diff --git a/src/Stream.c b/src/Stream.c index 7241bf3..2cb299f 100644 --- a/src/Stream.c +++ b/src/Stream.c @@ -250,7 +250,7 @@ StreamVprintf(Stream * stream, const char *fmt, va_list ap) int ret; - if (!stream || !fmt) + if (!fmt) { return -1; } @@ -264,9 +264,12 @@ StreamVprintf(Stream * stream, const char *fmt, va_list ap) ret = vfprintf(fp, fmt, ap); fclose(fp); - if (ret >= 0) + if (ret >= 0 && stream) { - ret = StreamPuts(stream, buf); + if (StreamPuts(stream, buf) < 0) + { + ret = -1; + }; } free(buf); /* Allocated by stdlib, not Memory diff --git a/src/include/Json.h b/src/include/Json.h index aa97ae1..e2442dd 100644 --- a/src/include/Json.h +++ b/src/include/Json.h @@ -247,8 +247,12 @@ extern void JsonFree(HashMap *); * CanonicalJson encoder. This will typically be used for encoding * object keys; to encode values, just use * .Fn JsonEncodeValue . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. */ -extern void JsonEncodeString(const char *, Stream *); +extern int JsonEncodeString(const char *, Stream *); /** * Serialize a JSON value as it would appear in JSON output. This is @@ -267,14 +271,22 @@ extern void JsonEncodeString(const char *, Stream *); * .Va JSON_PRETTY . * To get minified output, set it to * .Va JSON_DEFAULT . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. */ -extern void JsonEncodeValue(JsonValue *, Stream *, int); +extern int JsonEncodeValue(JsonValue *, Stream *, int); /** * Encode a JSON object as it would appear in JSON output, writing it * to the given output stream. This function is recursive; it will * serialize everything accessible from the passed object. The third * parameter has the same behavior as described above. + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. */ extern int JsonEncode(HashMap *, Stream *, int); diff --git a/src/include/Str.h b/src/include/Str.h index c0a89f0..390a189 100644 --- a/src/include/Str.h +++ b/src/include/Str.h @@ -88,4 +88,12 @@ extern int StrBlank(const char *str); */ extern char * StrRandom(size_t); +/** + * Convert the specified integer into a string, returning the string + * on the heap, or NULL if there was a memory allocation error. The + * returned string should be freed by the caller after it is no longer + * needed. + */ +extern char * StrInt(long); + #endif /* TELODENDRIA_STR_H */ diff --git a/tools/src/hdoc.c b/tools/src/hdoc.c index a02ec70..9315f62 100644 --- a/tools/src/hdoc.c +++ b/tools/src/hdoc.c @@ -321,7 +321,7 @@ main(int argc, char **argv) break; } StreamPrintf(StreamStderr(), "Warning: Unknown expression: %s\n", - expr.data.text); + expr.data.text); break; default: StreamPrintf(StreamStderr(), "Unknown header type: %d\n", expr.type);