summary refs log tree commit diff
diff options
context:
space:
mode:
authorsternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-09-26 16:38:05 +0200
committersternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-09-26 16:46:24 +0200
commite949a8baea7cc2a35885f1016276d30f571ff8b6 (patch)
treed41ed74fd6c5ef7721ccc7fc52ff1bca1576301f
parentbaecfd220afc0dd0dca31101637e29d418e699f5 (diff)
refactor(warteraum): replace json_output with own emitjson
Still somewhat limited, but implements everything we need in warteraum.
API is very similar to json_output, but we choose to not implement
buffer handling and use open_memstream instead. The FILE * abstraction
suits us very well here since we just want to write chars to some kind
of output.
-rw-r--r--README.adoc1
-rw-r--r--default.nix3
-rw-r--r--warteraum/build_config1
-rw-r--r--warteraum/default.o.do3
-rw-r--r--warteraum/emitjson.c169
-rw-r--r--warteraum/emitjson.h36
-rw-r--r--warteraum/main.c119
-rw-r--r--warteraum/test/all.do2
-rw-r--r--warteraum/test/default.exe.do3
-rwxr-xr-xwarteraum/test/run15
-rw-r--r--warteraum/test/test_emitjson.c54
-rw-r--r--warteraum/warteraum.do2
12 files changed, 359 insertions, 49 deletions
diff --git a/README.adoc b/README.adoc
index 933b274..b791064 100644
--- a/README.adoc
+++ b/README.adoc
@@ -196,7 +196,6 @@ Help is welcome! Some things that remain to be done:
 * Important: Documentation. Annoying sterni into doing it is also helping.
 * Make `warteraum` accept `application/json` request bodies for the `v2`
   API using http://www.catb.org/~esr/microjson/[microjson] (?).
-* Rewrite `json_output.c` to avoid licensing issue
 * Polish the web frontend, test across browsers
 * Refresh queue regularly in the web frontend
 * Write more tests
diff --git a/default.nix b/default.nix
index 9236b3f..bee5fb2 100644
--- a/default.nix
+++ b/default.nix
@@ -35,7 +35,7 @@ let
       builtins.filter nonempty (builtins.genList
         stringSplitter ((builtins.stringLength s / n) + 1));
 
-  warteraumDrv = { stdenv, redo, scrypt, scryptSalt ? null, apiTokens ? null }:
+  warteraumDrv = { stdenv, redo, scrypt, jq, scryptSalt ? null, apiTokens ? null }:
     let
       saltBytes = stringSegments 2 scryptSalt;
       saltArray =
@@ -79,6 +79,7 @@ let
 
       doCheck = true;
       checkPhase = "./test/run";
+      checkInputs = [ jq ];
 
       installPhase = ''
         install -Dm755 warteraum -t $out/bin
diff --git a/warteraum/build_config b/warteraum/build_config
index fbb6223..3881462 100644
--- a/warteraum/build_config
+++ b/warteraum/build_config
@@ -9,7 +9,6 @@ CFLAGS="$CFLAGS -std=gnu99"
 # disable some warnings not caused by us
 CFLAGS="$CFLAGS -Wno-unused-command-line-argument"   # nix musl-clang
 CFLAGS="$CFLAGS -Wno-gnu-empty-initializer"          # httpserver.h
-CFLAGS="$CFLAGS -Wno-unused-function"                # json_output
 
 # fix build issue with glibc 2.31 on NixOS
 # https://github.com/jeremycw/httpserver.h/pull/43
diff --git a/warteraum/default.o.do b/warteraum/default.o.do
index 9a75d37..f91bd8f 100644
--- a/warteraum/default.o.do
+++ b/warteraum/default.o.do
@@ -10,9 +10,8 @@ fi
 
 case "$2" in
   main)
-    redo-ifchange queue.h routing.h form.h v1_static.h
+    redo-ifchange queue.h routing.h form.h v1_static.h emitjson.h
     redo-ifchange scrypt.h tokens.h http_string.h
-    redo-ifchange ../third_party/json_output/json_output.h
     redo-ifchange ../third_party/httpserver.h/httpserver.h
     ;;
   form)
