* Add escaping of backslash to json output
* Add escaping of foward slash on tokenizing and output
* Changes to internal tokenizer from using recursion to
using a depth state structure to allow incremental parsing
git-svn-id: http://svn.metaparadigm.com/svn/json-c/trunk@14 327403b1-1117-474d-bef2-5cb71233fd97
This commit is contained in:
13
COPYING
13
COPYING
@@ -10,9 +10,10 @@ Software is furnished to do so, subject to the following conditions:
|
|||||||
The above copyright notice and this permission notice shall be included
|
The above copyright notice and this permission notice shall be included
|
||||||
in all copies or substantial portions of the Software.
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
0.7
|
||||||
|
* Add escaping of backslash to json output
|
||||||
|
* Add escaping of foward slash on tokenizing and output
|
||||||
|
* Changes to internal tokenizer from using recursion to
|
||||||
|
using a depth state structure to allow incremental parsing
|
||||||
|
|
||||||
0.6
|
0.6
|
||||||
* Fix bug in escaping of control characters
|
* Fix bug in escaping of control characters
|
||||||
Johan Bj<42>rklund, johbjo09 at kth dot se
|
Johan Bj<42>rklund, johbjo09 at kth dot se
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
CFLAGS = -Wall -std=gnu99 -D_GNU_SOURCE -D_REENTRANT
|
AM_CFLAGS = -Wall -std=gnu99 -D_GNU_SOURCE -D_REENTRANT
|
||||||
|
|
||||||
lib_LTLIBRARIES = libjson.la
|
lib_LTLIBRARIES = libjson.la
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: json_object.c,v 1.15 2006/01/30 23:07:57 mclark Exp $
|
* $Id: json_object.c,v 1.17 2006/07/25 03:24:50 mclark Exp $
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
||||||
* Michael Clark <michael@metaparadigm.com>
|
* Michael Clark <michael@metaparadigm.com>
|
||||||
@@ -93,6 +93,8 @@ static int json_escape_str(struct printbuf *pb, char *str)
|
|||||||
case '\r':
|
case '\r':
|
||||||
case '\t':
|
case '\t':
|
||||||
case '"':
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
case '/':
|
||||||
if(pos - start_offset > 0)
|
if(pos - start_offset > 0)
|
||||||
printbuf_memappend(pb, str + start_offset, pos - start_offset);
|
printbuf_memappend(pb, str + start_offset, pos - start_offset);
|
||||||
if(c == '\b') printbuf_memappend(pb, "\\b", 2);
|
if(c == '\b') printbuf_memappend(pb, "\\b", 2);
|
||||||
@@ -100,6 +102,8 @@ static int json_escape_str(struct printbuf *pb, char *str)
|
|||||||
else if(c == '\r') printbuf_memappend(pb, "\\r", 2);
|
else if(c == '\r') printbuf_memappend(pb, "\\r", 2);
|
||||||
else if(c == '\t') printbuf_memappend(pb, "\\t", 2);
|
else if(c == '\t') printbuf_memappend(pb, "\\t", 2);
|
||||||
else if(c == '"') printbuf_memappend(pb, "\\\"", 2);
|
else if(c == '"') printbuf_memappend(pb, "\\\"", 2);
|
||||||
|
else if(c == '\\') printbuf_memappend(pb, "\\\\", 2);
|
||||||
|
else if(c == '/') printbuf_memappend(pb, "\\/", 2);
|
||||||
start_offset = ++pos;
|
start_offset = ++pos;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
375
json_tokener.c
375
json_tokener.c
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: json_tokener.c,v 1.19 2006/01/30 23:07:57 mclark Exp $
|
* $Id: json_tokener.c,v 1.20 2006/07/25 03:24:50 mclark Exp $
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
||||||
* Michael Clark <michael@metaparadigm.com>
|
* Michael Clark <michael@metaparadigm.com>
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "json_object.h"
|
#include "json_object.h"
|
||||||
#include "json_tokener.h"
|
#include "json_tokener.h"
|
||||||
|
|
||||||
|
|
||||||
#if !HAVE_STRNCASECMP && defined(_MSC_VER)
|
#if !HAVE_STRNCASECMP && defined(_MSC_VER)
|
||||||
/* MSC has the version as _strnicmp */
|
/* MSC has the version as _strnicmp */
|
||||||
# define strncasecmp _strnicmp
|
# define strncasecmp _strnicmp
|
||||||
@@ -31,21 +32,76 @@
|
|||||||
#endif /* HAVE_STRNCASECMP */
|
#endif /* HAVE_STRNCASECMP */
|
||||||
|
|
||||||
|
|
||||||
static struct json_object* json_tokener_do_parse(struct json_tokener *this);
|
static const char* json_null_str = "null";
|
||||||
|
static const char* json_true_str = "true";
|
||||||
|
static const char* json_false_str = "false";
|
||||||
|
|
||||||
struct json_object* json_tokener_parse(char * s)
|
const char* json_tokener_errors[] = {
|
||||||
|
"success",
|
||||||
|
"continue",
|
||||||
|
"nesting to deep",
|
||||||
|
"unexpected end of data",
|
||||||
|
"unexpected character",
|
||||||
|
"null expected",
|
||||||
|
"boolean expected",
|
||||||
|
"number expected",
|
||||||
|
"array value separator ',' expected",
|
||||||
|
"quoted object property name expected",
|
||||||
|
"object property name separator ':' expected",
|
||||||
|
"object value separator ',' expected",
|
||||||
|
"invalid string sequence",
|
||||||
|
"expected comment",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct json_tokener* json_tokener_new()
|
||||||
{
|
{
|
||||||
struct json_tokener tok;
|
struct json_tokener *tok = calloc(1, sizeof(struct json_tokener));
|
||||||
|
tok->pb = printbuf_new();
|
||||||
|
json_tokener_reset(tok);
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void json_tokener_free(struct json_tokener *tok)
|
||||||
|
{
|
||||||
|
json_tokener_reset(tok);
|
||||||
|
if(tok) printbuf_free(tok->pb);
|
||||||
|
free(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void json_tokener_reset_level(struct json_tokener *tok, int depth)
|
||||||
|
{
|
||||||
|
tok->stack[depth].state = json_tokener_state_eatws;
|
||||||
|
tok->stack[depth].saved_state = json_tokener_state_start;
|
||||||
|
json_object_put(tok->stack[depth].current);
|
||||||
|
tok->stack[depth].current = NULL;
|
||||||
|
free(tok->stack[depth].obj_field_name);
|
||||||
|
tok->stack[depth].obj_field_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void json_tokener_reset(struct json_tokener *tok)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i = tok->depth; i >= 0; i--)
|
||||||
|
json_tokener_reset_level(tok, i);
|
||||||
|
tok->depth = 0;
|
||||||
|
tok->err = json_tokener_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object* json_tokener_parse(char *str)
|
||||||
|
{
|
||||||
|
struct json_tokener* tok;
|
||||||
struct json_object* obj;
|
struct json_object* obj;
|
||||||
|
|
||||||
tok.source = s;
|
tok = json_tokener_new();
|
||||||
tok.pos = 0;
|
obj = json_tokener_parse_ex(tok, str, -1);
|
||||||
tok.pb = printbuf_new();
|
if(tok->err != json_tokener_success)
|
||||||
obj = json_tokener_do_parse(&tok);
|
obj = error_ptr(-tok->err);
|
||||||
printbuf_free(tok.pb);
|
json_tokener_free(tok);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if !HAVE_STRNDUP
|
#if !HAVE_STRNDUP
|
||||||
/* CAW: compliant version of strndup() */
|
/* CAW: compliant version of strndup() */
|
||||||
char* strndup(const char* str, size_t n)
|
char* strndup(const char* str, size_t n)
|
||||||
@@ -67,31 +123,45 @@ char* strndup(const char* str, size_t n)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static struct json_object* json_tokener_do_parse(struct json_tokener *this)
|
|
||||||
|
#define state tok->stack[tok->depth].state
|
||||||
|
#define saved_state tok->stack[tok->depth].saved_state
|
||||||
|
#define current tok->stack[tok->depth].current
|
||||||
|
#define obj_field_name tok->stack[tok->depth].obj_field_name
|
||||||
|
|
||||||
|
struct json_object* json_tokener_parse_ex(struct json_tokener *tok,
|
||||||
|
char *str, int len)
|
||||||
{
|
{
|
||||||
enum json_tokener_state state, saved_state;
|
struct json_object *obj = NULL;
|
||||||
enum json_tokener_error err = json_tokener_success;
|
|
||||||
struct json_object *current = NULL, *obj;
|
|
||||||
char *obj_field_name = NULL;
|
|
||||||
char quote_char;
|
|
||||||
int deemed_double, start_offset;
|
|
||||||
char c;
|
char c;
|
||||||
|
|
||||||
state = json_tokener_state_eatws;
|
tok->char_offset = 0;
|
||||||
saved_state = json_tokener_state_start;
|
tok->err = json_tokener_success;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
c = this->source[this->pos];
|
if(tok->char_offset == len) {
|
||||||
|
if(tok->depth == 0 && state == json_tokener_state_eatws &&
|
||||||
|
saved_state == json_tokener_state_finish)
|
||||||
|
tok->err = json_tokener_success;
|
||||||
|
else
|
||||||
|
tok->err = json_tokener_continue;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = *str;
|
||||||
|
redo_char:
|
||||||
switch(state) {
|
switch(state) {
|
||||||
|
|
||||||
case json_tokener_state_eatws:
|
case json_tokener_state_eatws:
|
||||||
if(isspace(c)) {
|
if(isspace(c)) {
|
||||||
this->pos++;
|
/* okay */
|
||||||
} else if(c == '/') {
|
} else if(c == '/') {
|
||||||
|
printbuf_reset(tok->pb);
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
state = json_tokener_state_comment_start;
|
state = json_tokener_state_comment_start;
|
||||||
start_offset = this->pos++;
|
|
||||||
} else {
|
} else {
|
||||||
state = saved_state;
|
state = saved_state;
|
||||||
|
goto redo_char;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -99,35 +169,34 @@ static struct json_object* json_tokener_do_parse(struct json_tokener *this)
|
|||||||
switch(c) {
|
switch(c) {
|
||||||
case '{':
|
case '{':
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
saved_state = json_tokener_state_object;
|
saved_state = json_tokener_state_object_field_start;
|
||||||
current = json_object_new_object();
|
current = json_object_new_object();
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
case '[':
|
case '[':
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
saved_state = json_tokener_state_array;
|
saved_state = json_tokener_state_array;
|
||||||
current = json_object_new_array();
|
current = json_object_new_array();
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
case 'n':
|
case 'n':
|
||||||
state = json_tokener_state_null;
|
state = json_tokener_state_null;
|
||||||
start_offset = this->pos++;
|
printbuf_reset(tok->pb);
|
||||||
break;
|
tok->st_pos = 0;
|
||||||
|
goto redo_char;
|
||||||
case '"':
|
case '"':
|
||||||
case '\'':
|
case '\'':
|
||||||
quote_char = c;
|
|
||||||
printbuf_reset(this->pb);
|
|
||||||
state = json_tokener_state_string;
|
state = json_tokener_state_string;
|
||||||
start_offset = ++this->pos;
|
printbuf_reset(tok->pb);
|
||||||
|
tok->quote_char = c;
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'T':
|
||||||
case 't':
|
case 't':
|
||||||
case 'F':
|
case 'F':
|
||||||
case 'f':
|
case 'f':
|
||||||
state = json_tokener_state_boolean;
|
state = json_tokener_state_boolean;
|
||||||
start_offset = this->pos++;
|
printbuf_reset(tok->pb);
|
||||||
break;
|
tok->st_pos = 0;
|
||||||
|
goto redo_char;
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
case '0' ... '9':
|
case '0' ... '9':
|
||||||
#else
|
#else
|
||||||
@@ -143,30 +212,38 @@ static struct json_object* json_tokener_do_parse(struct json_tokener *this)
|
|||||||
case '9':
|
case '9':
|
||||||
#endif
|
#endif
|
||||||
case '-':
|
case '-':
|
||||||
deemed_double = 0;
|
|
||||||
state = json_tokener_state_number;
|
state = json_tokener_state_number;
|
||||||
start_offset = this->pos++;
|
printbuf_reset(tok->pb);
|
||||||
break;
|
tok->is_double = 0;
|
||||||
|
goto redo_char;
|
||||||
default:
|
default:
|
||||||
err = json_tokener_error_parse_unexpected;
|
tok->err = json_tokener_error_parse_unexpected;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_finish:
|
case json_tokener_state_finish:
|
||||||
goto out;
|
if(tok->depth == 0) goto out;
|
||||||
|
obj = json_object_get(current);
|
||||||
|
json_tokener_reset_level(tok, tok->depth);
|
||||||
|
tok->depth--;
|
||||||
|
goto redo_char;
|
||||||
|
|
||||||
case json_tokener_state_null:
|
case json_tokener_state_null:
|
||||||
if(strncasecmp("null", this->source + start_offset,
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
this->pos - start_offset))
|
if(strncasecmp(json_null_str, tok->pb->buf,
|
||||||
return error_ptr(-json_tokener_error_parse_null);
|
min(tok->st_pos+1, strlen(json_null_str))) == 0) {
|
||||||
if(this->pos - start_offset == 4) {
|
if(tok->st_pos == strlen(json_null_str)) {
|
||||||
current = NULL;
|
current = NULL;
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
goto redo_char;
|
||||||
this->pos++;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tok->err = json_tokener_error_parse_null;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
tok->st_pos++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_comment_start:
|
case json_tokener_state_comment_start:
|
||||||
@@ -175,291 +252,265 @@ static struct json_object* json_tokener_do_parse(struct json_tokener *this)
|
|||||||
} else if(c == '/') {
|
} else if(c == '/') {
|
||||||
state = json_tokener_state_comment_eol;
|
state = json_tokener_state_comment_eol;
|
||||||
} else {
|
} else {
|
||||||
err = json_tokener_error_parse_comment;
|
tok->err = json_tokener_error_parse_comment;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
this->pos++;
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_comment:
|
case json_tokener_state_comment:
|
||||||
if(c == '*') state = json_tokener_state_comment_end;
|
if(c == '*') state = json_tokener_state_comment_end;
|
||||||
this->pos++;
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_comment_eol:
|
case json_tokener_state_comment_eol:
|
||||||
if(c == '\n') {
|
if(c == '\n') {
|
||||||
if(mc_get_debug()) {
|
mc_debug("json_tokener_comment: %s\n", tok->pb->buf);
|
||||||
char *tmp = strndup(this->source + start_offset,
|
|
||||||
this->pos - start_offset);
|
|
||||||
mc_debug("json_tokener_comment: %s\n", tmp);
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
|
} else {
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
}
|
}
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_comment_end:
|
case json_tokener_state_comment_end:
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
if(c == '/') {
|
if(c == '/') {
|
||||||
if(mc_get_debug()) {
|
mc_debug("json_tokener_comment: %s\n", tok->pb->buf);
|
||||||
char *tmp = strndup(this->source + start_offset,
|
|
||||||
this->pos - start_offset + 1);
|
|
||||||
mc_debug("json_tokener_comment: %s\n", tmp);
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
} else {
|
||||||
state = json_tokener_state_comment;
|
state = json_tokener_state_comment;
|
||||||
}
|
}
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_string:
|
case json_tokener_state_string:
|
||||||
if(c == quote_char) {
|
if(c == tok->quote_char) {
|
||||||
printbuf_memappend(this->pb, this->source + start_offset,
|
current = json_object_new_string(tok->pb->buf);
|
||||||
this->pos - start_offset);
|
|
||||||
current = json_object_new_string(this->pb->buf);
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else if(c == '\\') {
|
} else if(c == '\\') {
|
||||||
saved_state = json_tokener_state_string;
|
saved_state = json_tokener_state_string;
|
||||||
state = json_tokener_state_string_escape;
|
state = json_tokener_state_string_escape;
|
||||||
|
} else {
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
}
|
}
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_string_escape:
|
case json_tokener_state_string_escape:
|
||||||
switch(c) {
|
switch(c) {
|
||||||
case '"':
|
case '"':
|
||||||
case '\\':
|
case '\\':
|
||||||
printbuf_memappend(this->pb, this->source + start_offset,
|
case '/':
|
||||||
this->pos - start_offset - 1);
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
start_offset = this->pos++;
|
|
||||||
state = saved_state;
|
state = saved_state;
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
case 'n':
|
case 'n':
|
||||||
case 'r':
|
case 'r':
|
||||||
case 't':
|
case 't':
|
||||||
printbuf_memappend(this->pb, this->source + start_offset,
|
if(c == 'b') printbuf_memappend(tok->pb, "\b", 1);
|
||||||
this->pos - start_offset - 1);
|
else if(c == 'n') printbuf_memappend(tok->pb, "\n", 1);
|
||||||
if(c == 'b') printbuf_memappend(this->pb, "\b", 1);
|
else if(c == 'r') printbuf_memappend(tok->pb, "\r", 1);
|
||||||
else if(c == 'n') printbuf_memappend(this->pb, "\n", 1);
|
else if(c == 't') printbuf_memappend(tok->pb, "\t", 1);
|
||||||
else if(c == 'r') printbuf_memappend(this->pb, "\r", 1);
|
|
||||||
else if(c == 't') printbuf_memappend(this->pb, "\t", 1);
|
|
||||||
start_offset = ++this->pos;
|
|
||||||
state = saved_state;
|
state = saved_state;
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
printbuf_memappend(this->pb, this->source + start_offset,
|
tok->ucs_char = 0;
|
||||||
this->pos - start_offset - 1);
|
tok->st_pos = 0;
|
||||||
start_offset = ++this->pos;
|
|
||||||
state = json_tokener_state_escape_unicode;
|
state = json_tokener_state_escape_unicode;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
err = json_tokener_error_parse_string;
|
tok->err = json_tokener_error_parse_string;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_escape_unicode:
|
case json_tokener_state_escape_unicode:
|
||||||
if(strchr(json_hex_chars, c)) {
|
if(strchr(json_hex_chars, c)) {
|
||||||
this->pos++;
|
tok->ucs_char += ((unsigned int)hexdigit(c) << ((3-tok->st_pos++)*4));
|
||||||
if(this->pos - start_offset == 4) {
|
if(tok->st_pos == 4) {
|
||||||
unsigned char utf_out[3];
|
unsigned char utf_out[3];
|
||||||
unsigned int ucs_char =
|
if (tok->ucs_char < 0x80) {
|
||||||
(hexdigit(*(this->source + start_offset)) << 12) +
|
utf_out[0] = tok->ucs_char;
|
||||||
(hexdigit(*(this->source + start_offset + 1)) << 8) +
|
printbuf_memappend(tok->pb, (char*)utf_out, 1);
|
||||||
(hexdigit(*(this->source + start_offset + 2)) << 4) +
|
} else if (tok->ucs_char < 0x800) {
|
||||||
hexdigit(*(this->source + start_offset + 3));
|
utf_out[0] = 0xc0 | (tok->ucs_char >> 6);
|
||||||
if (ucs_char < 0x80) {
|
utf_out[1] = 0x80 | (tok->ucs_char & 0x3f);
|
||||||
utf_out[0] = ucs_char;
|
printbuf_memappend(tok->pb, (char*)utf_out, 2);
|
||||||
printbuf_memappend(this->pb, (char*)utf_out, 1);
|
|
||||||
} else if (ucs_char < 0x800) {
|
|
||||||
utf_out[0] = 0xc0 | (ucs_char >> 6);
|
|
||||||
utf_out[1] = 0x80 | (ucs_char & 0x3f);
|
|
||||||
printbuf_memappend(this->pb, (char*)utf_out, 2);
|
|
||||||
} else {
|
} else {
|
||||||
utf_out[0] = 0xe0 | (ucs_char >> 12);
|
utf_out[0] = 0xe0 | (tok->ucs_char >> 12);
|
||||||
utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
|
utf_out[1] = 0x80 | ((tok->ucs_char >> 6) & 0x3f);
|
||||||
utf_out[2] = 0x80 | (ucs_char & 0x3f);
|
utf_out[2] = 0x80 | (tok->ucs_char & 0x3f);
|
||||||
printbuf_memappend(this->pb, (char*)utf_out, 3);
|
printbuf_memappend(tok->pb, (char*)utf_out, 3);
|
||||||
}
|
}
|
||||||
start_offset = this->pos;
|
|
||||||
state = saved_state;
|
state = saved_state;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = json_tokener_error_parse_string;
|
tok->err = json_tokener_error_parse_string;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_boolean:
|
case json_tokener_state_boolean:
|
||||||
if(strncasecmp("true", this->source + start_offset,
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
this->pos - start_offset) == 0) {
|
if(strncasecmp(json_true_str, tok->pb->buf,
|
||||||
if(this->pos - start_offset == 4) {
|
min(tok->st_pos+1, strlen(json_true_str))) == 0) {
|
||||||
|
if(tok->st_pos == strlen(json_true_str)) {
|
||||||
current = json_object_new_boolean(1);
|
current = json_object_new_boolean(1);
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
goto redo_char;
|
||||||
this->pos++;
|
|
||||||
}
|
}
|
||||||
} else if(strncasecmp("false", this->source + start_offset,
|
} else if(strncasecmp(json_false_str, tok->pb->buf,
|
||||||
this->pos - start_offset) == 0) {
|
min(tok->st_pos+1, strlen(json_false_str))) == 0) {
|
||||||
if(this->pos - start_offset == 5) {
|
if(tok->st_pos == strlen(json_false_str)) {
|
||||||
current = json_object_new_boolean(0);
|
current = json_object_new_boolean(0);
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
goto redo_char;
|
||||||
this->pos++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = json_tokener_error_parse_boolean;
|
tok->err = json_tokener_error_parse_boolean;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
tok->st_pos++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_number:
|
case json_tokener_state_number:
|
||||||
if(!c || !strchr(json_number_chars, c)) {
|
if(c && strchr(json_number_chars, c)) {
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
|
if(c == '.' || c == 'e') tok->is_double = 1;
|
||||||
|
} else {
|
||||||
int numi;
|
int numi;
|
||||||
double numd;
|
double numd;
|
||||||
char *tmp = strndup(this->source + start_offset,
|
if(!tok->is_double && sscanf(tok->pb->buf, "%d", &numi) == 1) {
|
||||||
this->pos - start_offset);
|
|
||||||
if(!deemed_double && sscanf(tmp, "%d", &numi) == 1) {
|
|
||||||
current = json_object_new_int(numi);
|
current = json_object_new_int(numi);
|
||||||
} else if(deemed_double && sscanf(tmp, "%lf", &numd) == 1) {
|
} else if(tok->is_double && sscanf(tok->pb->buf, "%lf", &numd) == 1) {
|
||||||
current = json_object_new_double(numd);
|
current = json_object_new_double(numd);
|
||||||
} else {
|
} else {
|
||||||
free(tmp);
|
tok->err = json_tokener_error_parse_number;
|
||||||
err = json_tokener_error_parse_number;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
free(tmp);
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
goto redo_char;
|
||||||
if(c == '.' || c == 'e') deemed_double = 1;
|
|
||||||
this->pos++;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_array:
|
case json_tokener_state_array:
|
||||||
if(c == ']') {
|
if(c == ']') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
} else {
|
||||||
obj = json_tokener_do_parse(this);
|
if(tok->depth >= JSON_TOKENER_MAX_DEPTH-1) {
|
||||||
if(is_error(obj)) {
|
tok->err = json_tokener_error_depth;
|
||||||
err = -(enum json_tokener_error)obj;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
state = json_tokener_state_array_add;
|
||||||
|
tok->depth++;
|
||||||
|
json_tokener_reset_level(tok, tok->depth);
|
||||||
|
goto redo_char;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case json_tokener_state_array_add:
|
||||||
json_object_array_add(current, obj);
|
json_object_array_add(current, obj);
|
||||||
saved_state = json_tokener_state_array_sep;
|
saved_state = json_tokener_state_array_sep;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
}
|
goto redo_char;
|
||||||
break;
|
|
||||||
|
|
||||||
case json_tokener_state_array_sep:
|
case json_tokener_state_array_sep:
|
||||||
if(c == ']') {
|
if(c == ']') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else if(c == ',') {
|
} else if(c == ',') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_array;
|
saved_state = json_tokener_state_array;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
} else {
|
||||||
json_object_put(current);
|
tok->err = json_tokener_error_parse_array;
|
||||||
return error_ptr(-json_tokener_error_parse_array);
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_object:
|
|
||||||
state = json_tokener_state_object_field_start;
|
|
||||||
start_offset = this->pos;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json_tokener_state_object_field_start:
|
case json_tokener_state_object_field_start:
|
||||||
if(c == '}') {
|
if(c == '}') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else if (c == '"' || c == '\'') {
|
} else if (c == '"' || c == '\'') {
|
||||||
quote_char = c;
|
tok->quote_char = c;
|
||||||
printbuf_reset(this->pb);
|
printbuf_reset(tok->pb);
|
||||||
state = json_tokener_state_object_field;
|
state = json_tokener_state_object_field;
|
||||||
start_offset = ++this->pos;
|
|
||||||
} else {
|
} else {
|
||||||
err = json_tokener_error_parse_object;
|
tok->err = json_tokener_error_parse_object_key_name;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_object_field:
|
case json_tokener_state_object_field:
|
||||||
if(c == quote_char) {
|
if(c == tok->quote_char) {
|
||||||
printbuf_memappend(this->pb, this->source + start_offset,
|
obj_field_name = strdup(tok->pb->buf);
|
||||||
this->pos - start_offset);
|
|
||||||
obj_field_name = strdup(this->pb->buf);
|
|
||||||
saved_state = json_tokener_state_object_field_end;
|
saved_state = json_tokener_state_object_field_end;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else if(c == '\\') {
|
} else if(c == '\\') {
|
||||||
saved_state = json_tokener_state_object_field;
|
saved_state = json_tokener_state_object_field;
|
||||||
state = json_tokener_state_string_escape;
|
state = json_tokener_state_string_escape;
|
||||||
|
} else {
|
||||||
|
printbuf_memappend(tok->pb, &c, 1);
|
||||||
}
|
}
|
||||||
this->pos++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_object_field_end:
|
case json_tokener_state_object_field_end:
|
||||||
if(c == ':') {
|
if(c == ':') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_object_value;
|
saved_state = json_tokener_state_object_value;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
} else {
|
||||||
return error_ptr(-json_tokener_error_parse_object);
|
tok->err = json_tokener_error_parse_object_key_sep;
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case json_tokener_state_object_value:
|
case json_tokener_state_object_value:
|
||||||
obj = json_tokener_do_parse(this);
|
if(tok->depth >= JSON_TOKENER_MAX_DEPTH-1) {
|
||||||
if(is_error(obj)) {
|
tok->err = json_tokener_error_depth;
|
||||||
err = -(enum json_tokener_error)obj;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
state = json_tokener_state_object_value_add;
|
||||||
|
tok->depth++;
|
||||||
|
json_tokener_reset_level(tok, tok->depth);
|
||||||
|
goto redo_char;
|
||||||
|
|
||||||
|
case json_tokener_state_object_value_add:
|
||||||
json_object_object_add(current, obj_field_name, obj);
|
json_object_object_add(current, obj_field_name, obj);
|
||||||
free(obj_field_name);
|
free(obj_field_name);
|
||||||
obj_field_name = NULL;
|
obj_field_name = NULL;
|
||||||
saved_state = json_tokener_state_object_sep;
|
saved_state = json_tokener_state_object_sep;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
break;
|
goto redo_char;
|
||||||
|
|
||||||
case json_tokener_state_object_sep:
|
case json_tokener_state_object_sep:
|
||||||
if(c == '}') {
|
if(c == '}') {
|
||||||
this->pos++;
|
|
||||||
saved_state = json_tokener_state_finish;
|
saved_state = json_tokener_state_finish;
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else if(c == ',') {
|
} else if(c == ',') {
|
||||||
this->pos++;
|
saved_state = json_tokener_state_object_field_start;
|
||||||
saved_state = json_tokener_state_object;
|
|
||||||
state = json_tokener_state_eatws;
|
state = json_tokener_state_eatws;
|
||||||
} else {
|
} else {
|
||||||
err = json_tokener_error_parse_object;
|
tok->err = json_tokener_error_parse_object_value_sep;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
str++;
|
||||||
|
tok->char_offset++;
|
||||||
} while(c);
|
} while(c);
|
||||||
|
|
||||||
if(state != json_tokener_state_finish &&
|
if(state != json_tokener_state_finish &&
|
||||||
saved_state != json_tokener_state_finish)
|
saved_state != json_tokener_state_finish)
|
||||||
err = json_tokener_error_parse_eof;
|
tok->err = json_tokener_error_parse_eof;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
free(obj_field_name);
|
if(tok->err == json_tokener_success) return json_object_get(current);
|
||||||
if(err == json_tokener_success) return current;
|
mc_debug("json_tokener_parse_ex: error %s at offset %d\n",
|
||||||
mc_debug("json_tokener_do_parse: error=%d state=%d char=%c\n",
|
json_tokener_errors[tok->err], tok->char_offset);
|
||||||
err, state, c);
|
return NULL;
|
||||||
json_object_put(current);
|
|
||||||
return error_ptr(-err);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: json_tokener.h,v 1.9 2006/01/30 23:07:57 mclark Exp $
|
* $Id: json_tokener.h,v 1.10 2006/07/25 03:24:50 mclark Exp $
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
|
||||||
* Michael Clark <michael@metaparadigm.com>
|
* Michael Clark <michael@metaparadigm.com>
|
||||||
@@ -16,15 +16,19 @@
|
|||||||
|
|
||||||
enum json_tokener_error {
|
enum json_tokener_error {
|
||||||
json_tokener_success,
|
json_tokener_success,
|
||||||
|
json_tokener_continue,
|
||||||
|
json_tokener_error_depth,
|
||||||
|
json_tokener_error_parse_eof,
|
||||||
json_tokener_error_parse_unexpected,
|
json_tokener_error_parse_unexpected,
|
||||||
json_tokener_error_parse_null,
|
json_tokener_error_parse_null,
|
||||||
json_tokener_error_parse_boolean,
|
json_tokener_error_parse_boolean,
|
||||||
json_tokener_error_parse_number,
|
json_tokener_error_parse_number,
|
||||||
json_tokener_error_parse_array,
|
json_tokener_error_parse_array,
|
||||||
json_tokener_error_parse_object,
|
json_tokener_error_parse_object_key_name,
|
||||||
|
json_tokener_error_parse_object_key_sep,
|
||||||
|
json_tokener_error_parse_object_value_sep,
|
||||||
json_tokener_error_parse_string,
|
json_tokener_error_parse_string,
|
||||||
json_tokener_error_parse_comment,
|
json_tokener_error_parse_comment
|
||||||
json_tokener_error_parse_eof
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum json_tokener_state {
|
enum json_tokener_state {
|
||||||
@@ -42,22 +46,44 @@ enum json_tokener_state {
|
|||||||
json_tokener_state_boolean,
|
json_tokener_state_boolean,
|
||||||
json_tokener_state_number,
|
json_tokener_state_number,
|
||||||
json_tokener_state_array,
|
json_tokener_state_array,
|
||||||
|
json_tokener_state_array_add,
|
||||||
json_tokener_state_array_sep,
|
json_tokener_state_array_sep,
|
||||||
json_tokener_state_object,
|
|
||||||
json_tokener_state_object_field_start,
|
json_tokener_state_object_field_start,
|
||||||
json_tokener_state_object_field,
|
json_tokener_state_object_field,
|
||||||
json_tokener_state_object_field_end,
|
json_tokener_state_object_field_end,
|
||||||
json_tokener_state_object_value,
|
json_tokener_state_object_value,
|
||||||
|
json_tokener_state_object_value_add,
|
||||||
json_tokener_state_object_sep
|
json_tokener_state_object_sep
|
||||||
};
|
};
|
||||||
|
|
||||||
struct json_tokener
|
struct json_tokener_srec
|
||||||
{
|
{
|
||||||
char *source;
|
enum json_tokener_state state, saved_state;
|
||||||
int pos;
|
struct json_object *obj;
|
||||||
struct printbuf *pb;
|
struct json_object *current;
|
||||||
|
char *obj_field_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct json_object* json_tokener_parse(char *s);
|
#define JSON_TOKENER_MAX_DEPTH 32
|
||||||
|
|
||||||
|
struct json_tokener
|
||||||
|
{
|
||||||
|
char *str;
|
||||||
|
struct printbuf *pb;
|
||||||
|
int depth, is_double, st_pos, char_offset;
|
||||||
|
enum json_tokener_error err;
|
||||||
|
unsigned int ucs_char;
|
||||||
|
char quote_char;
|
||||||
|
struct json_tokener_srec stack[JSON_TOKENER_MAX_DEPTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const char* json_tokener_errors[];
|
||||||
|
|
||||||
|
extern struct json_tokener* json_tokener_new();
|
||||||
|
extern void json_tokener_free(struct json_tokener *tok);
|
||||||
|
extern void json_tokener_reset(struct json_tokener *tok);
|
||||||
|
extern struct json_object* json_tokener_parse(char *str);
|
||||||
|
extern struct json_object* json_tokener_parse_ex(struct json_tokener *tok,
|
||||||
|
char *str, int len);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
26
test1.c
26
test1.c
@@ -6,15 +6,23 @@
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
struct json_tokener *tok;
|
||||||
struct json_object *my_string, *my_int, *my_object, *my_array;
|
struct json_object *my_string, *my_int, *my_object, *my_array;
|
||||||
struct json_object *new_obj;
|
struct json_object *new_obj;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
mc_set_debug(1);
|
||||||
|
|
||||||
my_string = json_object_new_string("\t");
|
my_string = json_object_new_string("\t");
|
||||||
printf("my_string=%s\n", json_object_get_string(my_string));
|
printf("my_string=%s\n", json_object_get_string(my_string));
|
||||||
printf("my_string.to_string()=%s\n", json_object_to_json_string(my_string));
|
printf("my_string.to_string()=%s\n", json_object_to_json_string(my_string));
|
||||||
json_object_put(my_string);
|
json_object_put(my_string);
|
||||||
|
|
||||||
|
my_string = json_object_new_string("\\");
|
||||||
|
printf("my_string=%s\n", json_object_get_string(my_string));
|
||||||
|
printf("my_string.to_string()=%s\n", json_object_to_json_string(my_string));
|
||||||
|
json_object_put(my_string);
|
||||||
|
|
||||||
my_string = json_object_new_string("foo");
|
my_string = json_object_new_string("foo");
|
||||||
printf("my_string=%s\n", json_object_get_string(my_string));
|
printf("my_string=%s\n", json_object_get_string(my_string));
|
||||||
printf("my_string.to_string()=%s\n", json_object_to_json_string(my_string));
|
printf("my_string.to_string()=%s\n", json_object_to_json_string(my_string));
|
||||||
@@ -98,6 +106,10 @@ int main(int argc, char **argv)
|
|||||||
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
||||||
json_object_put(new_obj);
|
json_object_put(new_obj);
|
||||||
|
|
||||||
|
new_obj = json_tokener_parse("[false]");
|
||||||
|
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
||||||
|
json_object_put(new_obj);
|
||||||
|
|
||||||
new_obj = json_tokener_parse("[\"abc\",null,\"def\",12]");
|
new_obj = json_tokener_parse("[\"abc\",null,\"def\",12]");
|
||||||
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
||||||
json_object_put(new_obj);
|
json_object_put(new_obj);
|
||||||
@@ -128,6 +140,20 @@ int main(int argc, char **argv)
|
|||||||
new_obj = json_tokener_parse("foo");
|
new_obj = json_tokener_parse("foo");
|
||||||
if(is_error(new_obj)) printf("got error as expected\n");
|
if(is_error(new_obj)) printf("got error as expected\n");
|
||||||
|
|
||||||
|
new_obj = json_tokener_parse("{ \"foo");
|
||||||
|
if(is_error(new_obj)) printf("got error as expected\n");
|
||||||
|
|
||||||
|
/* test incremental parsing */
|
||||||
|
tok = json_tokener_new();
|
||||||
|
new_obj = json_tokener_parse_ex(tok, "{ \"foo", 6);
|
||||||
|
if(is_error(new_obj)) printf("got error as expected\n");
|
||||||
|
new_obj = json_tokener_parse_ex(tok, "\": {\"bar", 8);
|
||||||
|
if(is_error(new_obj)) printf("got error as expected\n");
|
||||||
|
new_obj = json_tokener_parse_ex(tok, "\":13}}", 6);
|
||||||
|
printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
|
||||||
|
json_object_put(new_obj);
|
||||||
|
json_tokener_free(tok);
|
||||||
|
|
||||||
json_object_put(my_string);
|
json_object_put(my_string);
|
||||||
json_object_put(my_int);
|
json_object_put(my_int);
|
||||||
json_object_put(my_object);
|
json_object_put(my_object);
|
||||||
|
|||||||
Reference in New Issue
Block a user