#include "Json.h"
#include <cstdio>
#include <cstdlib>
#include <ctype.h>
#include <cerrno>
#include <AutoDeleter.h>
#include <DataIO.h>
#include <UnicodeChar.h>
#include "JsonEventListener.h"
#include "JsonMessageWriter.h"
namespace BPrivate {
static const size_t kInitialAssemblyBufferSize = 64;
static const size_t kRetainedAssemblyBufferSize = 32 * 1024;
static const size_t kAssemblyBufferSizeIncrement = 256;
static const size_t kMaximumUtf8SequenceLength = 7;
class JsonParseAssemblyBuffer {
public:
JsonParseAssemblyBuffer()
:
fAssemblyBuffer(NULL),
fAssemblyBufferAllocatedSize(0),
fAssemblyBufferUsedSize(0)
{
fAssemblyBuffer = (char*) malloc(kInitialAssemblyBufferSize);
if (fAssemblyBuffer != NULL)
fAssemblyBufferAllocatedSize = kInitialAssemblyBufferSize;
}
~JsonParseAssemblyBuffer()
{
if (fAssemblyBuffer != NULL)
free(fAssemblyBuffer);
}
const char* Buffer() const
{
return fAssemblyBuffer;
}
status_t Reset()
{
fAssemblyBufferUsedSize = 0;
if (fAssemblyBufferAllocatedSize > kRetainedAssemblyBufferSize) {
fAssemblyBuffer = (char*) realloc(fAssemblyBuffer, kRetainedAssemblyBufferSize);
if (fAssemblyBuffer == NULL) {
fAssemblyBufferAllocatedSize = 0;
return B_NO_MEMORY;
}
fAssemblyBufferAllocatedSize = kRetainedAssemblyBufferSize;
}
return B_OK;
}
status_t AppendCharacter(char c)
{
status_t result = _EnsureAssemblyBufferAllocatedSize(fAssemblyBufferUsedSize + 1);
if (result == B_OK) {
fAssemblyBuffer[fAssemblyBufferUsedSize] = c;
fAssemblyBufferUsedSize++;
}
return result;
}
status_t AppendCharacters(char* str, size_t len)
{
status_t result = _EnsureAssemblyBufferAllocatedSize(fAssemblyBufferUsedSize + len);
if (result == B_OK) {
memcpy(&fAssemblyBuffer[fAssemblyBufferUsedSize], str, len);
fAssemblyBufferUsedSize += len;
}
return result;
}
status_t AppendUnicodeCharacter(uint32 c)
{
status_t result = _EnsureAssemblyBufferAllocatedSize(
fAssemblyBufferUsedSize + kMaximumUtf8SequenceLength);
if (result == B_OK) {
char* insertPtr = &fAssemblyBuffer[fAssemblyBufferUsedSize];
char* ptr = insertPtr;
BUnicodeChar::ToUTF8(c, &ptr);
size_t sequenceLength = static_cast<uint32>(ptr - insertPtr);
fAssemblyBufferUsedSize += sequenceLength;
}
return result;
}
private:
status_t _EnsureAssemblyBufferAllocatedSize(size_t minimumSize)
{
if (fAssemblyBufferAllocatedSize < minimumSize) {
size_t requestedSize = minimumSize;
if (requestedSize < kRetainedAssemblyBufferSize - kAssemblyBufferSizeIncrement) {
requestedSize = ((requestedSize / kAssemblyBufferSizeIncrement) + 1)
* kAssemblyBufferSizeIncrement;
}
fAssemblyBuffer = (char*) realloc(fAssemblyBuffer, requestedSize);
if (fAssemblyBuffer == NULL) {
fAssemblyBufferAllocatedSize = 0;
return B_NO_MEMORY;
}
fAssemblyBufferAllocatedSize = requestedSize;
}
return B_OK;
}
private:
char* fAssemblyBuffer;
size_t fAssemblyBufferAllocatedSize;
size_t fAssemblyBufferUsedSize;
};
class JsonParseAssemblyBufferResetter {
public:
JsonParseAssemblyBufferResetter(JsonParseAssemblyBuffer* assemblyBuffer)
:
fAssemblyBuffer(assemblyBuffer)
{
}
~JsonParseAssemblyBufferResetter()
{
fAssemblyBuffer->Reset();
}
private:
JsonParseAssemblyBuffer*
fAssemblyBuffer;
};
class JsonParseContext {
public:
JsonParseContext(BDataIO* data, BJsonEventListener* listener)
:
fListener(listener),
fData(data),
fLineNumber(1),
fPushbackChar(0),
fHasPushbackChar(false),
fAssemblyBuffer(new JsonParseAssemblyBuffer())
{
}
~JsonParseContext()
{
delete fAssemblyBuffer;
}
BJsonEventListener* Listener() const
{
return fListener;
}
BDataIO* Data() const
{
return fData;
}
int LineNumber() const
{
return fLineNumber;
}
void IncrementLineNumber()
{
fLineNumber++;
}
status_t NextChar(char* buffer)
{
if (fHasPushbackChar) {
buffer[0] = fPushbackChar;
fHasPushbackChar = false;
return B_OK;
}
return Data()->ReadExactly(buffer, 1);
}
void PushbackChar(char c)
{
if (fHasPushbackChar)
debugger("illegal state - more than one character pushed back");
fPushbackChar = c;
fHasPushbackChar = true;
}
JsonParseAssemblyBuffer* AssemblyBuffer()
{
return fAssemblyBuffer;
}
private:
BJsonEventListener* fListener;
BDataIO* fData;
uint32 fLineNumber;
char fPushbackChar;
bool fHasPushbackChar;
JsonParseAssemblyBuffer*
fAssemblyBuffer;
};
status_t
BJson::Parse(const BString& JSON, BMessage& message)
{
return Parse(JSON.String(), message);
}
status_t
BJson::Parse(const char* JSON, size_t length, BMessage& message)
{
BMemoryIO* input = new BMemoryIO(JSON, length);
ObjectDeleter<BMemoryIO> inputDeleter(input);
BJsonMessageWriter* writer = new BJsonMessageWriter(message);
ObjectDeleter<BJsonMessageWriter> writerDeleter(writer);
Parse(input, writer);
status_t result = writer->ErrorStatus();
return result;
}
status_t
BJson::Parse(const char* JSON, BMessage& message)
{
return Parse(JSON, strlen(JSON), message);
}
void
BJson::Parse(BDataIO* data, BJsonEventListener* listener)
{
JsonParseContext context(data, listener);
ParseAny(context);
listener->Complete();
}
bool
BJson::NextChar(JsonParseContext& jsonParseContext, char* c)
{
status_t result = jsonParseContext.NextChar(c);
switch (result) {
case B_OK:
return true;
case B_PARTIAL_READ:
{
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "unexpected end of input");
return false;
}
default:
{
jsonParseContext.Listener()->HandleError(result, -1,
"io related read error");
return false;
}
}
}
bool
BJson::NextNonWhitespaceChar(JsonParseContext& jsonParseContext, char* c)
{
while (true) {
if (!NextChar(jsonParseContext, c))
return false;
switch (*c) {
case 0x0a:
case 0x0d:
jsonParseContext.IncrementLineNumber();
case ' ':
break;
default:
return true;
}
}
}
bool
BJson::ParseAny(JsonParseContext& jsonParseContext)
{
char c;
if (!NextNonWhitespaceChar(jsonParseContext, &c))
return false;
switch (c) {
case 'f':
return ParseExpectedVerbatimStringAndRaiseEvent(
jsonParseContext, "alse", 4, 'f', B_JSON_FALSE);
case 't':
return ParseExpectedVerbatimStringAndRaiseEvent(
jsonParseContext, "rue", 3, 't', B_JSON_TRUE);
case 'n':
return ParseExpectedVerbatimStringAndRaiseEvent(
jsonParseContext, "ull", 3, 'n', B_JSON_NULL);
case '"':
return ParseString(jsonParseContext, B_JSON_STRING);
case '{':
return ParseObject(jsonParseContext);
case '[':
return ParseArray(jsonParseContext);
case '+':
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
jsonParseContext.PushbackChar(c);
return ParseNumber(jsonParseContext);
default:
{
BString errorMessage;
if (c >= 0x20 && c < 0x7f) {
errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]"
" (%c) when parsing element", static_cast<uint8>(c), c);
} else {
errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]"
" when parsing element", (uint8) c);
}
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), errorMessage.String());
return false;
}
}
return true;
}
bool
BJson::ParseObjectNameValuePair(JsonParseContext& jsonParseContext)
{
bool didParseName = false;
char c;
while (true) {
if (!NextNonWhitespaceChar(jsonParseContext, &c))
return false;
switch (c) {
case '\"':
{
if (!didParseName) {
if (!ParseString(jsonParseContext, B_JSON_OBJECT_NAME))
return false;
didParseName = true;
} else {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "unexpected"
" [\"] character when parsing object name-"
" value separator");
return false;
}
break;
}
case ':':
{
if (didParseName) {
if (!ParseAny(jsonParseContext))
return false;
return true;
} else {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "unexpected"
" [:] character when parsing object name-"
" value pair");
return false;
}
}
default:
{
BString errorMessage;
errorMessage.SetToFormat(
"unexpected character [%c] when parsing object"
" name-value pair",
c);
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), errorMessage.String());
return false;
}
}
}
}
bool
BJson::ParseObject(JsonParseContext& jsonParseContext)
{
if (!jsonParseContext.Listener()->Handle(
BJsonEvent(B_JSON_OBJECT_START))) {
return false;
}
char c;
bool firstItem = true;
while (true) {
if (!NextNonWhitespaceChar(jsonParseContext, &c))
return false;
switch (c) {
case '}':
{
if (!jsonParseContext.Listener()->Handle(
BJsonEvent(B_JSON_OBJECT_END))) {
return false;
}
return true;
}
case ',':
{
if (firstItem) {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "unexpected"
" item separator when parsing start of"
" object");
return false;
}
if (!ParseObjectNameValuePair(jsonParseContext))
return false;
break;
}
default:
{
if (firstItem) {
jsonParseContext.PushbackChar(c);
if (!ParseObjectNameValuePair(jsonParseContext))
return false;
firstItem = false;
} else {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "expected"
" separator when parsing an object");
}
}
}
}
return true;
}
bool
BJson::ParseArray(JsonParseContext& jsonParseContext)
{
if (!jsonParseContext.Listener()->Handle(
BJsonEvent(B_JSON_ARRAY_START))) {
return false;
}
char c;
bool firstItem = true;
while (true) {
if (!NextNonWhitespaceChar(jsonParseContext, &c))
return false;
switch (c) {
case ']':
{
if (!jsonParseContext.Listener()->Handle(
BJsonEvent(B_JSON_ARRAY_END))) {
return false;
}
return true;
}
case ',':
{
if (firstItem) {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "unexpected"
" item separator when parsing start of"
" array");
}
if (!ParseAny(jsonParseContext))
return false;
break;
}
default:
{
if (firstItem) {
jsonParseContext.PushbackChar(c);
if (!ParseAny(jsonParseContext))
return false;
firstItem = false;
} else {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "expected"
" separator when parsing an array");
}
}
}
}
return true;
}
bool
BJson::ParseEscapeUnicodeSequence(JsonParseContext& jsonParseContext)
{
char ch;
uint32 unicodeCh = 0;
for (int i = 3; i >= 0; i--) {
if (!NextChar(jsonParseContext, &ch)) {
jsonParseContext.Listener()->HandleError(B_ERROR, jsonParseContext.LineNumber(),
"unable to read unicode sequence");
return false;
}
if (ch >= '0' && ch <= '9')
unicodeCh |= static_cast<uint32>(ch - '0') << (i * 4);
else if (ch >= 'a' && ch <= 'f')
unicodeCh |= (10 + static_cast<uint32>(ch - 'a')) << (i * 4);
else if (ch >= 'A' && ch <= 'F')
unicodeCh |= (10 + static_cast<uint32>(ch - 'A')) << (i * 4);
else {
BString errorMessage;
errorMessage.SetToFormat(
"malformed hex character [%c] in unicode sequence in string parsing", ch);
jsonParseContext.Listener()->HandleError(B_BAD_DATA, jsonParseContext.LineNumber(),
errorMessage.String());
return false;
}
}
JsonParseAssemblyBuffer* assemblyBuffer = jsonParseContext.AssemblyBuffer();
status_t result = assemblyBuffer->AppendUnicodeCharacter(unicodeCh);
if (result != B_OK) {
jsonParseContext.Listener()->HandleError(result, jsonParseContext.LineNumber(),
"unable to store unicode char as utf-8");
return false;
}
return true;
}
bool
BJson::ParseStringEscapeSequence(JsonParseContext& jsonParseContext)
{
char c;
if (!NextChar(jsonParseContext, &c))
return false;
JsonParseAssemblyBuffer* assemblyBuffer = jsonParseContext.AssemblyBuffer();
switch (c) {
case 'n':
assemblyBuffer->AppendCharacter('\n');
break;
case 'r':
assemblyBuffer->AppendCharacter('\r');
break;
case 'b':
assemblyBuffer->AppendCharacter('\b');
break;
case 'f':
assemblyBuffer->AppendCharacter('\f');
break;
case '\\':
assemblyBuffer->AppendCharacter('\\');
break;
case '/':
assemblyBuffer->AppendCharacter('/');
break;
case 't':
assemblyBuffer->AppendCharacter('\t');
break;
case '"':
assemblyBuffer->AppendCharacter('"');
break;
case 'u':
{
if (!ParseEscapeUnicodeSequence(jsonParseContext)) {
return false;
}
break;
}
default:
{
BString errorMessage;
errorMessage.SetToFormat("unexpected escaped character [%c] in string parsing", c);
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), errorMessage.String());
return false;
}
}
return true;
}
bool
BJson::ParseString(JsonParseContext& jsonParseContext,
json_event_type eventType)
{
char c;
JsonParseAssemblyBuffer* assemblyBuffer = jsonParseContext.AssemblyBuffer();
JsonParseAssemblyBufferResetter assembleBufferResetter(assemblyBuffer);
while(true) {
if (!NextChar(jsonParseContext, &c))
return false;
switch (c) {
case '"':
{
assemblyBuffer->AppendCharacter(0);
jsonParseContext.Listener()->Handle(
BJsonEvent(eventType, assemblyBuffer->Buffer()));
return true;
}
case '\\':
{
if (!ParseStringEscapeSequence(jsonParseContext))
return false;
break;
}
default:
{
uint8 uc = static_cast<uint8>(c);
if(uc < 0x20) {
BString errorMessage;
errorMessage.SetToFormat("illegal control character"
" [%" B_PRIu8 "] when parsing a string", uc);
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(),
errorMessage.String());
return false;
}
assemblyBuffer->AppendCharacter(c);
break;
}
}
}
}
bool
BJson::ParseExpectedVerbatimStringAndRaiseEvent(
JsonParseContext& jsonParseContext, const char* expectedString,
size_t expectedStringLength, char leadingChar,
json_event_type jsonEventType)
{
if (ParseExpectedVerbatimString(jsonParseContext, expectedString,
expectedStringLength, leadingChar)) {
if (!jsonParseContext.Listener()->Handle(BJsonEvent(jsonEventType)))
return false;
}
return true;
}
bool
BJson::ParseExpectedVerbatimString(JsonParseContext& jsonParseContext,
const char* expectedString, size_t expectedStringLength, char leadingChar)
{
char c;
size_t offset = 0;
while (offset < expectedStringLength) {
if (!NextChar(jsonParseContext, &c))
return false;
if (c != expectedString[offset]) {
BString errorMessage;
errorMessage.SetToFormat("malformed json primative literal; "
"expected [%c%s], but got [%c] at position %" B_PRIdSSIZE,
leadingChar, expectedString, c, offset);
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), errorMessage.String());
return false;
}
offset++;
}
return true;
}
bool
BJson::IsValidNumber(const char* value)
{
int32 offset = 0;
int32 len = strlen(value);
if (offset < len && value[offset] == '-')
offset++;
if (offset >= len)
return false;
if (isdigit(value[offset]) && value[offset] != '0') {
while (offset < len && isdigit(value[offset]))
offset++;
} else {
if (value[offset] == '0')
offset++;
else
return false;
}
if (offset < len && value[offset] == '.') {
offset++;
if (offset >= len)
return false;
while (offset < len && isdigit(value[offset]))
offset++;
}
if (offset < len && (value[offset] == 'E' || value[offset] == 'e')) {
offset++;
if(offset < len && (value[offset] == '+' || value[offset] == '-'))
offset++;
if (offset >= len)
return false;
while (offset < len && isdigit(value[offset]))
offset++;
}
return offset == len;
}
bool
BJson::ParseNumber(JsonParseContext& jsonParseContext)
{
JsonParseAssemblyBuffer* assemblyBuffer = jsonParseContext.AssemblyBuffer();
JsonParseAssemblyBufferResetter assembleBufferResetter(assemblyBuffer);
while (true) {
char c;
status_t result = jsonParseContext.NextChar(&c);
switch (result) {
case B_OK:
{
if (isdigit(c) || c == '.' || c == '-' || c == 'e' || c == 'E' || c == '+') {
assemblyBuffer->AppendCharacter(c);
break;
}
jsonParseContext.PushbackChar(c);
}
case B_PARTIAL_READ:
{
errno = 0;
assemblyBuffer->AppendCharacter(0);
if (!IsValidNumber(assemblyBuffer->Buffer())) {
jsonParseContext.Listener()->HandleError(B_BAD_DATA,
jsonParseContext.LineNumber(), "malformed number");
return false;
}
jsonParseContext.Listener()->Handle(BJsonEvent(B_JSON_NUMBER,
assemblyBuffer->Buffer()));
return true;
}
default:
{
jsonParseContext.Listener()->HandleError(result, -1,
"io related read error");
return false;
}
}
}
}
}