2814ce28c7ec02571f6dce4e72439719da953ff3 hiram Tue Apr 16 10:09:16 2019 -0700 using needMem() instead of stack memory to expand limit of JSON strings to 500,000,000 output refs #18869 diff --git src/lib/jsonWrite.c src/lib/jsonWrite.c index 8ed3e02..5be6dc8 100644 --- src/lib/jsonWrite.c +++ src/lib/jsonWrite.c @@ -1,259 +1,260 @@ /* jsonWrite - Helper routines for writing out JSON. */ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "hash.h" #include "dystring.h" #include "sqlNum.h" #include "jsonParse.h" #include "jsonWrite.h" // Separator between elements; set this to "\n" to see elements on separate lines. // Newlines are fine in Javascript, e.g. in an embedded <script>. // However, unescaped \n is illegal in JSON and web browsers may reject it. // Web browser plugins can pretty-print JSON nicely. struct jsonWrite *jsonWriteNew() /* Return new empty jsonWrite struct. */ { struct jsonWrite *jw; AllocVar(jw); jw->dy = dyStringNew(0); jw->sep = ' '; return jw; } void jsonWriteFree(struct jsonWrite **pJw) /* Free up a jsonWrite object. */ { struct jsonWrite *jw = *pJw; if (jw != NULL) { dyStringFree(&jw->dy); freez(pJw); } } static void jsonWritePushObjStack(struct jsonWrite *jw, bool isNotEmpty, bool isObject) /* Push a new object or list on stack */ { int stackIx = jw->stackIx + 1; if (stackIx >= ArraySize(jw->objStack)) errAbort("Stack overflow in jsonWritePush"); jw->objStack[stackIx].isNotEmpty = isNotEmpty; jw->objStack[stackIx].isObject = isObject; jw->stackIx = stackIx; } static void jsonWritePopObjStack(struct jsonWrite *jw, bool isObject) /* pop object stack and just discard val. */ { boolean topIsObject = jw->objStack[jw->stackIx].isObject; if (topIsObject != isObject) errAbort("jsonWrite: expected to close %s but was told to close %s", topIsObject ? "object" : "list", isObject ? "object" : "list"); int stackIx = jw->stackIx - 1; if (stackIx < 0) errAbort("Stack underflow in jsonWritePopObjStack"); jw->stackIx = stackIx; } INLINE void jsonWriteMaybeComma(struct jsonWrite *jw) /* If this is not the first item added to an object or list, write a comma. */ { if (jw->objStack[jw->stackIx].isNotEmpty) { dyStringAppendC(jw->dy, ','); dyStringAppendC(jw->dy, jw->sep); } else jw->objStack[jw->stackIx].isNotEmpty = TRUE; } void jsonWriteTag(struct jsonWrite *jw, char *var) /* Print out preceding comma if necessary, and if var is non-NULL, quoted tag followed by colon. */ { jsonWriteMaybeComma(jw); if (var != NULL) dyStringPrintf(jw->dy, "\"%s\": ", var); } void jsonWriteString(struct jsonWrite *jw, char *var, char *string) /* Print out "var": "val" -- or rather, jsonStringEscape(val). * If var is NULL, print val only. If string is NULL, "var": null . */ { jsonWriteTag(jw, var); if (string) { size_t encSize = jsonStringEscapeSize(string); - char encoded[encSize]; + char *encoded = needMem(encSize); /* needMem limit is 500,000,000 */ jsonStringEscapeBuf(string, encoded, encSize); dyStringPrintf(jw->dy, "\"%s\"", encoded); + freeMem(encoded); } else dyStringAppend(jw->dy, "null"); } void jsonWriteDateFromUnix(struct jsonWrite *jw, char *var, long long unixTimeVal) /* Add "var": YYYY-MM-DDT-HH:MM:SSZ given a Unix time stamp. Var may be NULL. */ { struct dyString *dy = jw->dy; time_t timeStamp = unixTimeVal; struct tm tm; gmtime_r(&timeStamp, &tm); jsonWriteTag(jw, var); dyStringPrintf(dy, "\"%d:%02d:%02dT%02d:%02d:%02dZ\"", 1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); } void jsonWriteNumber(struct jsonWrite *jw, char *var, long long val) /* print out "var": val as number. Var may be NULL. */ { struct dyString *dy = jw->dy; jsonWriteTag(jw, var); dyStringPrintf(dy, "%lld", val); } void jsonWriteDouble(struct jsonWrite *jw, char *var, double val) /* print out "var": val as number. Var may be NULL. */ { struct dyString *dy = jw->dy; jsonWriteTag(jw, var); dyStringPrintf(dy, "%g", val); } void jsonWriteLink(struct jsonWrite *jw, char *var, char *objRoot, char *name) /* Print out the jsony type link to another object. objRoot will start and end with a '/' * and may have additional slashes in this usage. Var may be NULL. */ { struct dyString *dy = jw->dy; jsonWriteTag(jw, var); dyStringPrintf(dy, "\"%s%s\"", objRoot, name); } void jsonWriteLinkNum(struct jsonWrite *jw, char *var, char *objRoot, long long id) /* Print out the jsony type link to another object with a numerical id. objRoot will start * and end with a '/' and may have additional slashes in this usage. Var may be NULL */ { struct dyString *dy = jw->dy; jsonWriteTag(jw, var); dyStringPrintf(dy, "\"%s%lld\"", objRoot, id); } void jsonWriteListStart(struct jsonWrite *jw, char *var) /* Start an array in JSON. Var may be NULL */ { struct dyString *dy = jw->dy; jsonWriteTag(jw, var); dyStringAppendC(dy, '['); dyStringAppendC(dy, jw->sep); jsonWritePushObjStack(jw, FALSE, FALSE); } void jsonWriteListEnd(struct jsonWrite *jw) /* End an array in JSON */ { struct dyString *dy = jw->dy; dyStringAppendC(dy, ']'); dyStringAppendC(dy, jw->sep); jsonWritePopObjStack(jw, FALSE); } void jsonWriteObjectStart(struct jsonWrite *jw, char *var) /* Print start of object, preceded by tag if var is non-NULL. */ { jsonWriteTag(jw, var); struct dyString *dy = jw->dy; dyStringAppendC(dy, '{'); dyStringAppendC(dy, jw->sep); jsonWritePushObjStack(jw, FALSE, TRUE); } void jsonWriteObjectEnd(struct jsonWrite *jw) /* End object in JSON */ { struct dyString *dy = jw->dy; dyStringAppendC(dy, '}'); dyStringAppendC(dy, jw->sep); jsonWritePopObjStack(jw, TRUE); } void jsonWriteStringf(struct jsonWrite *jw, char *var, char *format, ...) /* Write "var": "val" where val is jsonStringEscape'd formatted string. */ { // In order to use jsonStringEscape(), we need to use a temporary dyString // instead of jw->dy in the dyStringVaPrintf, and pass that to jsonWriteString. struct dyString *tmpDy = dyStringNew(0); va_list args; va_start(args, format); dyStringVaPrintf(tmpDy, format, args); va_end(args); jsonWriteString(jw, var, tmpDy->string); dyStringFree(&tmpDy); } void jsonWriteBoolean(struct jsonWrite *jw, char *var, boolean val) /* Write out "var": true or "var": false depending on val (no quotes around true/false). */ { jsonWriteTag(jw, var); dyStringAppend(jw->dy, val ? "true" : "false"); } void jsonWriteValueLabelList(struct jsonWrite *jw, char *var, struct slPair *pairList) /* Print out a named list of {"value": "<pair->name>", "label": "<pair->val>"} objects. */ { jsonWriteListStart(jw, var); struct slPair *pair; for (pair = pairList; pair != NULL; pair = pair->next) { jsonWriteObjectStart(jw, NULL); jsonWriteString(jw, "value", pair->name); jsonWriteString(jw, "label", (char *)(pair->val)); jsonWriteObjectEnd(jw); } jsonWriteListEnd(jw); } void jsonWriteSlNameList(struct jsonWrite *jw, char *var, struct slName *slnList) /* Print out a named list of strings from slnList. */ { jsonWriteListStart(jw, var); struct slName *sln; for (sln = slnList; sln != NULL; sln = sln->next) jsonWriteString(jw, NULL, sln->name); jsonWriteListEnd(jw); } void jsonWriteAppend(struct jsonWrite *jwA, char *var, struct jsonWrite *jwB) /* Append jwB's contents to jwA's. If jwB is non-NULL, it must be fully closed (no unclosed * list or object). If var is non-NULL, write it out as a tag before appending. * If both var and jwB are NULL, leave jwA unchanged. */ { if (jwB && jwB->stackIx) errAbort("jsonWriteAppend: second argument must be fully closed but its stackIx is %d not 0", jwB->stackIx); jsonWriteTag(jwA, var); if (jwB) dyStringAppendN(jwA->dy, jwB->dy->string, jwB->dy->stringSize); else if (var) dyStringAppend(jwA->dy, "null"); } int jsonWritePopToLevel(struct jsonWrite *jw, uint level) /* Close out the objects and lists that are deeper than level, so we end up at level ready to * add new items. Return the level that we end up with, which may not be the same as level, * if level is deeper than the current stack. */ { int i; for (i = jw->stackIx; i > level; i--) { if (jw->objStack[i].isObject) jsonWriteObjectEnd(jw); else jsonWriteListEnd(jw); } return jw->stackIx; }