/**************************************************************************** * * Copyright (c) 2006 Novell, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. * * To contact Novell about this file by physical or electronic mail, you * may find current contact information at www.novell.com. * ****************************************************************************/ /* Copyright (c) 2002 JSON.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include #include #include "assert.h" /* FIXME: this just parses entire buffers, we really want something * that can process streams */ struct _BongoJsonParser { int error; const char *buffer; int index; int buflen; int charptr; int numRead; BOOL eof; char realbuf[1024]; int maxLen; void *userData; BongoJsonParserReadFunc readFunc; BongoJsonParserFreeFunc freeFunc; }; static BongoJsonNode *BongoJsonParserNextNode(BongoJsonParser *t); static int FileRead(void *userData, char *buffer, int maxRead) { FILE *f = userData; return (int)fread(buffer, 1, maxRead, f); } static void BongoJsonParserInitString(BongoJsonParser *t, const char *source) { t->buffer = source; t->index = 0; t->buflen = strlen(source); t->error = BONGO_JSON_OK; t->eof = FALSE; t->userData = NULL; t->readFunc = NULL; t->freeFunc = NULL; t->maxLen = 0; } static void BongoJsonParserInit(BongoJsonParser *t, void *userData, BongoJsonParserReadFunc readFunc, BongoJsonParserFreeFunc freeFunc, size_t maxLen) { t->buffer = t->realbuf; t->index = 0; t->buflen = 0; t->error = BONGO_JSON_OK; t->eof = FALSE; t->numRead = 0; t->charptr = 0; t->userData = userData; t->readFunc = readFunc; t->freeFunc = freeFunc; t->maxLen = maxLen; } static void BongoJsonParserBack(BongoJsonParser *t) { if (t->index > 0) { t->index--; t->charptr--; } } static void EnsureBuffer(BongoJsonParser *t) { if (t->index >= t->buflen) { int toRead = 1024; if (t->maxLen != 0) { int left = t->maxLen - t->numRead; if (left < toRead) { left = toRead; } } if (toRead == 0) { t->eof = TRUE; } else { if (t->readFunc) { t->buflen = t->readFunc(t->userData, t->realbuf, toRead); t->numRead += t->buflen; if (t->buflen == 0) { t->eof = TRUE; } } else { t->eof = TRUE; } } t->index = 0; } } static char BongoJsonParserNext(BongoJsonParser *t) { EnsureBuffer(t); if (t->eof) { return 0; } t->charptr++; return t->buffer[t->index++]; } #define BongoJsonParserError(t, error) BongoJsonParserErrorReal(t, error, __FILE__, __LINE__) static void BongoJsonParserErrorReal(BongoJsonParser *t, int error, const char *file, int line) { t->error = error; printf("%s(%d): Parse error %d at %d\n", file, line, error, t->charptr); } static void BongoJsonParserNextChars(BongoJsonParser *t, char *buf, int buflen) { while (buflen) { int toCopy; EnsureBuffer(t); if (t->eof) { BongoJsonParserError(t, BONGO_JSON_BOUNDS_ERROR); return; } toCopy = t->buflen - t->index; if (toCopy > buflen) { toCopy = buflen; } memcpy(buf, t->buffer + t->index, toCopy); buflen -= toCopy; t->index += toCopy; t->charptr += toCopy; } } static char BongoJsonParserNextClean(BongoJsonParser *t) { while (TRUE) { char c; c = BongoJsonParserNext(t); if (c == '/') { switch(BongoJsonParserNext(t)) { case '/' : do { c = BongoJsonParserNext(t); } while (c != '\n' && c != '\r' && c != 0); break; case '*' : while (TRUE) { c = BongoJsonParserNext(t); if (c == 0) { BongoJsonParserError(t, BONGO_JSON_UNCLOSED_COMMENT); return 0; } else if (c == '*') { if (BongoJsonParserNext(t) == '/') { break; } BongoJsonParserBack(t); } } break; default : BongoJsonParserBack(t); return '/'; } } else if (c == 0 || c > ' ') { return c; } } } static char * BongoJsonParserNextString(BongoJsonParser *t, char quote) { char c; BongoStringBuilder sb; BongoStringBuilderInit(&sb); while (TRUE) { c = BongoJsonParserNext(t); if (c == 0 || c == '\r' || c == '\n') { BongoJsonParserError(t, BONGO_JSON_UNCLOSED_STRING); MemFree(sb.value); return NULL; } if (c == '\\') { char buf[5]; char *endp; uint64_t charAsInt; c = BongoJsonParserNext(t); switch(c) { case 0 : BongoJsonParserError(t, BONGO_JSON_UNCLOSED_STRING); MemFree(sb.value); return NULL; case 'b' : BongoStringBuilderAppendChar(&sb, '\b'); break; case 't' : BongoStringBuilderAppendChar(&sb, '\t'); break; case 'n' : BongoStringBuilderAppendChar(&sb, '\t'); break; case 'f' : BongoStringBuilderAppendChar(&sb, '\f'); break; case 'r' : BongoStringBuilderAppendChar(&sb, '\r'); break; case 'u' : BongoJsonParserNextChars(t, buf, 4); if (t->error) { MemFree(sb.value); return NULL; } buf[4] = '\0'; charAsInt = HexToUInt64(buf, &endp); BongoStringBuilderAppendChar(&sb, (char)charAsInt); break; default : BongoStringBuilderAppendChar(&sb, c); } } else { if (c == quote) { return sb.value; } BongoStringBuilderAppendChar(&sb, c); } } } static BongoJsonNode * BongoJsonParserNextObjectNode(BongoJsonParser *t) { char c; BongoJsonNode *ret; BongoJsonObject *obj; if (BongoJsonParserNextClean(t) != '{') { BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_CHAR); return NULL; } obj = BongoJsonObjectNew(); ret = BongoJsonNodeNewObject(obj); while (TRUE) { BongoJsonNode *nextNode; char *key; c = BongoJsonParserNextClean(t); switch (c) { case 0 : BongoJsonParserError(t, BONGO_JSON_UNCLOSED_OBJECT); goto done; case '}' : goto done; default : BongoJsonParserBack(t); nextNode = BongoJsonParserNextNode(t); if (t->error || nextNode->type != BONGO_JSON_STRING) { if (nextNode) { BongoJsonNodeFree(nextNode); } BongoJsonParserError(t, BONGO_JSON_BAD_KEY); goto done; } key = BongoJsonNodeAsString(nextNode); BongoJsonNodeFreeSteal(nextNode); } c = BongoJsonParserNextClean(t); if (c != ':') { BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_CHAR); goto done; } nextNode = BongoJsonParserNextNode(t); if (t->error) { goto done; } BongoJsonObjectPut(obj, key, nextNode); MemFree(key); switch(BongoJsonParserNextClean(t)) { case ',': if (BongoJsonParserNextClean(t) == '}') { goto done; } BongoJsonParserBack(t); break; case '}' : goto done; default : BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_CHAR); goto done; } } done : if (t->error) { BongoJsonNodeFree(ret); ret = NULL; } return ret; } static BongoJsonNode * BongoJsonParserNextArrayNode(BongoJsonParser *t) { BongoJsonNode *ret; BongoArray *array; if (BongoJsonParserNextClean(t) != '[') { BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_CHAR); return NULL; } array = BongoArrayNew(sizeof(BongoJsonNode *), 15); ret = BongoJsonNodeNewArray(array); if (BongoJsonParserNextClean(t) == ']') { return ret; } BongoJsonParserBack(t); while (TRUE) { BongoJsonNode *nextNode; nextNode = BongoJsonParserNextNode(t); if (t->error) { goto done; } BongoArrayAppendValue(array, nextNode); switch(BongoJsonParserNextClean(t)) { case '0' : BongoJsonParserError(t, BONGO_JSON_UNCLOSED_ARRAY); goto done; case ',': if (BongoJsonParserNextClean(t) == ']') { goto done; } BongoJsonParserBack(t); break; case ']' : goto done; default : BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_CHAR); goto done; } } done : if (t->error) { BongoJsonNodeFree(ret); ret = NULL; } return ret; } static BongoJsonNode * BongoJsonParserNextStringNode(BongoJsonParser *t, char c) { char *s; s = BongoJsonParserNextString(t, c); return BongoJsonNodeNewStringGive(s); } static BongoJsonNode * BongoJsonParserNextNode(BongoJsonParser *t) { BongoStringBuilder sb; char b; char c; char *s; c = BongoJsonParserNextClean(t); if (c == '"' || c == '\'') { return BongoJsonParserNextStringNode(t, c); } else if (c == '{') { BongoJsonParserBack(t); return BongoJsonParserNextObjectNode(t); } else if (c == '[') { BongoJsonParserBack(t); return BongoJsonParserNextArrayNode(t); } else if (c == 0) { BongoJsonParserError(t, BONGO_JSON_UNEXPECTED_EOF); return NULL; } BongoStringBuilderInit(&sb); b = c; while (c > ' ' && c != ':' && c != ',' && c != ']' && c != '}' && c != '/') { BongoStringBuilderAppendChar(&sb, c); c = BongoJsonParserNext(t); } BongoJsonParserBack(t); s = sb.value; BongoStrTrim(s); if (!strcmp(s, "true")) { MemFree(s); return BongoJsonNodeNewBool(TRUE); } else if (!strcmp(s, "false")) { MemFree(s); return BongoJsonNodeNewBool(FALSE); } else if (!strcmp(s, "null")) { MemFree(s); return BongoJsonNodeNewNull(); } if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { int i; double d; char *endp; i = strtol(s, &endp, 10); if (*endp == '\0') { MemFree(s); return BongoJsonNodeNewInt(i); } d = strtod(s, &endp); if (*endp == '\0') { MemFree(s); return BongoJsonNodeNewDouble(d); } } if (s[0] == '\0') { BongoJsonParserError(t, BONGO_JSON_MISSING_VALUE); MemFree(s); return NULL; } return BongoJsonNodeNewStringGive(s); } BongoJsonResult BongoJsonParserReadNode(BongoJsonParser *parser, BongoJsonNode **node) { *node = BongoJsonParserNextNode(parser); return parser->error; } BongoJsonResult BongoJsonParseString(const char *str, BongoJsonNode **node) { BongoJsonParser t; BongoJsonParserInitString(&t, str); *node = BongoJsonParserNextNode(&t); return t.error; } BongoJsonResult BongoJsonParseFile(FILE *f, int toRead, BongoJsonNode **node) { BongoJsonParser t; BongoJsonParserInit(&t, f, FileRead, NULL, toRead); *node = BongoJsonParserNextNode(&t); return t.error; } BongoJsonParser * BongoJsonParserNewString(const char *str) { BongoJsonParser *parser; parser = MemMalloc0(sizeof(BongoJsonParser)); BongoJsonParserInitString(parser, str); return parser; } BongoJsonParser * BongoJsonParserNewFile(FILE *f, int toRead) { BongoJsonParser *parser; parser = MemMalloc0(sizeof(BongoJsonParser)); BongoJsonParserInit(parser, f, FileRead, NULL, toRead); return parser; } BongoJsonParser * BongoJsonParserNew(void *userData, BongoJsonParserReadFunc readFunc, BongoJsonParserFreeFunc freeFunc, int maxRead) { BongoJsonParser *parser; parser = MemMalloc(sizeof(BongoJsonParser)); BongoJsonParserInit(parser, userData, readFunc, freeFunc, maxRead); return parser; } void BongoJsonParserFree(BongoJsonParser *parser) { if (parser->freeFunc) { parser->freeFunc(parser->userData); } MemFree(parser); } BongoJsonResult BongoJsonValidateString(const char *str) { BongoJsonNode *node; BongoJsonResult res; /* This could be done more efficiently */ res = BongoJsonParseString(str, &node); if (res == BONGO_JSON_OK) { BongoJsonNodeFree(node); } return res; } char * BongoJsonQuoteString(const char *str) { BongoStringBuilder sb; BongoStringBuilderInit(&sb); BongoJsonQuoteStringToStringBuilder(str, &sb); return sb.value; } #define UTF8_LENGTH(c, len) \ if (c < 128) \ len = 1; \ else if ((c & 0xe0) == 0xc0) \ len = 2; \ else if ((c & 0xf0) == 0xe0) \ len = 3; \ else if ((c & 0xf8) == 0xf0) \ len = 4; \ else if ((c & 0xfc) == 0xf8) \ len = 5; \ else if ((c & 0xfe) == 0xfc) \ len = 6; \ else \ len = -1; void BongoJsonQuoteStringToStringBuilder(const char *str, BongoStringBuilder *sb) { unsigned char c; int i; int len; if (str == NULL || str[0] == '\0') { BongoStringBuilderAppend(sb, "\"\""); return; } len = strlen(str); BongoStringBuilderAppendChar(sb, '"'); for (i = 0; i < len; i++) { int len; c = str[i]; UTF8_LENGTH(c, len); if (len < 2) { if ((c == '\\') || (c == '"')) { char str[2]; str[0] = '\\'; str[1] = c; BongoStringBuilderAppendN(sb, str, 2); } else if (c == '\b') { BongoStringBuilderAppend(sb, "\\b"); } else if (c == '\t') { BongoStringBuilderAppend(sb, "\\t"); } else if (c == '\f') { BongoStringBuilderAppend(sb, "\\n"); } else if (c == '\r') { BongoStringBuilderAppend(sb, "\\r"); } else { if (c < ' ') { BongoStringBuilderAppendF(sb, "\\u%04x", (int)c); } else { BongoStringBuilderAppendChar(sb, c); } } } else { BongoStringBuilderAppendN(sb, str + i, len); i += len - 1; } } BongoStringBuilderAppendChar(sb, '"'); }