Json : Fix String Writer

Tidy-up and correct logic around writing JSON
encoded strings.

related #13832

Change-Id: I1eca33e11dff4457f85a896c02331c1cd9ae1110
Reviewed-on: https://review.haiku-os.org/617
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Andrew Lindesay
2018-10-06 19:58:35 +00:00
committed by waddlesplash
parent f1962303a3
commit 15fed7905d
4 changed files with 101 additions and 47 deletions
+1
View File
@@ -39,6 +39,7 @@ private:
status_t StreamStringEncoded(const char* string); status_t StreamStringEncoded(const char* string);
status_t StreamStringEncoded(const char* string, status_t StreamStringEncoded(const char* string,
off_t offset, size_t length); off_t offset, size_t length);
status_t StreamStringUnicodeCharacter(uint32 c);
status_t StreamQuotedEncodedString(const char* string); status_t StreamQuotedEncodedString(const char* string);
status_t StreamQuotedEncodedString(const char* string, status_t StreamQuotedEncodedString(const char* string,
+47 -47
View File
@@ -55,6 +55,20 @@ b_json_simple_esc_sequence(char c)
} }
static size_t
b_json_len_7bit_clean_non_esc(uint8* c, size_t length) {
size_t result = 0;
while (result < length
&& b_json_is_7bit_clean(c[result])
&& b_json_simple_esc_sequence(c[result]) == NULL) {
result++;
}
return result;
}
/*! The class and sub-classes of it are used as a stack internal to the /*! The class and sub-classes of it are used as a stack internal to the
BJsonTextWriter class. As the JSON is parsed, the stack of these BJsonTextWriter class. As the JSON is parsed, the stack of these
internal listeners follows the stack of the JSON parsing in terms of internal listeners follows the stack of the JSON parsing in terms of
@@ -595,6 +609,16 @@ BJsonTextWriter::StreamStringEncoded(const char* string)
} }
status_t
BJsonTextWriter::StreamStringUnicodeCharacter(uint32 c)
{
sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, c);
// note that the buffer's first two bytes are populated with the JSON
// prefix for a unicode char.
return StreamStringVerbatim(fUnicodeAssemblyBuffer, 0, 6);
}
/*! Note that this method will expect a UTF-8 encoded string. */ /*! Note that this method will expect a UTF-8 encoded string. */
status_t status_t
@@ -603,67 +627,43 @@ BJsonTextWriter::StreamStringEncoded(const char* string,
{ {
status_t writeResult = B_OK; status_t writeResult = B_OK;
uint8* string8bit = (uint8*)string; uint8* string8bit = (uint8*)string;
off_t i = 0;
while (writeResult == B_OK && length != 0) { while (i < length && writeResult == B_OK) {
uint8 c = string8bit[offset]; uint8 c = string8bit[offset + i];
const char* simpleEsc = b_json_simple_esc_sequence(c); const char* simpleEsc = b_json_simple_esc_sequence(c);
// simple escape sequence involving the backslash + one character.
if (simpleEsc != NULL) { if (simpleEsc != NULL) {
// here the character to emit is something like a tab or a quote
// in this case the output JSON should escape it so that it looks
// like \t or \n in the output.
writeResult = StreamStringVerbatim(simpleEsc, 0, 2); writeResult = StreamStringVerbatim(simpleEsc, 0, 2);
i++;
if (writeResult == B_OK) {
offset++;
length--;
}
} else { } else {
if (b_json_is_7bit_clean(c)) { if (b_json_is_7bit_clean(c)) {
// in this case the first character is a simple one that can be
// roll forward while the characters are simple and then // output without any special handling. Find the sequence of
// output them at as a block verbatim. // such characters and output them as a sequence so that it's
// included as one write operation.
uint32 count7BitClean = 1; size_t l = 1 + b_json_len_7bit_clean_non_esc(
&string8bit[offset + i + 1], length - (offset + i + 1));
while (count7BitClean < length writeResult = StreamStringVerbatim(&string[offset + i], 0, l);
&& b_json_is_7bit_clean( i += static_cast<off_t>(l);
string8bit[offset + count7BitClean])) {
count7BitClean++;
}
writeResult = StreamStringVerbatim(&string[offset], 0,
count7BitClean);
if (writeResult == B_OK) {
offset += count7BitClean;
length -= count7BitClean;
}
} else { } else {
if (b_json_is_illegal(c)) { if (b_json_is_illegal(c)) {
fprintf(stderr, "! string encoding error - illegal " fprintf(stderr, "! string encoding error - illegal "
"character [%" B_PRIu32 "]\n", static_cast<uint32>(c)); "character [%" B_PRIu32 "]\n", static_cast<uint32>(c));
offset++; i++;
length--;
} else { } else {
// if the character is < 128 then it can be rendered // now we have a UTF-8 sequence. Read the UTF-8 sequence
// verbatim - check how many are like this and then // to get the unicode character and then encode that as
// render those verbatim. // JSON.
const char* stringInitial = &string[offset]; const char* unicodeStr = &string[offset + i];
uint32 unicodeCharacter = BUnicodeChar::FromUTF8( uint32 unicodeCharacter = BUnicodeChar::FromUTF8(
&stringInitial); &unicodeStr);
writeResult = StreamStringUnicodeCharacter(
sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32,
unicodeCharacter); unicodeCharacter);
writeResult = StreamStringVerbatim(fUnicodeAssemblyBuffer, i += static_cast<off_t>(unicodeStr - &string[offset + i]);
0, 6);
if (writeResult == B_OK) {
uint32 sequence_length
= (uint32)(stringInitial - &string[offset]);
offset += sequence_length;
length -= sequence_length;
}
} }
} }
} }
@@ -173,6 +173,52 @@ JsonTextWriterTest::TestFalse()
} }
void
JsonTextWriterTest::TestStringGeneric(const char *input,
const char *expectedOut)
{
BMallocIO* outputData = new BMallocIO();
ObjectDeleter<BMallocIO> outputDataDeleter(outputData);
BJsonTextWriter writer(outputData);
CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString(input));
writer.Complete();
BString outputString((char*)outputData->Buffer(),
outputData->BufferLength());
fprintf(stderr, "expected out >%s<\n", expectedOut);
fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n",
outputString.String(), outputData->BufferLength());
CPPUNIT_ASSERT_EQUAL(BString(expectedOut),
outputString);
}
void
JsonTextWriterTest::TestString()
{
TestStringGeneric(
"\"Eichh\xc3\xb6rnchen\"\nsind\nTiere.",
"\"\\\"Eichh\\u00f6rnchen\\\"\\nsind\\nTiere.\"");
// complex example with unicode, escapes and simple sequences.
TestStringGeneric("", "\"\"");
TestStringGeneric("Press \"C\" to continue",
"\"Press \\\"C\\\" to continue\"");
// test of a simple string of one character enclosed with escape
// characters to check handling of one character simple sub-sequences.
TestStringGeneric("\xc3\xb6", "\"\\u00f6\"");
// test of a unicode character on its own.
TestStringGeneric("simple", "\"simple\"");
// test of a simple string that contains no escapes or anything complex.
TestStringGeneric("\t", "\"\\t\"");
// test of a single escape character.
TestStringGeneric("\007B", "\"B\"");
// contains an illegal character which should be ignored.
TestStringGeneric("X", "\"X\"");
// a simple string with a single character
}
/*static*/ void /*static*/ void
JsonTextWriterTest::AddTests(BTestSuite& parent) JsonTextWriterTest::AddTests(BTestSuite& parent)
{ {
@@ -191,6 +237,10 @@ JsonTextWriterTest::AddTests(BTestSuite& parent)
"JsonTextWriterTest::TestFalse", "JsonTextWriterTest::TestFalse",
&JsonTextWriterTest::TestFalse)); &JsonTextWriterTest::TestFalse));
suite.addTest(new CppUnit::TestCaller<JsonTextWriterTest>(
"JsonTextWriterTest::TestString",
&JsonTextWriterTest::TestString));
suite.addTest(new CppUnit::TestCaller<JsonTextWriterTest>( suite.addTest(new CppUnit::TestCaller<JsonTextWriterTest>(
"JsonTextWriterTest::TestArrayA", "JsonTextWriterTest::TestArrayA",
&JsonTextWriterTest::TestArrayA)); &JsonTextWriterTest::TestArrayA));
@@ -25,9 +25,12 @@ public:
void TestDouble(); void TestDouble();
void TestInteger(); void TestInteger();
void TestFalse(); void TestFalse();
void TestString();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);
private: private:
void TestStringGeneric(const char *input,
const char *expectedOut);
}; };