#include "json.h" #include #include #include #include #include #include const char* jsonErrorString(jsonResult_t res) { switch (res) { case jsonSuccess: return "jsonSuccess"; case jsonFileError: return "jsonFileError"; case jsonUnknownStateError: return "jsonUnknownStateError"; case jsonEmptyStateError: return "jsonEmptyStateError"; case jsonExpectedNonNoneStateError: return "jsonExpectedNonNoneStateError"; case jsonMemoryError: return "jsonMemoryError"; case jsonStringOverflowError: return "jsonStringOverflowError"; case jsonStringBadChar: return "jsonStringBadChar"; case jsonLockError: return "jsonLockError"; default: return "unknown json error"; } } // We use these statics to mantain a stack of states where we are writing. typedef struct jsonFileOutput { jsonState_t* states; size_t state_cap; // Allocated stack capacity size_t state_n; // # of items in the stack. FILE* fp; pthread_mutex_t mutex; } jsonFileOutput; jsonResult_t jsonInitFileOutput(jsonFileOutput** jfo, const char* outfile) { jsonFileOutput* new_jfo = (jsonFileOutput*)malloc(sizeof(jsonFileOutput)); if (new_jfo == NULL) { return jsonMemoryError; } if (pthread_mutex_init(&new_jfo->mutex, NULL) != 0) { free(new_jfo); *jfo = 0; return jsonLockError; } new_jfo->states = NULL; new_jfo->state_cap = 0; new_jfo->state_n = 0; new_jfo->fp = fopen(outfile, "w"); if (new_jfo->fp == NULL) { free(new_jfo); *jfo = 0; return jsonFileError; } *jfo = new_jfo; return jsonSuccess; } jsonResult_t jsonNewline(jsonFileOutput* jfo) { fprintf(jfo->fp, "\n"); return jsonSuccess; } jsonResult_t jsonFlushOutput(jsonFileOutput* jfo) { fflush(jfo->fp); return jsonSuccess; } jsonResult_t jsonLockOutput(jsonFileOutput* jfo) { if (pthread_mutex_lock(&jfo->mutex) != 0) { return jsonLockError; } return jsonSuccess; } jsonResult_t jsonUnlockOutput(jsonFileOutput* jfo) { if (pthread_mutex_unlock(&jfo->mutex) != 0) { return jsonLockError; } return jsonSuccess; } jsonResult_t jsonFinalizeFileOutput(jsonFileOutput* jfo) { // Really should probably complain if we aren't in a valid state if (pthread_mutex_destroy(&jfo->mutex) != 0) { free(jfo); return jsonLockError; } if (jfo->states != NULL) { free(jfo->states); } jfo->states = NULL; jfo->state_cap = 0; jfo->state_n = 0; if (jfo->fp) { fclose(jfo->fp); jfo->fp = 0; } free(jfo); return jsonSuccess; } static int utf8copy(unsigned char* out, int out_lim, const unsigned char* in) { int copy_len; if ((in[0] & 0xE0) == 0xC0) { // 2-byte sequence if ((in[1] & 0xC0) != 0x80 || out_lim < 2) { return 0; } copy_len = 2; } else if ((in[0] & 0xF0) == 0xE0) { // 3-byte sequence if ((in[1] & 0xC0) != 0x80 || (in[2] & 0xC0) != 0x80 || out_lim < 3) { return 0; } copy_len = 3; } else if ((in[0] & 0xF8) == 0xF0) { // 4-byte sequence if ((in[1] & 0xC0) != 0x80 || (in[2] & 0xC0) != 0x80 || (in[3] & 0xC0) != 0x80 || out_lim < 4) { return 0; } copy_len = 4; } else { // Invalid start byte return 0; } for (int i = 0; i < copy_len; ++i) { out[i] = in[i]; } return copy_len; } // This tries to sanitize/quote a string from 'in' into 'out', // assuming 'out' has length 'lim'. We mainly quote ",/,\,\t,\n, and // bail if we encounter non-printable stuff or non-ASCII stuff. // 'in' should be null-terminated, of course. // // We return false if we were not able to copy all of 'in', either for // length reasons or for unhandled characters. static jsonResult_t sanitizeJson(unsigned char out[], int lim, const unsigned char* in) { int c = 0; while (*in) { if (c + 1 >= lim) { out[c] = 0; return jsonStringOverflowError; } switch (*in) { case '"': case '\\': case '/': case '\t': case '\n': if (c + 2 > lim) { out[c] = 0; return jsonStringOverflowError; } out[c++] = '\\'; if (*in == '\n') { out[c++] = 'n'; } else if (*in == '\t') { out[c++] = 't'; } else { out[c++] = *in; } ++in; break; default: if (*in <= 0x1F) { out[c] = 0; return jsonStringBadChar; } else if (*in <= 0x7F) { out[c++] = *in; ++in; } else { const int utf8len = utf8copy(out + c, lim - c - 1, in); if (utf8len == 0) { out[c] = 0; return jsonStringBadChar; } c += utf8len; in += utf8len; } break; } } out[c] = 0; return jsonSuccess; } static size_t max(size_t a, size_t b) { if (a < b) { return b; } return a; } // Push state onto the state stack. Reallocate for extra storage if needed. // Because JSON_NONE is a pseudo-state, don't allow it to be pushed. static jsonResult_t jsonPushState(jsonFileOutput* jfo, jsonState_t state) { if (state == JSON_NONE) { return jsonExpectedNonNoneStateError; } if (jfo->state_cap <= (jfo->state_n + 1)) { jfo->state_cap = max((size_t)16, jfo->state_cap * 2); jfo->states = (jsonState_t*)realloc(jfo->states, sizeof(jsonState_t) * jfo->state_cap); if (jfo->states == 0) { return jsonMemoryError; } } jfo->states[jfo->state_n++] = state; return jsonSuccess; } // Return the current state at the top of the stack static jsonState_t jsonCurrState(const jsonFileOutput* jfo) { if (jfo->state_n == 0) { return JSON_NONE; } return jfo->states[jfo->state_n - 1]; } // Replace the stack with state (equivalent to a pop & push if stack is not empty) static jsonResult_t jsonReplaceState(jsonFileOutput* jfo, jsonState_t state) { if (state == JSON_NONE) { return jsonExpectedNonNoneStateError; } if (jfo->state_n == 0) { return jsonEmptyStateError; } jfo->states[jfo->state_n - 1] = state; return jsonSuccess; } // Pop the top state off the stack, or return that the state is empty static jsonState_t jsonPopState(jsonFileOutput* jfo) { if (jfo->state_n == 0) { return JSON_NONE; } return jfo->states[--jfo->state_n]; } // Emit a key and separator. Santize the key. // This is only acceptable if the top state is an object // Emit a ',' separator of we aren't the first item. jsonResult_t jsonKey(jsonFileOutput* jfo, const char* name) { switch (jsonCurrState(jfo)) { case JSON_OBJECT_EMPTY: jsonReplaceState(jfo, JSON_OBJECT_SOME); break; case JSON_OBJECT_SOME: fprintf(jfo->fp, ","); break; default: return jsonUnknownStateError; } unsigned char tmp[2048]; const jsonResult_t res = sanitizeJson(tmp, sizeof(tmp), (const unsigned char*)name); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "\"%s\":", tmp); jsonPushState(jfo, JSON_KEY); return jsonSuccess; } // Helper function for inserting values. // Only acceptable after keys, top-level, or in lists. // Emit preceeding ',' if in a list and not first item. static jsonResult_t jsonValHelper(jsonFileOutput* jfo) { switch (jsonCurrState(jfo)) { case JSON_LIST_EMPTY: jsonReplaceState(jfo, JSON_LIST_SOME); break; case JSON_LIST_SOME: fprintf(jfo->fp, ","); break; case JSON_KEY: jsonPopState(jfo); break; case JSON_NONE: break; default: return jsonUnknownStateError; } return jsonSuccess; } // Start an object jsonResult_t jsonStartObject(jsonFileOutput* jfo) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "{"); return jsonPushState(jfo, JSON_OBJECT_EMPTY); } // Close an object jsonResult_t jsonFinishObject(jsonFileOutput* jfo) { switch (jsonPopState(jfo)) { case JSON_OBJECT_EMPTY: case JSON_OBJECT_SOME: break; default: return jsonUnknownStateError; } fprintf(jfo->fp, "}"); return jsonSuccess; } // Start a list jsonResult_t jsonStartList(jsonFileOutput* jfo) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "["); return jsonPushState(jfo, JSON_LIST_EMPTY); } // Close a list jsonResult_t jsonFinishList(jsonFileOutput* jfo) { switch (jsonPopState(jfo)) { case JSON_LIST_EMPTY: case JSON_LIST_SOME: break; default: return jsonUnknownStateError; } fprintf(jfo->fp, "]"); return jsonSuccess; } // Write a null value jsonResult_t jsonNull(jsonFileOutput* jfo) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "null"); return jsonSuccess; } // Write a (sanititzed) string jsonResult_t jsonStr(jsonFileOutput* jfo, const char* str) { if (str == NULL) { jsonNull(jfo); return jsonSuccess; } const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } unsigned char tmp[2048]; const jsonResult_t san_res = sanitizeJson(tmp, sizeof(tmp), (const unsigned char*)str); if (san_res != jsonSuccess) { return san_res; } fprintf(jfo->fp, "\"%s\"", tmp); return jsonSuccess; } // Write a bool as "true" or "false" strings. jsonResult_t jsonBool(jsonFileOutput* jfo, bool val) { return jsonStr(jfo, val ? "true" : "false"); } // Write an integer value jsonResult_t jsonInt(jsonFileOutput* jfo, const int val) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "%d", val); return jsonSuccess; } // Write an integer value jsonResult_t jsonUint32(jsonFileOutput* jfo, const uint32_t val) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "%u", val); return jsonSuccess; } // Write an integer value jsonResult_t jsonUint64(jsonFileOutput* jfo, const uint64_t val) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "%lu", val); return jsonSuccess; } // Write a size_t value jsonResult_t jsonSize_t(jsonFileOutput* jfo, const size_t val) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } fprintf(jfo->fp, "%zu", val); return jsonSuccess; } // Write a double value jsonResult_t jsonDouble(jsonFileOutput* jfo, const double val) { const jsonResult_t res = jsonValHelper(jfo); if (res != jsonSuccess) { return res; } if (val != val) { fprintf(jfo->fp, "\"nan\""); } else { fprintf(jfo->fp, "%lf", val); } return jsonSuccess; } #ifdef DO_JSON_TEST // compile with // gcc json.cc -Iinclude/ -DDO_JSON_TEST -o json_test // run with: // ./json_test // if something fails, it will print out the error // if it all works, print out "output matches reference" #define JSONCHECK(expr) \ do { \ const jsonResult_t res = (expr); \ if (res != jsonSuccess) { \ fprintf(stderr, "jsonError: %s\n", jsonErrorString(res)); \ exit(1); \ } \ } while (0) int main() { const char refstr[] = "{\"number\":123,\"utfstring\":\"∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ " "¬β = ¬(¬α ∨ β),\",\"list\":[\"true\",null,9423812381231,3123111,0.694234]}"; jsonFileOutput* jfo; JSONCHECK(jsonInitFileOutput(&jfo, "test.json")); JSONCHECK(jsonStartObject(jfo)); JSONCHECK(jsonKey(jfo, "number")); JSONCHECK(jsonInt(jfo, 123)); JSONCHECK(jsonKey(jfo, "utfstring")); JSONCHECK( jsonStr(jfo, "∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),")); JSONCHECK(jsonKey(jfo, "list")); JSONCHECK(jsonStartList(jfo)); JSONCHECK(jsonBool(jfo, true)); JSONCHECK(jsonNull(jfo)); JSONCHECK(jsonUint64(jfo, 9423812381231ULL)); JSONCHECK(jsonSize_t(jfo, 3123111)); JSONCHECK(jsonDouble(jfo, 0.69423413)); JSONCHECK(jsonFinishList(jfo)); JSONCHECK(jsonFinishObject(jfo)); JSONCHECK(jsonFinalizeFileOutput(jfo)); FILE* fp = fopen("test.json", "r"); const size_t reflen = sizeof(refstr) / sizeof(char); char buffer[reflen]; fread(buffer, sizeof(char), reflen, fp); fclose(fp); if (memcmp(buffer, refstr, reflen) == 0) { printf("output matches reference\n"); } else { printf("output %s\nreference %s\n", buffer, refstr); return 1; } return 0; } #endif