diff --git a/warteraum/emitjson.c b/warteraum/emitjson.c
new file mode 100644
index 0000000..f5ef6eb
--- /dev/null
+++ b/warteraum/emitjson.c
@@ -0,0 +1,169 @@
+#include <stdio.h>
+#include "emitjson.h"
+
+#define BUF_SIZE 512
+
+void ej_putc(struct ej_context *ctx, char c) {
+  fputc(c, ctx->out);
+  ctx->written++;
+}
+
+void ej_init(struct ej_context *ctx, FILE *out) {
+  ctx->out = out;
+  ctx->need_comma = false;
+  ctx->written = 0;
+}
+
+void ej_object(struct ej_context *ctx) {
+  if(ctx->need_comma) {
+    ej_putc(ctx, ',');
+  }
+  ej_putc(ctx, '{');
+  ctx->need_comma = false;
+}
+
+void ej_object_end(struct ej_context *ctx) {
+  ej_putc(ctx, '}');
+  ctx->need_comma = true;
+}
+
+void ej_bind(struct ej_context *ctx, const char *b, size_t len) {
+  ej_string(ctx, b, len);
+  ctx->need_comma = false;
+
+  ej_putc(ctx, ':');
+}
+
+void ej_array(struct ej_context *ctx) {
+  if(ctx->need_comma) {
+    ej_putc(ctx, ',');
+  }
+  ej_putc(ctx, '[');
+  ctx->need_comma = false;
+}
+
+void ej_array_end(struct ej_context *ctx) {
+  ej_putc(ctx, ']');
+  ctx->need_comma = true;
+}
+
+void ej_string(struct ej_context *ctx, const char *s, size_t len) {
+  if(ctx->need_comma) {
+    ej_putc(ctx, ',');
+  }
+
+  ctx->need_comma = true;
+
+  ej_putc(ctx, '"');
+
+  for(size_t i = 0; i < len; i++) {
+    char c;
+    bool escape = false;
+
+    switch(s[i]) {
+      case '\t':
+        escape = true;
+        c = 't';
+        break;
+      case '\f':
+        escape = true;
+        c = 'f';
+        break;
+      case '\r':
+        escape = true;
+        c = 'r';
+        break;
+      case '\n':
+        escape = true;
+        c = 'n';
+        break;
+      case '\\':
+      case '"':
+        escape = true;
+      default:
+        c = s[i];
+        break;
+    }
+
+    if(escape) {
+      ej_putc(ctx, '\\');
+    }
+
+    ej_putc(ctx, c);
+  }
+
+  ej_putc(ctx, '"');
+}
+
+void ej_null(struct ej_context *ctx) {
+  if(ctx->need_comma) {
+    ej_putc(ctx, ',');
+  }
+
+  ctx->need_comma = true;
+
+  size_t wsize = fwrite("null", 1, sizeof("null") - 1, ctx->out);
+  ctx->written += wsize;
+}
+
+void ej_bool(struct ej_context *ctx, bool b) {
+  if(ctx->need_comma) {
+    ej_putc(ctx, ',');
+  }
+
+  ctx->need_comma = true;
+
+  size_t len;
+  char *s;
+
+  if(b) {
+    s = "true";
+    len = sizeof("true") - 1;
+  } else {
+    s = "false";
+    len = sizeof("false") - 1;
+  }
+
+  size_t wsize = fwrite(s, 1, len, ctx->out);
+  ctx->written += wsize;
+}
+
+// generics for C 🤡
+#define EJ_INT_FUN(name, type, sign) \
+  void name(struct ej_context *ctx, type u) {                  \
+    if(ctx->need_comma) {                                      \
+      ej_putc(ctx, ',');                                       \
+    }                                                          \
+                                                               \
+    ctx->need_comma = true;                                    \
+                                                               \
+    char buf[BUF_SIZE];                                        \
+    type d;                                                    \
+    size_t len = 0;                                            \
+    char *start = buf + BUF_SIZE;                              \
+    bool add_sign = false;                                     \
+                                                               \
+    if(sign && u < 0) {                                        \
+      add_sign = true;                                         \
+      u = -u;                                                  \
+    }                                                          \
+                                                               \
+    do {                                                       \
+      start--;                                                 \
+      len++;                                                   \
+      d = u % 10;                                              \
+      u = u / 10;                                              \
+      *start = d + 48;                                         \
+    } while(u > 0 && len <= BUF_SIZE);                         \
+                                                               \
+    if(add_sign) {                                             \
+      ej_putc(ctx, '-');                                       \
+    }                                                          \
+                                                               \
+    size_t wsize = fwrite(start, sizeof(char), len, ctx->out); \
+    ctx->written += wsize;                                     \
+  }
+
+EJ_INT_FUN(ej_uint, unsigned int, false)
+
+EJ_INT_FUN(ej_int, int, true)
diff --git a/warteraum/emitjson.h b/warteraum/emitjson.h
new file mode 100644
index 0000000..0e6f673
--- /dev/null
+++ b/warteraum/emitjson.h
@@ -0,0 +1,36 @@
+#ifndef WARTERAUM_EMITJSON_H
+#define WARTERAUM_EMITJSON_H
+
+#include <stdbool.h>
+
+struct ej_context {
+  FILE *out;
+  bool need_comma;
+  size_t written;
+};
+
+void ej_init(struct ej_context *, FILE *);
+
+void ej_object(struct ej_context *);
+void ej_object_end(struct ej_context *);
+
+#define EJ_STATIC_BIND(ctx, b) \
+  ej_bind(ctx, b, sizeof(b) - 1)
+
+void ej_bind(struct ej_context *, const char *, size_t);
+//void ej_bind0(struct ej_context *, const char *, size_t);
+
+void ej_array(struct ej_context *);
+void ej_array_end(struct ej_context *);
+
+void ej_string(struct ej_context *, const char *, size_t);
+//void ej_string0(struct ej_context *, const char *);
+
+void ej_null(struct ej_context *);
+
+void ej_bool(struct ej_context *, bool);
+
+void ej_int(struct ej_context *, int);
+void ej_uint(struct ej_context *, unsigned int);
+
+#endif
diff --git a/warteraum/main.c b/warteraum/main.c
index d8dbd7b..fa06871 100644
--- a/warteraum/main.c
+++ b/warteraum/main.c
@@ -6,7 +6,7 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "../third_party/json_output/json_output.h"
+#include "emitjson.h"
 
 #include "http_string.h"
 
