/*============================================================================= json.c =============================================================================== Bo Lorentsen (bl@lue.dk) had the idea to do XML-RPC values in JSON and wrote the original version of this code in February and March 2010. Bryan Henderson restructured the code and improved diagnostic information (made it tell you where the JSON is screwed up) before its first release in XML-RPC for C and C++ in Release 1.22. JSON: RFC-4627 =============================================================================*/ #include "xmlrpc_config.h" #include #include #include #include #include #include #include #include "xmlrpc-c/json.h" #include "xmlrpc-c/util.h" #include "xmlrpc-c/base_int.h" #include "xmlrpc-c/string_int.h" #include "xmlrpc-c/string_number.h" /*============================================================================= Tokenizer for the json parser =============================================================================*/ enum ttype { typeNone, typeOpenBrace, typeCloseBrace, typeOpenBracket, typeCloseBracket, typeColon, typeComma, typeString, typeInteger, typeFloat, typeNull, typeUndefined, typeTrue, typeFalse, typeEof, } ; static const char * tokTypeName(enum ttype const type) { switch (type) { case typeNone: return "None"; case typeOpenBrace: return "Open brace"; case typeCloseBrace: return "Close brace"; case typeOpenBracket: return "Open bracket"; case typeCloseBracket: return "Close bracket"; case typeColon: return "Colon"; case typeComma: return "Comma"; case typeString: return "String"; case typeInteger: return "Integer"; case typeFloat: return "Float"; case typeNull: return "Null"; case typeUndefined: return "Undefined"; case typeTrue: return "True"; case typeFalse: return "False"; case typeEof: return "Eof"; default: return "???"; } } typedef struct { const char * original; size_t size; const char * begin; const char * end; enum ttype type; } Tokenizer; static void initializeTokenizer(Tokenizer * const tokP, const char * const str) { tokP->original = str; tokP->end = str; /* end of the "previous" token */ tokP->type = typeNone; } static void terminateTokenizer(Tokenizer * const tokP ATTR_UNUSED ) { } struct docPosition { /* A position in the document, as meaningful to the user */ unsigned int lineNum; /* First line is 1 */ unsigned int colNum; /* First column is 1 */ }; static struct docPosition currentDocumentPosition(Tokenizer * const tokP) { /*---------------------------------------------------------------------------- Return the document position (line & column) of the start of the current token -----------------------------------------------------------------------------*/ struct docPosition retval; unsigned int curLine; unsigned int curCol; const char * cursor; curLine = 0; curCol = 0; for (cursor = tokP->original; cursor < tokP->begin; ++cursor) { ++curCol; if (*cursor == '\n') { ++curLine; curCol = 0; } } retval.lineNum = curLine + 1; retval.colNum = curCol + 1; return retval; } static void setParseErr(xmlrpc_env * const envP, Tokenizer * const tokP, const char * const format, ...) { struct docPosition const pos = currentDocumentPosition(tokP); va_list args; const char * msg; XMLRPC_ASSERT(envP != NULL); XMLRPC_ASSERT(format != NULL); va_start(args, format); xmlrpc_vasprintf(&msg, format, args); xmlrpc_env_set_fault_formatted( envP, XMLRPC_PARSE_ERROR, "JSON parse error at Line %u, Column %u: %s", pos.lineNum, pos.colNum, msg); xmlrpc_strfree(msg); va_end(args); } static void finishStringToken(xmlrpc_env * const envP, Tokenizer * const tokP) { ++tokP->end; while (*tokP->end != '"' && *tokP->end != '\0' && !envP->fault_occurred) { if (*tokP->end == '\\') { ++tokP->end; switch (*tokP->end) { case '"': case '\\': case '/': case 'b': case 'f': case 'n': case 'r': case 't': ++tokP->end; break; case 'u': { const char * cur; ++tokP->end; cur = tokP->end; while (isxdigit(*cur) && cur - tokP->end < 4) ++cur; if (cur - tokP->end < 4) setParseErr(envP, tokP, "hex unicode must contain 4 digits. " "There are only %u here", cur - tokP->end); else tokP->end = cur; } break; default: setParseErr(envP, tokP, "unknown escape character " "after backslash: '%c'", *tokP->end); } } else ++tokP->end; } if (!envP->fault_occurred) { if (*tokP->end == '\0') setParseErr(envP, tokP, "JSON document ends in the middle " "of a backslash escape sequence"); else { ++tokP->end; tokP->size = (tokP->end - tokP->begin) - 1; } } } static bool isInteger(const char * const token, unsigned int const tokSize) { if (tokSize < 1) return false; else { unsigned int i; i = 0; if (token[0] == '-') ++i; while (i < tokSize) { if (!isdigit(token[i])) return false; ++i; } return true; } } static bool isFloat(const char * const token, unsigned int const tokSize) { unsigned int i; bool seenPeriod; bool seenDigit; seenPeriod = false; seenDigit = false; i = 0; if (tokSize >= 1 && token[0] == '-') ++i; while (i < tokSize) { char const c = token[i]; if (c == 'e') return isInteger(&token[i], tokSize - i); else if (c == '.') { if (seenPeriod) { /* It's a second period */ return false; } else { seenPeriod = true; } } else if (isdigit(c)) seenDigit = true; else return false; ++i; } if (seenDigit) return true; else return false; } static bool isWordChar(char const candidate) { /*---------------------------------------------------------------------------- Return true iff 'candidate' is a character that can be in a "word" token. A word token is a multi-character token that is either a JSON keyword or a number. -----------------------------------------------------------------------------*/ return (isalnum(candidate) || candidate == '.' || candidate == '-'); } static void finishAlphanumericWordToken(Tokenizer * const tokP) { ++tokP->end; while (isWordChar(*tokP->end)) ++tokP->end; tokP->size = tokP->end - tokP->begin; } static void finishDelimiterToken(Tokenizer * const tokP) { ++tokP->end; tokP->size = tokP->end - tokP->begin; } static bool atComment(Tokenizer * const tokP) { return (*tokP->begin == '/' && *(tokP->begin + 1) == '/'); } static void advancePastWhiteSpace(Tokenizer * const tokP) { while (isspace(*tokP->begin)) ++tokP->begin; } static void advancePastComments(Tokenizer * const tokP) { /*---------------------------------------------------------------------------- Advance the pointer over any comments. -----------------------------------------------------------------------------*/ while (atComment(tokP)) { /* A comment ends at a newline or end of document */ while (*tokP->begin != '\n' && *tokP->begin != '\0') ++tokP->begin; } } static void advanceToNextToken(Tokenizer * const tokP) { /*---------------------------------------------------------------------------- Advance the pointer over any white space and comments to the next token, or end of document, whichever comes first. -----------------------------------------------------------------------------*/ while (*tokP->begin != '\0' && (isspace(*tokP->begin) || atComment(tokP))) { advancePastWhiteSpace(tokP); advancePastComments(tokP); } } static void getToken(xmlrpc_env * const envP, Tokenizer * const tokP) { /* The token starts where the last one left off */ tokP->begin = tokP->end; advanceToNextToken(tokP); if (*tokP->begin == '\0') { /* End of document */ tokP->end = tokP->begin; tokP->type = typeEof; tokP->size = tokP->end - tokP->begin; } else { tokP->end = tokP->begin; /* initial value */ if (*tokP->begin == '{') { finishDelimiterToken(tokP); tokP->type = typeOpenBrace; } else if (*tokP->begin == '}') { finishDelimiterToken(tokP); tokP->type = typeCloseBrace; } else if (*tokP->begin == '[') { finishDelimiterToken(tokP); tokP->type = typeOpenBracket; } else if (*tokP->begin == ']') { finishDelimiterToken(tokP); tokP->type = typeCloseBracket; } else if (*tokP->begin == ':') { finishDelimiterToken(tokP); tokP->type = typeColon; } else if (*tokP->begin == ',') { finishDelimiterToken(tokP); tokP->type = typeComma; } else if (*tokP->begin == '"') { finishStringToken(envP, tokP); if (!envP->fault_occurred) tokP->type = typeString; } else { if (isWordChar(*tokP->begin)) { finishAlphanumericWordToken(tokP); if (isInteger(tokP->begin, tokP->size)) tokP->type = typeInteger; else if (isFloat(tokP->begin, tokP->size)) tokP->type = typeFloat; else if (xmlrpc_strneq(tokP->begin, "null", tokP->size)) tokP->type = typeNull; else if (xmlrpc_strneq(tokP->begin, "undefined", tokP->size)) tokP->type = typeUndefined; else if(xmlrpc_strneq(tokP->begin, "false", tokP->size)) tokP->type = typeFalse; else if(xmlrpc_strneq(tokP->begin, "true", tokP->size)) tokP->type = typeTrue; else setParseErr(envP, tokP, "Invalid word token -- " "Not a valid integer, floating point " "number, 'null', 'true', or 'false'"); } else { setParseErr(envP, tokP, "Not a valid token -- starts with '%c'; " "a valid token starts with " "one of []{}:,\"-. or digit or letter", *tokP->begin); } } } } /*===========================================================================*/ static int utf8Decode(uint32_t const c, char * const out) { /*--------------------------------------------------------------------------- convert a unicode char to a utf8 char ---------------------------------------------------------------------------*/ if (c <= 0x7F) { /* 0XXX XXXX one byte */ out[0] = (char) c; return 1; } else if (c <= 0x7FF) { /* 110X XXXX two bytes */ out[0] = (char)( 0xC0 | (c >> 6) ); out[1] = (char)( 0x80 | (c & 0x3F) ); return 2; } else if (c <= 0xFFFF) { /* 1110 XXXX three bytes */ out[0] = (char) (0xE0 | (c >> 12)); out[1] = (char) (0x80 | ((c >> 6) & 0x3F)); out[2] = (char) (0x80 | (c & 0x3F)); return 3; } else if (c <= 0x1FFFFF) { /* 1111 0XXX four bytes */ out[0] = (char) (0xF0 | (c >> 18)); out[1] = (char) (0x80 | ((c >> 12) & 0x3F)); out[2] = (char) (0x80 | ((c >> 6) & 0x3F)); out[3] = (char) (0x80 | (c & 0x3F)); return 4; } else return 0; } static void getBackslashSequence(xmlrpc_env * const envP, const char * const cur, xmlrpc_mem_block * const memBlockP, unsigned int * const nBytesConsumedP) { char buffer[5]; unsigned int tsize; switch (*cur) { case '"': buffer[0] = '"'; tsize = 1; *nBytesConsumedP = 1; break; case '/': buffer[0] = '/'; tsize = 1; *nBytesConsumedP = 1; break; case '\\': buffer[0] = '\\'; tsize = 1; *nBytesConsumedP = 1; break; case 'b': buffer[0] = '\b'; tsize = 1; *nBytesConsumedP = 1; break; case 'f': buffer[0] = '\f'; tsize = 1; *nBytesConsumedP = 1; break; case 'n': buffer[0] = '\n'; tsize = 1; *nBytesConsumedP = 1; break; case 'r': buffer[0] = '\r'; tsize = 1; *nBytesConsumedP = 1; break; case 't': buffer[0] = '\t'; tsize = 1; *nBytesConsumedP = 1; break; case 'u': { long digit; strncpy(buffer, cur + 1, 4); digit = strtol(buffer, NULL, 16); tsize = utf8Decode(digit, buffer); *nBytesConsumedP = 5; /* uXXXX */ break; } default: xmlrpc_faultf(envP, "Invalid character after backslash " "escape: '%c'", *cur); *nBytesConsumedP = 0; /* quiet compiler warning */ tsize = 0; /* quiet compiler warning */ } if (!envP->fault_occurred) XMLRPC_MEMBLOCK_APPEND(char, envP, memBlockP, buffer, tsize ); } static void unescapeString(xmlrpc_env * const envP, const char * const begin, const char * const end, xmlrpc_mem_block * const memBlockP) { XMLRPC_MEMBLOCK_INIT(char, envP, memBlockP, 0); if (!envP->fault_occurred) { const char * cur; const char * last; cur = begin; last = cur; while (cur != end && !envP->fault_occurred) { if (*cur == '\\') { if (cur != last) { XMLRPC_MEMBLOCK_APPEND( char, envP, memBlockP, last, cur - last ); if (!envP->fault_occurred) last = cur; } if (!envP->fault_occurred) { unsigned int nBytesConsumed; cur += 1; /* consume slash */ getBackslashSequence(envP, cur, memBlockP, &nBytesConsumed); if (!envP->fault_occurred) { cur += nBytesConsumed; last = cur; } } } else ++cur; } if (!envP->fault_occurred) { if (cur != last) { XMLRPC_MEMBLOCK_APPEND(char, envP, memBlockP, last, cur - last ); } } if (!envP->fault_occurred) { /* Append terminating NUL */ XMLRPC_MEMBLOCK_APPEND(char, envP, memBlockP, "", 1); } if (envP->fault_occurred) XMLRPC_MEMBLOCK_CLEAN(char, memBlockP); } } static xmlrpc_value * makeUtf8String(xmlrpc_env * const envP, const char * const begin, const char * const end) { /*---------------------------------------------------------------------------- Copy a json string directly into a string value, and convert any json escaping (\uXXXX) to something acceptable to the internal string handling. Try to do this in as few chunks as possible ! -----------------------------------------------------------------------------*/ xmlrpc_value * valP; xmlrpc_createXmlrpcValue(envP, &valP); if (!envP->fault_occurred) { valP->_type = XMLRPC_TYPE_STRING; valP->_wcs_block = NULL; if (!envP->fault_occurred) unescapeString(envP, begin, end, &valP->_block); if (envP->fault_occurred) xmlrpc_DECREF(valP); } return valP; } static xmlrpc_value * stringTokenValue(xmlrpc_env * const envP, Tokenizer * const tokP) { xmlrpc_env env; xmlrpc_value * valP; xmlrpc_env_init(&env); assert(tokP->end >= tokP->begin + 2); assert(*tokP->begin == '"'); assert(*(tokP->end-1) == '"'); valP = makeUtf8String(&env, tokP->begin + 1, tokP->end - 1); if (env.fault_occurred) { setParseErr(envP, tokP, "Error in string token: %s", env.fault_string); } xmlrpc_env_clean(&env); return valP; } static xmlrpc_value * integerTokenValue(xmlrpc_env * const envP, Tokenizer * const tokP) { xmlrpc_env env; char valueString[tokP->size + 1]; xmlrpc_int64 value; xmlrpc_value * valP; xmlrpc_env_init(&env); memcpy(valueString, tokP->begin, tokP->size); valueString[tokP->size] = '\0'; xmlrpc_parse_int64(&env, valueString, &value); if (env.fault_occurred) setParseErr(envP, tokP, "Error in integer token value '%s': %s", tokP->begin, env.fault_string); else valP = xmlrpc_i8_new(envP, value); xmlrpc_env_clean(&env); return valP; } /* Forward declarations for recursion: */ static xmlrpc_value * parseValue(xmlrpc_env * const envP, Tokenizer * const tokP); static xmlrpc_value * parseList(xmlrpc_env * const envP, Tokenizer * const tokP); static xmlrpc_value * parseObject(xmlrpc_env * const envP, Tokenizer * const tokP); static void parseListElement(xmlrpc_env * const envP, Tokenizer * const tokP, xmlrpc_value * const listArrayP, bool * const endOfListP) { xmlrpc_value * itemP; itemP = parseValue(envP, tokP); if (!envP->fault_occurred) { xmlrpc_array_append_item(envP, listArrayP, itemP); if (!envP->fault_occurred) { getToken(envP, tokP); if (!envP->fault_occurred) { if (tokP->type == typeComma) { *endOfListP = false; } else if (tokP->type == typeCloseBracket) *endOfListP = true; else setParseErr(envP, tokP, "Need comma or close bracket " "after array item. Instead we have %s", tokTypeName(tokP->type)); } } xmlrpc_DECREF(itemP); } } static xmlrpc_value * parseList(xmlrpc_env * const envP, Tokenizer * const tokP) { xmlrpc_value * retval; XMLRPC_ASSERT_ENV_OK(envP); retval = xmlrpc_array_new(envP); if (!envP->fault_occurred) { bool endOfList; for (endOfList = false; !endOfList && !envP->fault_occurred; ) { getToken(envP,tokP); if (!envP->fault_occurred) { if (tokP->type == typeEof) endOfList = true; else if (tokP->type == typeCloseBracket) endOfList = true; else parseListElement(envP, tokP, retval, &endOfList); } } if (envP->fault_occurred) xmlrpc_DECREF(retval); } return retval; } static void parseObjectMemberValue(xmlrpc_env * const envP, Tokenizer * const tokP, xmlrpc_value * const keyP, xmlrpc_value * const objectP) { xmlrpc_value * valP; getToken(envP,tokP); if (!envP->fault_occurred) { valP = parseValue(envP, tokP); if (!envP->fault_occurred) { xmlrpc_struct_set_value_v(envP, objectP, keyP, valP); xmlrpc_DECREF(valP); } } } static void parseObjectMember(xmlrpc_env * const envP, Tokenizer * const tokP, xmlrpc_value * const objectP) { xmlrpc_env env; xmlrpc_value * keyP; xmlrpc_env_init(&env); /* The current token is the string which is the member name: */ assert(tokP->type = typeString); assert(tokP->end >= tokP->begin + 2); assert(*tokP->begin == '"'); assert(*(tokP->end-1) == '"'); keyP = makeUtf8String(&env, tokP->begin + 1, tokP->end - 1); if (env.fault_occurred) setParseErr(envP, tokP, "Error in what is supposed to be " "the key of a member of an object: %s", env.fault_string); else { getToken(envP, tokP); if (!envP->fault_occurred) { if (tokP->type == typeColon) parseObjectMemberValue(envP, tokP, keyP, objectP); else setParseErr(envP, tokP, "Need a colon after member key " "in object. Instead we have %s", tokTypeName(tokP->type)); } xmlrpc_DECREF(keyP); } xmlrpc_env_clean(&env); } static xmlrpc_value * parseObject(xmlrpc_env * const envP, Tokenizer * const tokP) { xmlrpc_value * retval; XMLRPC_ASSERT_ENV_OK(envP); retval = xmlrpc_struct_new(envP); if (!envP->fault_occurred) { bool objectDone; objectDone = false; while (!objectDone && !envP->fault_occurred) { getToken(envP, tokP); if (!envP->fault_occurred) { if (tokP->type == typeCloseBrace) { objectDone = true; } else if (tokP->type == typeString) { parseObjectMember(envP, tokP, retval); if (!envP->fault_occurred) { getToken(envP, tokP); if (!envP->fault_occurred) { if (tokP->type == typeComma) { /* member separator; keep going */ } else if (tokP->type == typeCloseBrace) { /* No more members in this object */ objectDone = true; } else setParseErr( envP, tokP, "Need a comma or close brace after object " "member. Instead we have %s", tokTypeName(tokP->type)); } } } else { setParseErr(envP, tokP, "Need a string (i.e. starting with " "a quotation mark) as member key " "in object, or closing brace to end the " "object. Instead we have %s", tokTypeName(tokP->type)); } } } if (envP->fault_occurred) xmlrpc_DECREF(retval); } return retval; } static xmlrpc_value * parseValue(xmlrpc_env * const envP, Tokenizer * const tokP) { xmlrpc_value * retval; XMLRPC_ASSERT_ENV_OK(envP); switch (tokP->type) { case typeOpenBracket: retval = parseList(envP, tokP); break; case typeOpenBrace: retval = parseObject(envP, tokP); break; case typeNull: retval = xmlrpc_nil_new(envP); break; case typeUndefined: retval = xmlrpc_nil_new(envP); break; case typeFalse: retval = xmlrpc_bool_new(envP, (xmlrpc_bool)false); break; case typeTrue: retval = xmlrpc_bool_new(envP, (xmlrpc_bool)true); break; case typeInteger: retval = integerTokenValue(envP, tokP); break; case typeFloat: retval = xmlrpc_double_new(envP, strtod(tokP->begin, NULL)); break; case typeString: retval = stringTokenValue(envP, tokP); break; default: retval = NULL; setParseErr(envP, tokP, "Invalid token " "where a value is supposed to begin: %s. " "Should be an open bracket, open brace, " "'null', 'false', 'true', a number, or a string", tokTypeName(tokP->type)); } return retval; } xmlrpc_value * xmlrpc_parse_json(xmlrpc_env * const envP, const char * const str) { xmlrpc_value * retval = retval; Tokenizer tok; XMLRPC_ASSERT_ENV_OK(envP); initializeTokenizer(&tok, str); getToken(envP, &tok); if (!envP->fault_occurred) { retval = parseValue(envP, &tok); if (!envP->fault_occurred) { getToken(envP, &tok); if (!envP->fault_occurred) { if (tok.type != typeEof) setParseErr(envP, &tok, "There is junk after the end of " "the JSON value, to wit a %s token", tokTypeName(tok.type)); } if (envP->fault_occurred) xmlrpc_DECREF(retval); } } terminateTokenizer(&tok); return retval; } /*============================================================================ Serialize value to JSON ============================================================================*/ /* Borrowed from xmlrpc_serialize */ static void formatOut(xmlrpc_env * const envP, xmlrpc_mem_block * const outputP, const char * const formatString, ... ) { va_list args; char buffer[1024]; int rc; XMLRPC_ASSERT_ENV_OK(envP); va_start(args, formatString); rc = XMLRPC_VSNPRINTF(buffer, sizeof(buffer), formatString, args); /* Old vsnprintf() (and Windows) fails with return value -1 if the full string doesn't fit in the buffer. New vsnprintf() puts whatever will fit in the buffer, and returns the length of the full string regardless. For us, this truncation is a failure. */ if (rc < 0) xmlrpc_faultf(envP, "formatOut() overflowed internal buffer"); else { unsigned int const formattedLen = rc; if (formattedLen + 1 >= (sizeof(buffer))) xmlrpc_faultf(envP, "formatOut() overflowed internal buffer"); else XMLRPC_MEMBLOCK_APPEND(char, envP, outputP, buffer, formattedLen); } va_end(args); } static void indent(xmlrpc_env * const envP, unsigned int const level, xmlrpc_mem_block * const outP) { unsigned int i; for (i = 0; i < level * 2 && !envP->fault_occurred; ++i) XMLRPC_MEMBLOCK_APPEND(char, envP, outP, " ", 1); } /* Forward declaration for recursion */ static void serializeValue(xmlrpc_env * const envP, xmlrpc_value * const valP, unsigned int const level, xmlrpc_mem_block * const outP); static void appendEscapeSeq(xmlrpc_env * const envP, xmlrpc_mem_block * const outP, unsigned char const c) { /*---------------------------------------------------------------------------- Append to *outP the escaped representation of 'c'. This is e.g. "\t" for tab, or "\u001C" for something exotic. -----------------------------------------------------------------------------*/ unsigned int size; char buffer[6]; char slashChar; /* Character that goes after the backslash, including 'u' for \uHHHH */ switch (c) { case '"' : slashChar = '"'; break; /* U+0022 */ case '\\': slashChar = '\\'; break; /* U+005C */ case '\b': slashChar = 'b'; break; /* U+0008 */ case '\f': slashChar = 'f'; break; /* U+000C */ case '\n': slashChar = 'n'; break; /* U+000A */ case '\r': slashChar = 'r'; break; /* U+000D */ case '\t': slashChar = 't'; break; /* U+0009 */ default: slashChar = 'u'; }; buffer[0] = '\\'; buffer[1] = slashChar; if (slashChar == 'u') { sprintf(&buffer[2], "%04x", c); size = 6; /* \u1234 */ } else size = 2; XMLRPC_MEMBLOCK_APPEND(char, envP, outP, buffer, size); } static void makeJsonString(xmlrpc_env * const envP, const char * const value, size_t const length, xmlrpc_mem_block * const outP) { /*---------------------------------------------------------------------------- Create a JSON representation of a string, appended to *outP. -----------------------------------------------------------------------------*/ const char * const begin = &value[0]; const char * const end = begin + length; const char * cur; const char * last; last = cur = begin; while (cur != end && !envP->fault_occurred) { unsigned char const c = *cur; if (c < 0x1F || c == '"' || c == '\\') { /* This characters needs to be escaped. Put a backslash escape sequence in the output for this character, after copying all the characters before it to the output. */ XMLRPC_MEMBLOCK_APPEND(char, envP, outP, last, cur - last); if (!envP->fault_occurred) { appendEscapeSeq(envP, outP, c); ++cur; last = cur; } } else ++cur; } /* Copy all characters since the last escaped character to the output */ if (cur != last) XMLRPC_MEMBLOCK_APPEND(char, envP, outP, last, cur - last); if (envP->fault_occurred) XMLRPC_MEMBLOCK_CLEAN(char, outP); } static void makeJsonStringFromXmlRpc(xmlrpc_env * const envP, const xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { /*---------------------------------------------------------------------------- Convert a string XML-RPC value to JSON, appended to *outP. -----------------------------------------------------------------------------*/ const char * value; size_t length; xmlrpc_read_string_lp(envP, valP, &length, &value); if (!envP->fault_occurred) { makeJsonString(envP, value, length, outP); xmlrpc_strfree(value); } } static void serializeInt(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { xmlrpc_int value; xmlrpc_read_int(envP, valP, &value); formatOut(envP, outP, "%d", value); } static void serializeI8(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { xmlrpc_int64 value; xmlrpc_read_i8(envP, valP, &value); formatOut(envP, outP, "%" XMLRPC_PRId64, value); } static void serializeBool(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { xmlrpc_bool value; xmlrpc_read_bool(envP, valP, &value); formatOut(envP, outP, "%s", value ? "true" : "false"); } static void serializeDouble(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { xmlrpc_double value; xmlrpc_read_double(envP, valP, &value); formatOut(envP, outP, "%e", value); } static void serializeDatetime(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { /* ISO 8601 time string as JSON does not have a datetime type */ formatOut(envP, outP, "\"%u%02u%02uT%02u:%02u:%02u\"", valP->_value.dt.Y, valP->_value.dt.M, valP->_value.dt.D, valP->_value.dt.h, valP->_value.dt.m, valP->_value.dt.s); } static void serializeString(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { formatOut(envP, outP, "\""); makeJsonStringFromXmlRpc(envP, valP, outP); formatOut(envP, outP, "\""); } static void serializeBitstring(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { /*---------------------------------------------------------------------------- Append to *outP a JSON string whose value is the bit string *valP in base64 ASCII. -----------------------------------------------------------------------------*/ const unsigned char * bytes; size_t size; xmlrpc_read_base64(envP, valP, &size, &bytes); if (!envP->fault_occurred) { xmlrpc_mem_block * const base64P = xmlrpc_base64_encode(envP, bytes, size); if (!envP->fault_occurred) { formatOut(envP, outP, "\""); XMLRPC_MEMBLOCK_APPEND( char, envP, outP, XMLRPC_MEMBLOCK_CONTENTS(char, base64P), XMLRPC_MEMBLOCK_SIZE(char, base64P)); if (!envP->fault_occurred) formatOut(envP, outP, "\""); XMLRPC_MEMBLOCK_FREE(char, base64P); } free((unsigned char*)bytes); } } static void serializeArray(xmlrpc_env * const envP, xmlrpc_value * const valP, unsigned int const level, xmlrpc_mem_block * const outP) { unsigned int const size = xmlrpc_array_size(envP, valP); if (!envP->fault_occurred) { unsigned int i; formatOut(envP, outP, "[\n"); for (i = 0; i < size && !envP->fault_occurred; ++i) { xmlrpc_value * const itemP = xmlrpc_array_get_item(envP, valP, i); if (!envP->fault_occurred) { if (!envP->fault_occurred) { serializeValue(envP, itemP, level + 1, outP); if (i < size - 1) XMLRPC_MEMBLOCK_APPEND(char, envP, outP, ",\n", 2); } } } if (!envP->fault_occurred) { XMLRPC_MEMBLOCK_APPEND(char, envP, outP, "\n", 1); indent(envP, level, outP); if (!envP->fault_occurred) { XMLRPC_MEMBLOCK_APPEND(char, envP, outP, "]", 1); } } } } static void serializeStructMember(xmlrpc_env * const envP, xmlrpc_value * const memberKeyP, xmlrpc_value * const memberValueP, unsigned int const level, xmlrpc_mem_block * const outP) { serializeValue(envP, memberKeyP, level, outP); if (!envP->fault_occurred) { formatOut(envP, outP, ":"); if (!envP->fault_occurred) serializeValue(envP, memberValueP, level, outP); } } static void serializeStruct(xmlrpc_env * const envP, xmlrpc_value * const valP, unsigned int const level, xmlrpc_mem_block * const outP) { if (!envP->fault_occurred) { formatOut(envP, outP, "{\n"); if (!envP->fault_occurred) { unsigned int const size = xmlrpc_struct_size(envP, valP); if (!envP->fault_occurred) { unsigned int i; for (i = 0; i < size && !envP->fault_occurred; ++i) { xmlrpc_value * memberKeyP; xmlrpc_value * memberValueP; xmlrpc_struct_get_key_and_value(envP, valP, i, &memberKeyP, &memberValueP); if (!envP->fault_occurred) { serializeStructMember(envP, memberKeyP, memberValueP, level + 1, outP); if (!envP->fault_occurred && i < size - 1) XMLRPC_MEMBLOCK_APPEND(char, envP, outP, ",\n", 2); } } if (!envP->fault_occurred) { XMLRPC_MEMBLOCK_APPEND(char, envP, outP, "\n", 1); indent(envP, level, outP); XMLRPC_MEMBLOCK_APPEND(char, envP, outP, "}", 1); } } } } } static void serializeValue(xmlrpc_env * const envP, xmlrpc_value * const valP, unsigned int const level, xmlrpc_mem_block * const outP) { XMLRPC_ASSERT_ENV_OK(envP); indent(envP, level, outP); switch (xmlrpc_value_type(valP)) { case XMLRPC_TYPE_INT: serializeInt(envP, valP, outP); break; case XMLRPC_TYPE_I8: serializeI8(envP, valP, outP); break; case XMLRPC_TYPE_BOOL: serializeBool(envP, valP, outP); break; case XMLRPC_TYPE_DOUBLE: serializeDouble(envP, valP, outP); break; case XMLRPC_TYPE_DATETIME: serializeDatetime(envP, valP, outP); break; case XMLRPC_TYPE_STRING: serializeString(envP, valP, outP); break; case XMLRPC_TYPE_BASE64: serializeBitstring(envP, valP, outP); break; case XMLRPC_TYPE_ARRAY: serializeArray(envP, valP, level, outP); break; case XMLRPC_TYPE_STRUCT: serializeStruct(envP, valP, level, outP); break; case XMLRPC_TYPE_C_PTR: xmlrpc_faultf(envP, "Tried to serialize a C pointer value."); break; case XMLRPC_TYPE_NIL: formatOut(envP, outP, "null"); break; case XMLRPC_TYPE_DEAD: xmlrpc_faultf(envP, "Tried to serialize a dead value."); break; default: xmlrpc_faultf(envP, "Invalid xmlrpc_value type: 0x%x", xmlrpc_value_type(valP)); } } void xmlrpc_serialize_json(xmlrpc_env * const envP, xmlrpc_value * const valP, xmlrpc_mem_block * const outP) { serializeValue(envP, valP, 0, outP); }