Add length calculations to JsonEncode() so we can set Content-Length.

This commit is contained in:
Jordan Bancino 2023-05-06 19:23:13 +00:00
parent bbea55be6c
commit 95ceba0645
9 changed files with 108 additions and 56 deletions

View file

@ -11,40 +11,12 @@ Key:
Milestone: v0.3.0 Milestone: v0.3.0
----------------- -----------------
[~] Move configuration to database [~] /_telodendria/admin/config endpoint
[x] Token permissions [x] Replace entire config
[x] Initial configuration [ ] Update only certain values
[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
[ ] Documentation [ ] 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) [ ] 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 [x] Debug memory leaks with caching
[ ] Debug OpenSSL [ ] Debug OpenSSL
@ -53,12 +25,10 @@ Milestone: v0.3.0
[~] 4: Account management [~] 4: Account management
[~] Deactivate [~] Deactivate
[x] Make sure UserLogin() fails if user is deactivated. [x] Make sure UserLogin() fails if user is deactivated.
[x] Change password
[~] Whoami [~] Whoami
[ ] Attach device id to user object [ ] Attach device id to user object
[ ] Use UserAuthenticate() [ ] Use UserAuthenticate()
[~] 9: User Data [~] 9: User Data
[x] 5: Capabilities negotiation
[ ] 10: Security (Rate Limiting) [ ] 10: Security (Rate Limiting)
Milestone: v0.4.0 Milestone: v0.4.0

View file

@ -652,7 +652,8 @@ HeaderParse(Stream * stream, HeaderExpr * expr)
} }
else else
{ {
/* Cope with preprocessor macro expansions at the top level. */ /* Cope with preprocessor macro expansions at the top
* level. */
expr->type = HP_UNKNOWN; expr->type = HP_UNKNOWN;
strncpy(expr->data.text, word, HEADER_EXPR_MAX); strncpy(expr->data.text, word, HEADER_EXPR_MAX);
} }

View file

@ -333,13 +333,15 @@ JsonValueFree(JsonValue * value)
Free(value); Free(value);
} }
void int
JsonEncodeString(const char *str, Stream * out) JsonEncodeString(const char *str, Stream * out)
{ {
size_t i; size_t i;
char c; char c;
int length = 0;
StreamPutc(out, '"'); StreamPutc(out, '"');
length++;
i = 0; i = 0;
while ((c = str[i]) != '\0') while ((c = str[i]) != '\0')
@ -351,21 +353,27 @@ JsonEncodeString(const char *str, Stream * out)
case '/': case '/':
StreamPutc(out, '\\'); StreamPutc(out, '\\');
StreamPutc(out, c); StreamPutc(out, c);
length += 2;
break; break;
case '\b': case '\b':
StreamPuts(out, "\\b"); StreamPuts(out, "\\b");
length += 2;
break; break;
case '\t': case '\t':
StreamPuts(out, "\\t"); StreamPuts(out, "\\t");
length += 2;
break; break;
case '\n': case '\n':
StreamPuts(out, "\\n"); StreamPuts(out, "\\n");
length += 2;
break; break;
case '\f': case '\f':
StreamPuts(out, "\\f"); StreamPuts(out, "\\f");
length += 2;
break; break;
case '\r': case '\r':
StreamPuts(out, "\\r"); StreamPuts(out, "\\r");
length += 2;
break; break;
default: /* Assume UTF-8 input */ default: /* Assume UTF-8 input */
/* /*
@ -381,11 +389,12 @@ JsonEncodeString(const char *str, Stream * out)
*/ */
if (c <= 0x001F) if (c <= 0x001F)
{ {
StreamPrintf(out, "\\u%04x", c); length += StreamPrintf(out, "\\u%04x", c);
} }
else else
{ {
StreamPutc(out, c); StreamPutc(out, c);
length++;
} }
break; break;
} }
@ -393,6 +402,9 @@ JsonEncodeString(const char *str, Stream * out)
} }
StreamPutc(out, '"'); StreamPutc(out, '"');
length++;
return length;
} }
static char * static char *
@ -567,67 +579,76 @@ JsonDecodeString(Stream * in)
return NULL; return NULL;
} }
void int
JsonEncodeValue(JsonValue * value, Stream * out, int level) JsonEncodeValue(JsonValue * value, Stream * out, int level)
{ {
size_t i; size_t i;
size_t len; size_t len;
Array *arr; Array *arr;
int length = 0;
switch (value->type) switch (value->type)
{ {
case JSON_OBJECT: case JSON_OBJECT:
JsonEncode(value->as.object, out, level >= 0 ? level : level); length += JsonEncode(value->as.object, out, level >= 0 ? level : level);
break; break;
case JSON_ARRAY: case JSON_ARRAY:
arr = value->as.array; arr = value->as.array;
len = ArraySize(arr); len = ArraySize(arr);
StreamPutc(out, '['); StreamPutc(out, '[');
length++;
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
{ {
if (level >= 0) 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) if (i < len - 1)
{ {
StreamPutc(out, ','); StreamPutc(out, ',');
length++;
} }
} }
if (level >= 0) if (level >= 0)
{ {
StreamPrintf(out, "\n%*s", level, ""); length += StreamPrintf(out, "\n%*s", level, "");
} }
StreamPutc(out, ']'); StreamPutc(out, ']');
length++;
break; break;
case JSON_STRING: case JSON_STRING:
JsonEncodeString(value->as.string, out); length += JsonEncodeString(value->as.string, out);
break; break;
case JSON_INTEGER: case JSON_INTEGER:
StreamPrintf(out, "%ld", value->as.integer); length += StreamPrintf(out, "%ld", value->as.integer);
break; break;
case JSON_FLOAT: case JSON_FLOAT:
StreamPrintf(out, "%f", value->as.floating); length += StreamPrintf(out, "%f", value->as.floating);
break; break;
case JSON_BOOLEAN: case JSON_BOOLEAN:
if (value->as.boolean) if (value->as.boolean)
{ {
StreamPuts(out, "true"); StreamPuts(out, "true");
length += 4;
} }
else else
{ {
StreamPuts(out, "false"); StreamPuts(out, "false");
length += 5;
} }
break; break;
case JSON_NULL: case JSON_NULL:
StreamPuts(out, "null"); StreamPuts(out, "null");
length += 4;
break; break;
default: default:
return; return -1;
} }
return length;
} }
int int
@ -637,10 +658,11 @@ JsonEncode(HashMap * object, Stream * out, int level)
size_t count; size_t count;
char *key; char *key;
JsonValue *value; JsonValue *value;
int length;
if (!object || !out) if (!object)
{ {
return 0; return -1;
} }
count = 0; count = 0;
@ -649,10 +671,16 @@ JsonEncode(HashMap * object, Stream * out, int level)
count++; count++;
} }
/* The total number of bytes written */
length = 0;
StreamPutc(out, '{'); StreamPutc(out, '{');
length++;
if (level >= 0) if (level >= 0)
{ {
StreamPutc(out, '\n'); StreamPutc(out, '\n');
length++;
} }
index = 0; index = 0;
@ -661,26 +689,31 @@ JsonEncode(HashMap * object, Stream * out, int level)
if (level >= 0) if (level >= 0)
{ {
StreamPrintf(out, "%*s", level + 2, ""); StreamPrintf(out, "%*s", level + 2, "");
length += level + 2;
} }
JsonEncodeString(key, out); length += JsonEncodeString(key, out);
StreamPutc(out, ':'); StreamPutc(out, ':');
length++;
if (level >= 0) if (level >= 0)
{ {
StreamPutc(out, ' '); StreamPutc(out, ' ');
length++;
} }
JsonEncodeValue(value, out, level >= 0 ? level + 2 : level); length += JsonEncodeValue(value, out, level >= 0 ? level + 2 : level);
if (index < count - 1) if (index < count - 1)
{ {
StreamPutc(out, ','); StreamPutc(out, ',');
length++;
} }
if (level >= 0) if (level >= 0)
{ {
StreamPutc(out, '\n'); StreamPutc(out, '\n');
length++;
} }
index++; index++;
@ -689,10 +722,12 @@ JsonEncode(HashMap * object, Stream * out, int level)
if (level >= 0) if (level >= 0)
{ {
StreamPrintf(out, "%*s", level, ""); StreamPrintf(out, "%*s", level, "");
length += level;
} }
StreamPutc(out, '}'); StreamPutc(out, '}');
length++;
return 1; return length;
} }
void void