@@ -20,9 +20,6 @@
 
 #define LISTEN_PORT 9000
 
-#define JSO_STATIC_PROP(s, str) \
-  jso_prop_len(s, str, sizeof(str) - 1)
-
 // compare http_string against a static string,
 // but optionally allow an ;… after it.
 // i.e. application/json;charset=utf8 matches with
@@ -32,6 +29,9 @@
     strncmp(a.buf, s, sizeof(s) - 1) == 0 &&                 \
     ((size_t) a.len < sizeof(s) || a.buf[sizeof(s) - 1] == ';'))
 
+#define INTERNAL_ERROR_STATIC \
+  "{\"error\":\"internal error while building error response\"}"
+
 // Global state
 
 static struct queue flip_queue;
@@ -106,21 +106,37 @@ void response_error(enum warteraum_result e, bool legacy_response, http_request_
     http_response_body(response, QUEUE_ADD_V1_FAILURE, sizeof(QUEUE_ADD_V1_FAILURE) - 1);
     http_respond(request, response);
   } else {
-    jso_stream s;
-
-    jso_init_growable(&s);
-
-    jso_object(&s);
-    JSO_STATIC_PROP(&s, "error");
-    jso_string_len(&s, errors[e].buf, (size_t) errors[e].len);
-    jso_end_object(&s);
+    size_t buf_size = 0;
+    char *buf = NULL;
+    FILE *out = open_memstream(&buf, &buf_size);
+    struct ej_context ctx;
+    bool static_buf = false;
+
+    ej_init(&ctx, out);
+
+    if(out == NULL) {
+      buf = INTERNAL_ERROR_STATIC;
+      buf_size = sizeof(INTERNAL_ERROR_STATIC) - 1;
+
+      static_buf = true;
+      e = WARTERAUM_INTERNAL_ERROR;
+    } else {
+      ej_object(&ctx);
+      EJ_STATIC_BIND(&ctx, "error");
+      ej_string(&ctx, errors[e].buf, (size_t) errors[e].len);
+      ej_object_end(&ctx);
+
+      fclose(out);
+    }
 
     http_response_status(response, codes[e]);
     http_response_header(response, "Content-Type", "application/json");
-    http_response_body(response, s.data, (int) s.pos);
+    http_response_body(response, buf, static_buf ? buf_size : (int) ctx.written);
     http_respond(request, response);
 
-    jso_close(&s);
+    if(!static_buf) {
+      free(buf);
+    }
   }
 }
 
