diff options
author | Matthias Clasen <mclasen@redhat.com> | 2021-12-05 21:09:44 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2021-12-05 21:17:03 -0500 |
commit | 63718feabe38dd29f88987493a47afb9f93e2b76 (patch) | |
tree | 430405f9ac627075be6988f0a10737a5f0434aa7 /pango/json | |
parent | 56174802a73d8855b7497d90f722b37233c6b477 (diff) | |
download | pango-63718feabe38dd29f88987493a47afb9f93e2b76.tar.gz |
Update the json parser
This includes better error reporting,
with error locations.
Diffstat (limited to 'pango/json')
-rw-r--r-- | pango/json/gtkjsonparser.c | 877 | ||||
-rw-r--r-- | pango/json/gtkjsonparserprivate.h | 36 |
2 files changed, 716 insertions, 197 deletions
diff --git a/pango/json/gtkjsonparser.c b/pango/json/gtkjsonparser.c index 91048fec..e82d974d 100644 --- a/pango/json/gtkjsonparser.c +++ b/pango/json/gtkjsonparser.c @@ -42,9 +42,12 @@ struct _GtkJsonParser { GBytes *bytes; const guchar *reader; /* current read head, pointing as far as we've read */ + const guchar *start; /* pointer at start of data, after optional BOM */ const guchar *end; /* pointer after end of data we're reading */ GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */ + const guchar *error_start; /* start of error location */ + const guchar *error_end; /* end of error location */ GtkJsonBlock *block; /* current block */ GtkJsonBlock *blocks; /* blocks array */ @@ -54,16 +57,17 @@ struct _GtkJsonParser typedef enum { WHITESPACE = (1 << 4), - STRING_ELEMENT = (1 << 5), - STRING_MARKER = (1 << 6), + NEWLINE = (1 << 5), + STRING_ELEMENT = (1 << 6), + STRING_MARKER = (1 << 7), } JsonCharacterType; #define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1) static const guchar json_character_table[256] = { ['\t'] = WHITESPACE, - ['\r'] = WHITESPACE, - ['\n'] = WHITESPACE, + ['\r'] = WHITESPACE | NEWLINE, + ['\n'] = WHITESPACE | NEWLINE, [' '] = WHITESPACE | STRING_ELEMENT, ['!'] = STRING_ELEMENT, ['"'] = GTK_JSON_STRING | STRING_MARKER, @@ -178,6 +182,21 @@ json_skip_characters (const guchar *start, } static const guchar * +json_skip_characters_until (const guchar *start, + const guchar *end, + JsonCharacterType type) +{ + const guchar *s; + + for (s = start; s < end; s++) + { + if (json_character_table[*s] & type) + break; + } + return s; +} + +static const guchar * json_find_character (const guchar *start, JsonCharacterType type) { @@ -191,14 +210,45 @@ json_find_character (const guchar *start, return s; } +GQuark +gtk_json_error_quark (void) +{ + return g_quark_from_static_string ("gtk-json-error-quark"); +} + static void -gtk_json_parser_value_error (GtkJsonParser *self, - const char *format, - ...) G_GNUC_PRINTF(2, 3); +gtk_json_parser_take_error (GtkJsonParser *self, + const guchar *start_location, + const guchar *end_location, + GError *error) +{ + g_assert (start_location <= end_location); + g_assert (self->start <= start_location); + g_assert (end_location <= self->end); + + if (self->error) + { + g_error_free (error); + return; + } + + self->error = error; + self->error_start = start_location; + self->error_end = end_location; +} + static void -gtk_json_parser_value_error (GtkJsonParser *self, - const char *format, - ...) +gtk_json_parser_syntax_error_at (GtkJsonParser *self, + const guchar *error_start, + const guchar *error_end, + const char *format, + ...) G_GNUC_PRINTF(4, 5); +static void +gtk_json_parser_syntax_error_at (GtkJsonParser *self, + const guchar *error_start, + const guchar *error_end, + const char *format, + ...) { va_list args; @@ -206,37 +256,144 @@ gtk_json_parser_value_error (GtkJsonParser *self, return; va_start (args, format); - self->error = g_error_new_valist (G_FILE_ERROR, - G_FILE_ERROR_FAILED, - format, args); + gtk_json_parser_take_error (self, + error_start, + error_end, + g_error_new_valist (GTK_JSON_ERROR, + GTK_JSON_ERROR_SYNTAX, + format, args)); va_end (args); } static void gtk_json_parser_syntax_error (GtkJsonParser *self, - const char *format, + const char *format, ...) G_GNUC_PRINTF(2, 3); static void gtk_json_parser_syntax_error (GtkJsonParser *self, - const char *format, + const char *format, ...) { va_list args; + const guchar *error_end; + + if (self->error) + return; + + va_start (args, format); + for (error_end = self->reader; + error_end < self->end && g_ascii_isalnum (*error_end); + error_end++) + ; + if (error_end == self->reader && + g_utf8_get_char_validated ((const char *) error_end, self->end - error_end) < (gunichar) -2) + { + error_end = (const guchar *) g_utf8_next_char (error_end); + } + + gtk_json_parser_take_error (self, + self->reader, + error_end, + g_error_new_valist (GTK_JSON_ERROR, + GTK_JSON_ERROR_SYNTAX, + format, args)); + va_end (args); +} + +static void +gtk_json_parser_type_error (GtkJsonParser *self, + const char *format, + ...) G_GNUC_PRINTF(2, 3); +static void +gtk_json_parser_type_error (GtkJsonParser *self, + const char *format, + ...) +{ + const guchar *start_location; + va_list args; if (self->error) return; + if (self->block->value) + start_location = self->block->value; + else if (self->block != self->blocks) + start_location = self->block[-1].value; + else + start_location = self->start; + va_start (args, format); - self->error = g_error_new_valist (G_FILE_ERROR, - G_FILE_ERROR_FAILED, - format, args); + gtk_json_parser_take_error (self, + start_location, + self->reader, + g_error_new_valist (GTK_JSON_ERROR, + GTK_JSON_ERROR_TYPE, + format, args)); + va_end (args); +} + +void +gtk_json_parser_value_error (GtkJsonParser *self, + const char *format, + ...) +{ + const guchar *start_location; + va_list args; + + if (self->error) + return; + + if (self->block->value) + start_location = self->block->value; + else if (self->block != self->blocks) + start_location = self->block[-1].value; + else + start_location = self->start; + + va_start (args, format); + gtk_json_parser_take_error (self, + start_location, + self->reader, + g_error_new_valist (GTK_JSON_ERROR, + GTK_JSON_ERROR_VALUE, + format, args)); + va_end (args); +} + +void +gtk_json_parser_schema_error (GtkJsonParser *self, + const char *format, + ...) +{ + const guchar *start_location; + va_list args; + + if (self->error) + return; + + if (self->block->member_name) + start_location = self->block->member_name; + if (self->block->value) + start_location = self->block->value; + else if (self->block != self->blocks) + start_location = self->block[-1].value; + else + start_location = self->start; + + va_start (args, format); + gtk_json_parser_take_error (self, + start_location, + self->reader, + g_error_new_valist (GTK_JSON_ERROR, + GTK_JSON_ERROR_SCHEMA, + format, args)); va_end (args); } static gboolean gtk_json_parser_is_eof (GtkJsonParser *self) { - return self->reader >= self->end || *self->reader == '\0'; + return self->reader >= self->end; } static gsize @@ -248,6 +405,18 @@ gtk_json_parser_remaining (GtkJsonParser *self) } static void +gtk_json_parser_skip_bom (GtkJsonParser *self) +{ + if (gtk_json_parser_remaining (self) < 3) + return; + + if (self->reader[0] == 0xEF && + self->reader[1] == 0xBB && + self->reader[2] == 0xBF) + self->reader += 3; +} + +static void gtk_json_parser_skip_whitespace (GtkJsonParser *self) { self->reader = json_skip_characters (self->reader, self->end, WHITESPACE); @@ -375,6 +544,58 @@ gtk_json_unescape_char (const guchar *json_escape, } } +typedef struct _JsonStringIter JsonStringIter; +struct _JsonStringIter +{ + char buf[6]; + const guchar *s; + const guchar *next; +}; + +static gsize +json_string_iter_next (JsonStringIter *iter) +{ + gsize len; + + iter->s = iter->next; + iter->next = json_find_character (iter->s, STRING_MARKER); + if (iter->next != iter->s) + return iter->next - iter->s; + if (*iter->next == '"') + return 0; + iter->next += gtk_json_unescape_char (iter->next, iter->buf, &len); + iter->s = (const guchar *) iter->buf; + return len; +} + +/* The escaped string MUST be valid json, so it must begin + * with " and end with " and must not contain any invalid + * escape codes. + * This function is meant to be fast + */ +static gsize +json_string_iter_init (JsonStringIter *iter, + const guchar *string) +{ + g_assert (*string == '"'); + + iter->next = string + 1; + + return json_string_iter_next (iter); +} + +static gboolean +json_string_iter_has_next (JsonStringIter *iter) +{ + return *iter->next != '"'; +} + +static const char * +json_string_iter_get (JsonStringIter *iter) +{ + return (const char *) iter->s; +} + /* The escaped string MUST be valid json, so it must begin * with " and end with " and must not contain any invalid * escape codes. @@ -383,44 +604,37 @@ gtk_json_unescape_char (const guchar *json_escape, static char * gtk_json_unescape_string (const guchar *escaped) { - char buf[6]; - gsize buf_size; + JsonStringIter iter; GString *string; - const guchar *last, *s; + gsize len; + len = json_string_iter_init (&iter, escaped); string = NULL; - g_assert (*escaped == '"'); - last = escaped + 1; - for (s = json_find_character (last, STRING_MARKER); - *s != '"'; - s = json_find_character (last, STRING_MARKER)) - { - g_assert (*s == '\\'); - if (string == NULL) - string = g_string_new (NULL); - g_string_append_len (string, (const char *) last, s - last); - last = s + gtk_json_unescape_char (s, buf, &buf_size); - g_string_append_len (string, buf, buf_size); - } + if (!json_string_iter_has_next (&iter)) + return g_strndup (json_string_iter_get (&iter), len); - if (string) - { - g_string_append_len (string, (const char *) last, s - last); - return g_string_free (string, FALSE); - } - else + string = g_string_new (NULL); + + do { - return g_strndup ((const char *) last, s - last); + g_string_append_len (string, json_string_iter_get (&iter), len); } + while ((len = json_string_iter_next (&iter))); + + return g_string_free (string, FALSE); } static gboolean gtk_json_parser_parse_string (GtkJsonParser *self) { + const guchar *start; + + start = self->reader; + if (!gtk_json_parser_try_char (self, '"')) { - gtk_json_parser_syntax_error (self, "Not a string"); + gtk_json_parser_type_error (self, "Not a string"); return FALSE; } @@ -430,7 +644,12 @@ gtk_json_parser_parse_string (GtkJsonParser *self) { if (*self->reader < 0x20) { - gtk_json_parser_syntax_error (self, "Disallowed control character in string literal"); + if (*self->reader == '\r' || *self->reader == '\n') + gtk_json_parser_syntax_error (self, "Newlines in strings are not allowed"); + else if (*self->reader == '\t') + gtk_json_parser_syntax_error (self, "Tabs not allowed in strings"); + else + gtk_json_parser_syntax_error (self, "Disallowed control character in string literal"); return FALSE; } else if (*self->reader > 127) @@ -451,9 +670,11 @@ gtk_json_parser_parse_string (GtkJsonParser *self) else if (*self->reader == '\\') { if (gtk_json_parser_remaining (self) < 2) - goto end; - self->reader++; - switch (*self->reader) + { + self->reader = self->end; + goto end; + } + switch (self->reader[1]) { case '"': case '\\': @@ -467,83 +688,127 @@ gtk_json_parser_parse_string (GtkJsonParser *self) case 'u': /* lots of work necessary to validate the unicode escapes here */ - if (gtk_json_parser_remaining (self) < 5 || - !g_ascii_isxdigit (self->reader[1]) || + if (gtk_json_parser_remaining (self) < 6 || !g_ascii_isxdigit (self->reader[2]) || !g_ascii_isxdigit (self->reader[3]) || - !g_ascii_isxdigit (self->reader[4])) + !g_ascii_isxdigit (self->reader[4]) || + !g_ascii_isxdigit (self->reader[5])) { - gtk_json_parser_syntax_error (self, "Invalid Unicode escape sequence"); + const guchar *end; + for (end = self->reader + 2; + end < self->reader + 6 && end < self->end; + end++) + { + if (!g_ascii_isxdigit (*end)) + break; + } + gtk_json_parser_syntax_error_at (self, self->reader, end, "Invalid Unicode escape sequence"); return FALSE; } else { - gunichar unichar = (g_ascii_xdigit_value (self->reader[1]) << 12) | - (g_ascii_xdigit_value (self->reader[2]) << 8) | - (g_ascii_xdigit_value (self->reader[3]) << 4) | - (g_ascii_xdigit_value (self->reader[4])); + gsize escape_size = 6; + gunichar unichar = (g_ascii_xdigit_value (self->reader[2]) << 12) | + (g_ascii_xdigit_value (self->reader[3]) << 8) | + (g_ascii_xdigit_value (self->reader[4]) << 4) | + (g_ascii_xdigit_value (self->reader[5])); - self->reader += 4; /* resolve UTF-16 surrogates for Unicode characters not in the BMP, * as per ECMA 404, ยง 9, "String" */ if (g_unichar_type (unichar) == G_UNICODE_SURROGATE) { - if (gtk_json_parser_remaining (self) >= 7 && - self->reader[1] == '\\' && - self->reader[2] == 'u' && - g_ascii_isxdigit (self->reader[3]) && - g_ascii_isxdigit (self->reader[4]) && - g_ascii_isxdigit (self->reader[5]) && - g_ascii_isxdigit (self->reader[6])) + if (gtk_json_parser_remaining (self) >= 12 && + self->reader[6] == '\\' && + self->reader[7] == 'u' && + g_ascii_isxdigit (self->reader[8]) && + g_ascii_isxdigit (self->reader[9]) && + g_ascii_isxdigit (self->reader[10]) && + g_ascii_isxdigit (self->reader[11])) { unichar = decode_utf16_surrogate_pair (unichar, - (g_ascii_xdigit_value (self->reader[3]) << 12) | - (g_ascii_xdigit_value (self->reader[4]) << 8) | - (g_ascii_xdigit_value (self->reader[5]) << 4) | - (g_ascii_xdigit_value (self->reader[6]))); - self->reader += 6; + (g_ascii_xdigit_value (self->reader[8]) << 12) | + (g_ascii_xdigit_value (self->reader[9]) << 8) | + (g_ascii_xdigit_value (self->reader[10]) << 4) | + (g_ascii_xdigit_value (self->reader[11]))); + escape_size += 6; } else { unichar = 0; } - } - if (unichar == 0) - { - gtk_json_parser_syntax_error (self, "Invalid UTF-16 surrogate pair"); - return FALSE; + if (unichar == 0) + { + gtk_json_parser_syntax_error_at (self, self->reader, self->reader + escape_size, "Invalid UTF-16 surrogate pair"); + return FALSE; + } + + self->reader += escape_size - 2; } } break; default: - gtk_json_parser_syntax_error (self, "Unknown escape sequence"); + if (g_utf8_get_char_validated ((const char *) self->reader + 1, self->end - self->reader - 1) < (gunichar) -2) + gtk_json_parser_syntax_error_at (self, self->reader, (const guchar *) g_utf8_next_char (self->reader + 1), "Unknown escape sequence"); + else + gtk_json_parser_syntax_error_at (self, self->reader, self->reader + 1, "Unknown escape sequence"); return FALSE; } - self->reader++; + self->reader += 2; } self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT); } end: - gtk_json_parser_syntax_error (self, "Unterminated string literal"); + gtk_json_parser_syntax_error_at (self, start, self->reader, "Unterminated string literal"); return FALSE; } static gboolean gtk_json_parser_parse_number (GtkJsonParser *self) { + const guchar *start = self->reader; + gboolean have_sign; + /* sign */ - gtk_json_parser_try_char (self, '-'); + have_sign = gtk_json_parser_try_char (self, '-'); /* integer part */ - if (!gtk_json_parser_try_char (self, '0')) + if (gtk_json_parser_try_char (self, '0')) + { + /* Technically, "01" in the JSON grammar would be 2 numbers: + * "0" followed by "1". + * Practically, nobody understands that it's 2 numbers, so we + * special-purpose an error message for it, because 2 numbers + * can never follow each other. + */ + if (!gtk_json_parser_is_eof (self) && + g_ascii_isdigit (*self->reader)) + { + do + { + self->reader++; + } + while (!gtk_json_parser_is_eof (self) && + g_ascii_isdigit (*self->reader)); + + gtk_json_parser_syntax_error_at (self, start, self->reader, "Numbers may not start with leading 0s"); + return FALSE; + } + } + else { if (gtk_json_parser_is_eof (self) || !g_ascii_isdigit (*self->reader)) - goto out; + { + if (have_sign) + gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a number after '-' character"); + else + gtk_json_parser_type_error (self, "Not a number"); + return FALSE; + } self->reader++; @@ -552,29 +817,41 @@ gtk_json_parser_parse_number (GtkJsonParser *self) } /* fractional part */ - if (gtk_json_parser_remaining (self) >= 2 && *self->reader == '.' && g_ascii_isdigit (self->reader[1])) + if (gtk_json_parser_try_char (self, '.')) { - self->reader += 2; + if (!g_ascii_isdigit (*self->reader)) + { + gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit after '.'"); + return FALSE; + } - while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)) - self->reader++; + do + { + self->reader++; + } + while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)); } /* exponent */ - if (gtk_json_parser_remaining (self) >= 2 && (self->reader[0] == 'e' || self->reader[0] == 'E') && - (g_ascii_isdigit (self->reader[1]) || - (gtk_json_parser_remaining (self) >= 3 && (self->reader[1] == '+' || self->reader[1] == '-') && g_ascii_isdigit (self->reader[2])))) + if (gtk_json_parser_try_char (self, 'e') || + gtk_json_parser_try_char (self, 'E')) { - self->reader += 2; + if (!gtk_json_parser_try_char (self, '-')) + gtk_json_parser_try_char (self, '+'); - while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)) - self->reader++; + if (!g_ascii_isdigit (*self->reader)) + { + gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit in exponent"); + return FALSE; + } + + do + { + self->reader++; + } + while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)); } return TRUE; - -out: - gtk_json_parser_syntax_error (self, "Not a valid number"); - return FALSE; } static gboolean @@ -614,7 +891,19 @@ gtk_json_parser_parse_value (GtkJsonParser *self) break; } - gtk_json_parser_syntax_error (self, "Expected a value"); + if (gtk_json_parser_remaining (self) >= 2 && + (self->block->value[0] == '.' || self->block->value[0] == '+') && + g_ascii_isdigit (self->block->value[1])) + { + const guchar *end = self->block->value + 3; + while (end < self->end && g_ascii_isalnum (*end)) + end++; + gtk_json_parser_syntax_error_at (self, self->block->value, end, "Numbers may not start with '%c'", *self->block->value); + } + else if (*self->reader == 0) + gtk_json_parser_syntax_error (self, "Unexpected nul byte in document"); + else + gtk_json_parser_syntax_error (self, "Expected a value"); return FALSE; } @@ -689,9 +978,9 @@ gtk_json_parser_new_for_bytes (GBytes *bytes) self->block = self->blocks; self->block->type = GTK_JSON_BLOCK_TOPLEVEL; - gtk_json_parser_skip_whitespace (self); - self->block->value = self->reader; - gtk_json_parser_parse_value (self); + gtk_json_parser_skip_bom (self); + self->start = self->reader; + gtk_json_parser_rewind (self); return self; } @@ -702,8 +991,6 @@ gtk_json_parser_free (GtkJsonParser *self) if (self == NULL) return; - g_clear_error (&self->error); - g_bytes_unref (self->bytes); if (self->blocks != self->blocks_preallocated) @@ -715,24 +1002,40 @@ gtk_json_parser_free (GtkJsonParser *self) static gboolean gtk_json_parser_skip_block (GtkJsonParser *self) { + gsize depth; + if (self->reader != self->block->value) return TRUE; - if (*self->reader == '{') - { - return gtk_json_parser_start_object (self) && - gtk_json_parser_end (self); - } - else if (*self->reader == '[') - { - return gtk_json_parser_start_array (self) && - gtk_json_parser_end (self); - } - else + depth = gtk_json_parser_get_depth (self); + while (TRUE) { - g_assert_not_reached (); - return FALSE; + if (*self->reader == '{') + { + if (!gtk_json_parser_start_object (self)) + return FALSE; + } + else if (*self->reader == '[') + { + if (!gtk_json_parser_start_array (self)) + return FALSE; + } + + while (self->reader != self->block->value) + { + /* This should never be reentrant to this function or we might + * loop causing stack overflow */ + if (!gtk_json_parser_next (self)) + { + if (!gtk_json_parser_end (self)) + return FALSE; + if (depth >= gtk_json_parser_get_depth (self)) + return TRUE; + } + } } + + return TRUE; } gboolean @@ -757,14 +1060,14 @@ gtk_json_parser_next (GtkJsonParser *self) if (gtk_json_parser_is_eof (self)) { self->block->value = NULL; - if (gtk_json_parser_remaining (self)) - { - gtk_json_parser_syntax_error (self, "Unexpected nul byte in document"); - } + } + else if (*self->reader == 0) + { + gtk_json_parser_syntax_error (self, "Unexpected nul byte in document"); } else { - gtk_json_parser_syntax_error (self, "Data at end of document"); + gtk_json_parser_syntax_error_at (self, self->reader, self->end, "Data at end of document"); } return FALSE; @@ -772,7 +1075,10 @@ gtk_json_parser_next (GtkJsonParser *self) gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { - gtk_json_parser_syntax_error (self, "Unexpected end of document"); + gtk_json_parser_syntax_error_at (self, + self->block[-1].value, + self->reader, + "Unterminated object"); self->block->member_name = NULL; self->block->value = NULL; } @@ -788,6 +1094,11 @@ gtk_json_parser_next (GtkJsonParser *self) return FALSE; } gtk_json_parser_skip_whitespace (self); + if (!gtk_json_parser_has_char (self, '"')) + { + gtk_json_parser_syntax_error (self, "Expected a string for object member name"); + return FALSE; + } self->block->member_name = self->reader; if (!gtk_json_parser_parse_string (self)) @@ -809,7 +1120,10 @@ gtk_json_parser_next (GtkJsonParser *self) gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { - gtk_json_parser_syntax_error (self, "Unexpected end of document"); + gtk_json_parser_syntax_error_at (self, + self->block[-1].value, + self->reader, + "Unterminated array"); self->block->member_name = NULL; self->block->value = NULL; } @@ -839,6 +1153,52 @@ gtk_json_parser_next (GtkJsonParser *self) return TRUE; } +void +gtk_json_parser_rewind (GtkJsonParser *self) +{ + if (self->error) + return; + + switch (self->block->type) + { + case GTK_JSON_BLOCK_OBJECT: + gtk_json_parser_pop_block (self); + self->reader = self->block->value; + gtk_json_parser_start_object (self); + break; + + case GTK_JSON_BLOCK_ARRAY: + gtk_json_parser_pop_block (self); + self->reader = self->block->value; + gtk_json_parser_start_array (self); + break; + + case GTK_JSON_BLOCK_TOPLEVEL: + self->reader = self->start; + gtk_json_parser_skip_whitespace (self); + if (gtk_json_parser_is_eof (self)) + { + gtk_json_parser_syntax_error_at (self, self->start, self->reader, "Empty document"); + } + else + { + self->block->value = self->reader; + gtk_json_parser_parse_value (self); + } + break; + + default: + g_assert_not_reached (); + return; + } +} + +gsize +gtk_json_parser_get_depth (GtkJsonParser *self) +{ + return self->block - self->blocks; +} + GtkJsonNode gtk_json_parser_get_node (GtkJsonParser *self) { @@ -857,43 +1217,214 @@ gtk_json_parser_get_error (GtkJsonParser *self) return self->error; } -char * -gtk_json_parser_get_member_name (GtkJsonParser *self) +void +gtk_json_parser_get_error_offset (GtkJsonParser *self, + gsize *start, + gsize *end) +{ + const guchar *data; + + if (self->error == NULL) + { + if (start) + *start = 0; + if (end) + *end = 0; + return; + } + + data = g_bytes_get_data (self->bytes, NULL); + if (start) + *start = self->error_start - data; + if (end) + *end = self->error_end - data; +} + +void +gtk_json_parser_get_error_location (GtkJsonParser *self, + gsize *start_line, + gsize *start_line_bytes, + gsize *end_line, + gsize *end_line_bytes) +{ + const guchar *s, *line_start; + gsize lines; + + if (self->error == NULL) + { + if (start_line) + *start_line = 0; + if (start_line_bytes) + *start_line_bytes = 0; + if (end_line) + *end_line = 0; + if (end_line_bytes) + *end_line_bytes = 0; + return; + } + + line_start = self->start; + lines = 0; + + for (s = json_skip_characters_until (line_start, self->error_start, NEWLINE); + s < self->error_start; + s = json_skip_characters_until (line_start, self->error_start, NEWLINE)) + { + if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n') + s++; + lines++; + line_start = s + 1; + } + + if (start_line) + *start_line = lines; + if (start_line_bytes) + *start_line_bytes = s - line_start; + + if (end_line == NULL && end_line_bytes == NULL) + return; + + for (s = json_skip_characters_until (s, self->error_end, NEWLINE); + s < self->error_end; + s = json_skip_characters_until (line_start, self->error_end, NEWLINE)) + { + if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n') + s++; + lines++; + line_start = s + 1; + } + + if (end_line) + *end_line = lines; + if (end_line_bytes) + *end_line_bytes = s - line_start; +} + +static gboolean +gtk_json_parser_supports_member (GtkJsonParser *self) { if (self->error) - return NULL; + return FALSE; if (self->block->type != GTK_JSON_BLOCK_OBJECT) - return NULL; + return FALSE; if (self->block->member_name == NULL) + return FALSE; + + return TRUE; +} + +char * +gtk_json_parser_get_member_name (GtkJsonParser *self) +{ + if (!gtk_json_parser_supports_member (self)) return NULL; return gtk_json_unescape_string (self->block->member_name); } +gboolean +gtk_json_parser_has_member (GtkJsonParser *self, + const char *name) +{ + JsonStringIter iter; + gsize found, len; + + if (!gtk_json_parser_supports_member (self)) + return FALSE; + + found = 0; + + for (len = json_string_iter_init (&iter, self->block->member_name); + len > 0; + len = json_string_iter_next (&iter)) + { + const char *s = json_string_iter_get (&iter); + + if (strncmp (name + found, s, len) != 0) + return FALSE; + + found += len; + } + + return TRUE; +} + +gboolean +gtk_json_parser_find_member (GtkJsonParser *self, + const char *name) +{ + if (!gtk_json_parser_supports_member (self)) + { + while (gtk_json_parser_next (self)); + return FALSE; + } + + gtk_json_parser_rewind (self); + + do + { + if (gtk_json_parser_has_member (self, name)) + return TRUE; + } + while (gtk_json_parser_next (self)); + + return FALSE; +} + gssize gtk_json_parser_select_member (GtkJsonParser *self, const char * const *options) { - char *member_name; - gssize i; + JsonStringIter iter; + gssize i, j; + gsize found, len; - member_name = gtk_json_parser_get_member_name (self); - if (member_name == NULL) + if (!gtk_json_parser_supports_member (self)) return -1; - for (i = 0; options[i]; i++) + if (options[0] == NULL) + return -1; + + found = 0; + i = 0; + + for (len = json_string_iter_init (&iter, self->block->member_name); + len > 0; + len = json_string_iter_next (&iter)) { - if (strcmp (member_name, options[i]) == 0) - break; + const char *s = json_string_iter_get (&iter); + + if (strncmp (options[i] + found, s, len) != 0) + { + for (j = i + 1; options[j]; j++) + { + if (strncmp (options[j], options[i], found) == 0 && + strncmp (options[j] + found, s, len) == 0) + { + i = j; + break; + } + } + if (j != i) + return -1; + } + found += len; } - if (options[i] == NULL) - i = -1; - g_free (member_name); + if (options[i][found] == 0) + return i; + + for (j = i + 1; options[j]; i++) + { + if (strncmp (options[j], options[i], found) != 0) + continue; + if (options[j][found] == 0) + return j; + } - return i; + return -1; } gboolean @@ -910,7 +1441,7 @@ gtk_json_parser_get_boolean (GtkJsonParser *self) else if (*self->block->value == 'f') return FALSE; - gtk_json_parser_value_error (self, "Expected a boolean value"); + gtk_json_parser_type_error (self, "Expected a boolean value"); return FALSE; } @@ -927,7 +1458,7 @@ gtk_json_parser_get_number (GtkJsonParser *self) if (!strchr ("-0123456789", *self->block->value)) { - gtk_json_parser_value_error (self, "Expected a number"); + gtk_json_parser_type_error (self, "Expected a number"); return 0; } @@ -957,49 +1488,6 @@ gtk_json_parser_get_number (GtkJsonParser *self) return result; } -int -gtk_json_parser_get_int (GtkJsonParser *self) -{ - int result; - - if (self->error) - return 0; - - if (self->block->value == NULL) - return 0; - - if (!strchr ("-0123456789", *self->block->value)) - { - gtk_json_parser_value_error (self, "Expected a number"); - return 0; - } - - errno = 0; - if (gtk_json_parser_remaining (self) == 0) - { - /* need terminated string here */ - char *s = g_strndup ((const char *) self->block->value, self->reader - self->block->value); - result = g_ascii_strtod (s, NULL); - g_free (s); - } - else - { - result = (int) g_ascii_strtoll ((const char *) self->block->value, NULL, 10); - } - - if (errno) - { - if (errno == ERANGE) - gtk_json_parser_value_error (self, "Number out of range"); - else - gtk_json_parser_value_error (self, "%s", g_strerror (errno)); - - return 0; - } - - return result; -} - #if 0 int gtk_json_parser_get_int (GtkJsonParser *self); guint gtk_json_parser_get_uint (GtkJsonParser *self); @@ -1016,7 +1504,7 @@ gtk_json_parser_get_string (GtkJsonParser *self) if (*self->block->value != '"') { - gtk_json_parser_value_error (self, "Expected a string"); + gtk_json_parser_type_error (self, "Expected a string"); return g_strdup (""); } @@ -1031,7 +1519,7 @@ gtk_json_parser_start_object (GtkJsonParser *self) if (!gtk_json_parser_try_char (self, '{')) { - gtk_json_parser_value_error (self, "Expected an object"); + gtk_json_parser_type_error (self, "Expected an object"); return FALSE; } @@ -1040,11 +1528,20 @@ gtk_json_parser_start_object (GtkJsonParser *self) gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { - gtk_json_parser_syntax_error (self, "Unexpected end of document"); + gtk_json_parser_syntax_error_at (self, + self->block[-1].value, + self->reader, + "Unterminated object"); return FALSE; } if (gtk_json_parser_has_char (self, '}')) return TRUE; + + if (!gtk_json_parser_has_char (self, '"')) + { + gtk_json_parser_syntax_error (self, "Expected a string for object member name"); + return FALSE; + } self->block->member_name = self->reader; if (!gtk_json_parser_parse_string (self)) @@ -1072,7 +1569,7 @@ gtk_json_parser_start_array (GtkJsonParser *self) if (!gtk_json_parser_try_char (self, '[')) { - gtk_json_parser_value_error (self, "Expected an array"); + gtk_json_parser_type_error (self, "Expected an array"); return FALSE; } @@ -1080,7 +1577,10 @@ gtk_json_parser_start_array (GtkJsonParser *self) gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { - gtk_json_parser_syntax_error (self, "Unexpected end of document"); + gtk_json_parser_syntax_error_at (self, + self->block[-1].value, + self->reader, + "Unterminated array"); return FALSE; } if (gtk_json_parser_has_char (self, ']')) @@ -1131,12 +1631,3 @@ gtk_json_parser_end (GtkJsonParser *self) return TRUE; } -void -gtk_json_parser_set_error (GtkJsonParser *self, - GError *error) -{ - if (self->error) - g_error_free (error); - else - self->error = error; -} diff --git a/pango/json/gtkjsonparserprivate.h b/pango/json/gtkjsonparserprivate.h index 81c001cc..38f508ab 100644 --- a/pango/json/gtkjsonparserprivate.h +++ b/pango/json/gtkjsonparserprivate.h @@ -35,8 +35,19 @@ typedef enum { GTK_JSON_ARRAY } GtkJsonNode; +typedef enum { + GTK_JSON_ERROR_FAILED, + GTK_JSON_ERROR_SYNTAX, + GTK_JSON_ERROR_TYPE, + GTK_JSON_ERROR_VALUE, + GTK_JSON_ERROR_SCHEMA, +} GtkJsonError; + typedef struct _GtkJsonParser GtkJsonParser; +#define GTK_JSON_ERROR (gtk_json_error_quark ()) +GQuark gtk_json_error_quark (void); + GtkJsonParser * gtk_json_parser_new_for_bytes (GBytes *bytes); GtkJsonParser * gtk_json_parser_new_for_string (const char *string, gssize size); @@ -44,9 +55,14 @@ GtkJsonParser * gtk_json_parser_new_for_string (const char void gtk_json_parser_free (GtkJsonParser *self); gboolean gtk_json_parser_next (GtkJsonParser *self); +void gtk_json_parser_rewind (GtkJsonParser *self); +gsize gtk_json_parser_get_depth (GtkJsonParser *self); GtkJsonNode gtk_json_parser_get_node (GtkJsonParser *self); -const GError * gtk_json_parser_get_error (GtkJsonParser *self) G_GNUC_PURE; char * gtk_json_parser_get_member_name (GtkJsonParser *self); +gboolean gtk_json_parser_has_member (GtkJsonParser *self, + const char *name); +gboolean gtk_json_parser_find_member (GtkJsonParser *self, + const char *name); gssize gtk_json_parser_select_member (GtkJsonParser *self, const char * const *options); @@ -60,9 +76,21 @@ gboolean gtk_json_parser_start_object (GtkJsonParser gboolean gtk_json_parser_start_array (GtkJsonParser *self); gboolean gtk_json_parser_end (GtkJsonParser *self); -void gtk_json_parser_set_error (GtkJsonParser *self, - GError *error); - +const GError * gtk_json_parser_get_error (GtkJsonParser *self) G_GNUC_PURE; +void gtk_json_parser_get_error_offset (GtkJsonParser *self, + gsize *start, + gsize *end); +void gtk_json_parser_get_error_location (GtkJsonParser *self, + gsize *start_line, + gsize *start_line_bytes, + gsize *end_line, + gsize *end_line_bytes); +void gtk_json_parser_value_error (GtkJsonParser *self, + const char *format, + ...) G_GNUC_PRINTF(2, 3); +void gtk_json_parser_schema_error (GtkJsonParser *self, + const char *format, + ...) G_GNUC_PRINTF(2, 3); G_END_DECLS |