View file

@ -93,9 +93,14 @@ MatrixHttpHandler(HttpServerContext * context, void *argp)
*/ */
if (response) if (response)
{ {
char *contentLen = StrInt(JsonEncode(response, NULL, JSON_DEFAULT));
HttpResponseHeader(context, "Content-Type", "application/json"); HttpResponseHeader(context, "Content-Type", "application/json");
HttpResponseHeader(context, "Content-Length", contentLen);
HttpSendHeaders(context); HttpSendHeaders(context);
Free(contentLen);
stream = HttpServerStream(context); stream = HttpServerStream(context);
JsonEncode(response, stream, JSON_DEFAULT); JsonEncode(response, stream, JSON_DEFAULT);
JsonFree(response); JsonFree(response);

View file

@ -258,3 +258,21 @@ StrRandom(size_t len)
str[len] = '\0'; str[len] = '\0';
return str; 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;
}

View file

@ -250,7 +250,7 @@ StreamVprintf(Stream * stream, const char *fmt, va_list ap)
int ret; int ret;
if (!stream || !fmt) if (!fmt)
{ {
return -1; return -1;
} }
@ -264,9 +264,12 @@ StreamVprintf(Stream * stream, const char *fmt, va_list ap)
ret = vfprintf(fp, fmt, ap); ret = vfprintf(fp, fmt, ap);
fclose(fp); 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 free(buf); /* Allocated by stdlib, not Memory

View file

@ -247,8 +247,12 @@ extern void JsonFree(HashMap *);
* CanonicalJson encoder. This will typically be used for encoding * CanonicalJson encoder. This will typically be used for encoding
* object keys; to encode values, just use * object keys; to encode values, just use
* .Fn JsonEncodeValue . * .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 * 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 . * .Va JSON_PRETTY .
* To get minified output, set it to * To get minified output, set it to
* .Va JSON_DEFAULT . * .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 * Encode a JSON object as it would appear in JSON output, writing it
* to the given output stream. This function is recursive; it will * to the given output stream. This function is recursive; it will
* serialize everything accessible from the passed object. The third * serialize everything accessible from the passed object. The third
* parameter has the same behavior as described above. * 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); extern int JsonEncode(HashMap *, Stream *, int);

View file

@ -88,4 +88,12 @@ extern int StrBlank(const char *str);
*/ */
extern char * StrRandom(size_t); 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 */ #endif /* TELODENDRIA_STR_H */