@@ -129,38 +145,49 @@ enum warteraum_result response_queue(enum warteraum_version v, http_request_t *r
   (void) v; // surpress warning for now
 
   unsigned int queue_length = 0;
-  jso_stream s;
-  jso_init_growable(&s);
 
-  jso_object(&s);
-  JSO_STATIC_PROP(&s, "queue");
-  jso_array(&s);
+  struct ej_context ctx;
+  size_t buf_size = 0;
+  char *buf = NULL;
+  FILE *out = open_memstream(&buf, &buf_size);
+
+  if(out == NULL) {
+    return WARTERAUM_INTERNAL_ERROR;
+  }
+
+  ej_init(&ctx, out);
+
+  ej_object(&ctx);
+  EJ_STATIC_BIND(&ctx, "queue");
+  ej_array(&ctx);
 
   queue_foreach(flip_queue, elem) {
     queue_length++;
-    jso_object(&s);
+    ej_object(&ctx);
 
-    JSO_STATIC_PROP(&s, "id");
-    jso_uint(&s, elem->id);
+    EJ_STATIC_BIND(&ctx, "id");
+    ej_uint(&ctx, elem->id);
 
-    JSO_STATIC_PROP(&s, "text");
-    jso_string_len(&s, elem->string, elem->string_size);
+    EJ_STATIC_BIND(&ctx, "text");
+    ej_string(&ctx, elem->string, elem->string_size);
 
-    jso_end_object(&s);
+    ej_object_end(&ctx);
   }
 
-  jso_end_array(&s);
+  ej_array_end(&ctx);
+
+  EJ_STATIC_BIND(&ctx, "length");
+  ej_uint(&ctx, queue_length);
+  ej_object_end(&ctx);
 
-  JSO_STATIC_PROP(&s, "length");
-  jso_uint(&s, queue_length);
-  jso_end_object(&s);
+  fclose(out);
 
   http_response_status(response, 200);
   http_response_header(response, "Content-Type", "application/json");
-  http_response_body(response, s.data, (int) s.pos);
+  http_response_body(response, buf, (int) ctx.written);
   http_respond(request, response);
 
-  jso_close(&s);
+  free(buf);
 
   return WARTERAUM_OK;
 }
@@ -219,22 +246,32 @@ enum warteraum_result response_queue_add(enum warteraum_version version, http_re
     http_response_body(response, QUEUE_ADD_V1_SUCCESS, sizeof(QUEUE_ADD_V1_SUCCESS) - 1);
     http_respond(request, response);
   } else {
-    jso_stream s;
-    jso_init_growable(&s);
+    struct ej_context ctx;
+    char *buf = NULL;
+    size_t buf_size = 0;
+    FILE *out = open_memstream(&buf, &buf_size);
+
+    if(out == NULL) {
+      return WARTERAUM_INTERNAL_ERROR;
+    }
+
+    ej_init(&ctx, out);
+
+    ej_object(&ctx);
+    EJ_STATIC_BIND(&ctx, "id");
+    ej_uint(&ctx, flip_queue.last->id);
+    EJ_STATIC_BIND(&ctx, "text");
+    ej_string(&ctx, flip_queue.last->string, flip_queue.last->string_size);
+    ej_object_end(&ctx);
 
-    jso_object(&s);
-    JSO_STATIC_PROP(&s, "id");
-    jso_uint(&s, flip_queue.last->id);
-    JSO_STATIC_PROP(&s, "text");
-    jso_string_len(&s, flip_queue.last->string, flip_queue.last->string_size);
-    jso_end_object(&s);
+    fclose(out);
 
     http_response_status(response, 200);
     http_response_header(response, "Content-Type", "application/json");
-    http_response_body(response, s.data, (int) s.pos);
+    http_response_body(response, buf, (int) ctx.written);
     http_respond(request, response);
 
-    jso_close(&s);
+    free(buf);
   }
 
   return WARTERAUM_OK;
diff --git a/warteraum/test/all.do b/warteraum/test/all.do
index 6bd544d..9fac8ba 100644
--- a/warteraum/test/all.do
+++ b/warteraum/test/all.do
@@ -1 +1 @@
-redo-ifchange test_queue.exe test_form.exe test_routing.exe
+redo-ifchange test_queue.exe test_form.exe test_routing.exe test_emitjson.exe
diff --git a/warteraum/test/default.exe.do b/warteraum/test/default.exe.do
index a1e90d3..8e32f6f 100644
--- a/warteraum/test/default.exe.do
+++ b/warteraum/test/default.exe.do
@@ -15,6 +15,9 @@ case "$2" in
     OBJS="../routing.o"
     redo-ifchange ../http_string.h
     ;;
+  test_emitjson)
+    OBJS="../emitjson.o"
+    ;;
 esac
 
 redo-ifchange $OBJS
diff --git a/warteraum/test/run b/warteraum/test/run
index e78a487..ef85225 100755
--- a/warteraum/test/run
+++ b/warteraum/test/run
@@ -5,10 +5,23 @@ cd "$(dirname "$0")"
 
 redo all
 
-TESTS="queue form routing"
+TESTS="queue form routing emitjson"
 
 for t in $TESTS; do
 
   echo -e "\n# $t tests\n"
   "./test_$t.exe"
 done
+
+echo -e "\n# test emitjson validity\n"
+
+if command -v jq > /dev/null; then
+  if ./test_emitjson.exe -o | jq; then
+    echo -e "\njson validity\tok"
+  else
+    echo -e "\njson validity\tFAIL"
+    exit 1
+  fi
+else
+  echo -e "json validity\tskipped (missing jq)"
+fi
diff --git a/warteraum/test/test_emitjson.c b/warteraum/test/test_emitjson.c
new file mode 100644
index 0000000..1f51adc
--- /dev/null
+++ b/warteraum/test/test_emitjson.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <string.h>
+#include "../emitjson.h"
+#include "test.h"
+
+int main(int argc, char **argv) {
+  bool output_json = false;
+
+  if(argc == 2 && strcmp(argv[1], "-o") == 0) {
+    output_json = true;
+  }
+
+  struct ej_context ctx;
+  size_t buf_size;
+  char *buf = NULL;
+  FILE *out = open_memstream(&buf, &buf_size);
+
+  ej_init(&ctx, out);
+
+  ej_object(&ctx);
+  EJ_STATIC_BIND(&ctx, "array");
+  ej_array(&ctx);
+  ej_uint(&ctx, 25500001);
+  ej_int(&ctx, -12000);
+  ej_int(&ctx, 10000);
+  ej_string(&ctx, "foo\tbar\nbaz\\", 12);
+  ej_null(&ctx);
+  ej_object(&ctx);
+  ej_bind(&ctx, "hello", 5);
+  ej_string(&ctx, "world", 5);
+  ej_bind(&ctx, "foo\\", 4);
+  ej_uint(&ctx, 42);
+  ej_object_end(&ctx);
+  ej_bool(&ctx, true);
+  ej_bool(&ctx, false);
+  ej_array_end(&ctx);
+  EJ_STATIC_BIND(&ctx, "number");
+  ej_uint(&ctx, 1312);
+  ej_object_end(&ctx);
+
+  fclose(out);
+
+  bool len_correct = buf_size == ctx.written;
+
+  if(output_json) {
+    fwrite(buf, 1, ctx.written, stdout);
+  } else {
+    test_case("Actually written size matches computed size", len_correct);
+  }
+
+  free(buf);
+
+  return !test_result;
+}
diff --git a/warteraum/warteraum.do b/warteraum/warteraum.do
index 467872a..1e6bda2 100644
--- a/warteraum/warteraum.do
+++ b/warteraum/warteraum.do
@@ -1,6 +1,6 @@
 source ./build_config
 redo-ifchange ./build_config
-OBJS="../third_party/json_output/json_output.o queue.o routing.o form.o main.o"
+OBJS="emitjson.o queue.o routing.o form.o main.o"
 DEPS="$OBJS ../third_party/httpserver.h/httpserver.h"
 redo-ifchange $DEPS