Annotation of parser3/src/classes/json.C, revision 1.57
1.1 misha 1: /** @file
2: Parser: @b json parser class.
3:
1.57 ! moko 4: Copyright (c) 2000-2023 Art. Lebedev Studio (http://www.artlebedev.com)
! 5: Authors: Konstantin Morshnev <moko@design.ru>
1.1 misha 6: */
7:
8: #include "classes.h"
9: #include "pa_vmethod_frame.h"
10:
11: #include "pa_request.h"
12: #include "pa_vbool.h"
13:
14: #include "pa_charset.h"
15: #include "pa_charsets.h"
1.29 moko 16: #include "pa_json.h"
1.1 misha 17:
1.14 misha 18: #ifdef XML
19: #include "pa_vxdoc.h"
20: #endif
21:
1.57 ! moko 22: volatile const char * IDENT_JSON_C="$Id: json.C,v 1.56 2021/01/15 16:28:36 moko Exp $";
1.17 moko 23:
1.1 misha 24: // class
25:
26: class MJson: public Methoded {
27: public:
28: MJson();
29: };
30:
31: // global variable
32:
1.41 moko 33: DECLARE_CLASS_VAR(json, new MJson);
1.1 misha 34:
35: // methods
1.53 moko 36: struct Json : public PA_Allocated {
1.4 moko 37: Stack<VHash*> stack;
1.3 moko 38: Stack<String*> key_stack;
1.1 misha 39:
1.3 moko 40: String* key;
1.1 misha 41: Value* result;
42:
1.16 misha 43: Junction* hook_object;
44: Junction* hook_array;
1.3 moko 45: Request* request;
46:
1.1 misha 47: Charset *charset;
1.23 moko 48: String::Language taint;
49:
1.1 misha 50: bool handle_double;
1.30 misha 51: bool handle_int;
1.4 moko 52: enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct;
1.3 moko 53:
1.23 moko 54: Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL),
1.30 misha 55: request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), handle_int(true),
56: distinct(D_EXCEPTION){}
1.4 moko 57:
58: bool set_distinct(const String &value){
59: if (value == "first") distinct = D_FIRST;
60: else if (value == "last") distinct = D_LAST;
61: else if (value == "all") distinct = D_ALL;
62: else return false;
63: return true;
64: }
1.1 misha 65: };
66:
67: static void set_json_value(Json *json, Value *value){
1.4 moko 68: VHash *top = json->stack.top_value();
1.3 moko 69: if(json->key == NULL){
1.51 moko 70: top->hash().put(format(top->get_hash()->count(), 0), value);
1.1 misha 71: } else {
1.4 moko 72: switch (json->distinct){
73: case Json::D_EXCEPTION:
74: if (top->hash().put_dont_replace(*json->key, value))
75: throw Exception(PARSER_RUNTIME, json->key, "duplicate key");
76: break;
77: case Json::D_FIRST:
78: top->hash().put_dont_replace(*json->key, value);
79: break;
80: case Json::D_LAST:
81: top->hash().put(*json->key, value);
82: break;
83: case Json::D_ALL:
84: if (top->hash().put_dont_replace(*json->key, value)){
85: for(int i=2;;i++){
86: String key;
87: key << *json->key << "_" << format(i, 0);
88: if (!top->hash().put_dont_replace(key, value)) break;
89: }
90: }
91: break;
92: }
1.3 moko 93: json->key=NULL;
1.1 misha 94: }
95: }
96:
1.25 moko 97: String* json_string(Json *json, const char *value, uint32_t length){
1.3 moko 98: String::C result = json->charset !=NULL ?
1.44 moko 99: Charset::transcode(String::C(value, length), pa_UTF8_charset, *json->charset) :
1.25 moko 100: String::C(pa_strdup(value, length), length);
1.39 moko 101: return new String(result, json->taint);
1.1 misha 102: }
103:
1.3 moko 104: static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){
1.10 moko 105: Value *params[]={new VString(key ? *key : String::Empty), value};
1.49 moko 106: METHOD_FRAME_ACTION(*hook->method, r.method_frame, hook->self, {
107: frame.store_params(params, 2);
108: r.call(frame);
109: return &frame.result();
110: });
1.1 misha 111: }
112:
1.25 moko 113: static int json_callback(Json *json, int type, const char *value, uint32_t length)
1.1 misha 114: {
115: switch(type) {
1.25 moko 116: case JSON_OBJECT_BEGIN:{
1.4 moko 117: VHash *v = new VHash();
1.16 misha 118: if (json->hook_object){
1.1 misha 119: json->key_stack.push(json->key);
1.16 misha 120: json->key=NULL;
1.1 misha 121: } else {
122: if (json->stack.count()) set_json_value(json, v);
123: }
124: json->stack.push(v);
125: break;
126: }
1.25 moko 127: case JSON_OBJECT_END:{
1.16 misha 128: if (json->hook_object){
1.3 moko 129: String* key = json->key_stack.pop();
1.16 misha 130: json->result = json_hook(*json->request, json->hook_object, key, json->stack.pop());
1.1 misha 131:
132: if (json->stack.count()){
133: json->key = key;
134: set_json_value(json, json->result);
135: }
136: } else {
137: json->result = json->stack.pop();
138: }
139: break;
140: }
1.25 moko 141: case JSON_ARRAY_BEGIN:{
1.4 moko 142: VHash *v = new VHash();
1.16 misha 143: if (json->hook_array){
144: json->key_stack.push(json->key);
145: json->key=NULL;
146: } else {
147: if (json->stack.count()) set_json_value(json, v);
148: }
1.1 misha 149: json->stack.push(v);
150: break;
151: }
1.25 moko 152: case JSON_ARRAY_END:
1.12 moko 153: // libjson supports array at top level, we too
1.16 misha 154: if (json->hook_array){
155: String* key = json->key_stack.pop();
156: json->result = json_hook(*json->request, json->hook_array, key, json->stack.pop());
157:
158: if (json->stack.count()){
159: json->key = key;
160: set_json_value(json, json->result);
161: }
162: } else {
163: json->result = json->stack.pop();
164: }
1.1 misha 165: break;
1.25 moko 166: case JSON_KEY:
167: json->key = json_string(json, value, length);
1.16 misha 168: break;
1.25 moko 169: case JSON_INT:
1.30 misha 170: if (json->handle_int){
171: set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
172: } else {
173: // JSON_STRING
174: set_json_value(json, new VString(*json_string(json, value, length)));
175: }
1.1 misha 176: break;
1.25 moko 177: case JSON_FLOAT:
1.1 misha 178: if (json->handle_double){
1.25 moko 179: set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
1.1 misha 180: break;
1.25 moko 181: } // else is JSON_STRING
182: case JSON_STRING:
183: set_json_value(json, new VString(*json_string(json, value, length)));
1.1 misha 184: break;
1.25 moko 185: case JSON_NULL:
1.18 moko 186: set_json_value(json, VVoid::get());
1.1 misha 187: break;
1.25 moko 188: case JSON_TRUE:
1.1 misha 189: set_json_value(json, &VBool::get(true));
190: break;
1.25 moko 191: case JSON_FALSE:
1.1 misha 192: set_json_value(json, &VBool::get(false));
1.25 moko 193: break;
1.1 misha 194: }
1.25 moko 195: return 0;
1.1 misha 196: }
197:
1.5 moko 198: static const char* json_error_message(int error_code){
199: static const char* error_messages[] = {
1.1 misha 200: NULL,
1.25 moko 201: "out of memory",
202: "bad character",
203: "stack empty",
204: "pop unexpected mode",
205: "nesting limit",
206: "data limit",
207: "comment not allowed by config",
1.35 moko 208: "unexpected character",
1.25 moko 209: "missing unicode low surrogate",
210: "unexpected unicode low surrogate",
211: "error comma out of structure",
212: "error in a callback"
1.1 misha 213: };
214: return error_messages[error_code];
215: }
216:
1.23 moko 217: extern String::Language get_untaint_lang(const String& lang_name);
218:
1.35 moko 219: #define SOURCE_MAX_LEN 60
220:
221: void json_exception_with_source(Request& r, const char* msg, const char* json, int offset){
222: int i;
223:
224: int line=0;
225: int start=0;
226: int end=strlen(json);
227:
228: if(offset>end)
229: offset=end;
230:
231: for(i = 0; i < offset; i++){
232: if(json[i]=='\n'){
233: line++;
234: }
235: }
236:
237: if(offset > SOURCE_MAX_LEN/2)
238: start = offset - SOURCE_MAX_LEN/2;
239:
240: for(i = offset-1; i>=start; i--){
241: if(json[i]=='\n'){
242: start=i+1;
243: break;
244: }
245: }
246:
247: if(start+SOURCE_MAX_LEN < end)
248: end=start+SOURCE_MAX_LEN;
249:
250: for(i = offset+1; i<end; i++){
251: if(json[i]=='\n'){
252: end=i;
253: break;
254: }
255: }
256:
257: char *source = pa_strdup(json+start, end-start);
258: int source_offset = offset-start;
259:
260: if(source[source_offset]=='\n')
261: source[source_offset]=' ';
262:
263: for(i = 0; i < source_offset; i++){
264: if(source[i]=='\t'){
265: source[i]=' ';
266: }
267: }
268:
269: if(r.charsets.source().isUTF8()){
270: source=(char *)fixUTF8(source);
271: if(source_offset>0){
272: String s_source(pa_strdup(source,source_offset));
273: source_offset=s_source.length(r.charsets.source());
274: }
275: }
276:
277: throw Exception("json.parse", 0, "%s at line %d\n%s\n%*s", msg, line+1, source, source_offset+1, "^");
278: }
279:
1.1 misha 280: static void _parse(Request& r, MethodParams& params) {
1.3 moko 281: const String& json_string=params.as_string(0, "json must be string");
282:
283: Json json(r.charsets.source().isUTF8() ? NULL : &(r.charsets.source()));
1.1 misha 284:
1.25 moko 285: json_config config = {
286: 0, // buffer_initial_size
1.26 moko 287: 128, // max_nesting
1.25 moko 288: 0, // max_data
289: 1, // allow_c_comments
290: 1, // allow_yaml_comments
291: pa_malloc,
292: pa_realloc,
293: pa_free
294: };
1.1 misha 295:
296: if(params.count() == 2)
297: if(HashStringValue* options=params.as_hash(1)) {
298: int valid_options=0;
299: if(Value* value=options->get("depth")) {
1.46 moko 300: config.max_nesting=r.process(*value).as_int();
1.1 misha 301: valid_options++;
302: }
303: if(Value* value=options->get("double")) {
1.46 moko 304: json.handle_double=r.process(*value).as_bool();
1.4 moko 305: valid_options++;
306: }
1.30 misha 307: if(Value* value=options->get("int")) {
1.46 moko 308: json.handle_int=r.process(*value).as_bool();
1.30 misha 309: valid_options++;
310: }
1.4 moko 311: if(Value* value=options->get("distinct")) {
312: const String& sdistinct=value->as_string();
313: if (!json.set_distinct(sdistinct))
314: throw Exception(PARSER_RUNTIME, &sdistinct, "must be 'first', 'last' or 'all'");
1.1 misha 315: valid_options++;
316: }
1.23 moko 317: if(Value* value=options->get("taint")) {
318: json.taint=get_untaint_lang(value->as_string());
319: valid_options++;
320: }
1.1 misha 321: if(Value* value=options->get("object")) {
1.16 misha 322: json.hook_object=value->get_junction();
1.3 moko 323: json.request=&r;
1.49 moko 324: if (!json.hook_object || !json.hook_object->method || !json.hook_object->method->params_names || !(json.hook_object->method->params_count == 2))
1.1 misha 325: throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters");
326: valid_options++;
327: }
1.16 misha 328: if(Value* value=options->get("array")) {
329: json.hook_array=value->get_junction();
330: json.request=&r;
1.49 moko 331: if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_count == 2))
1.16 misha 332: throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters");
333: valid_options++;
334: }
1.1 misha 335: if(valid_options!=options->count())
336: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
337: }
338:
1.28 moko 339: const String::Body json_body = json_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.44 moko 340: const char *json_cstr = json.charset != NULL ? Charset::transcode(json_body, *json.charset, pa_UTF8_charset).cstr() : json_body.cstr();
1.1 misha 341:
1.25 moko 342: json_parser parser;
343: if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json))
344: throw Exception("json.parse", 0, "%s", json_error_message(result));
345:
1.36 moko 346: if(!*json_cstr)
347: throw Exception("json.parse", 0, "empty string is not valid json");
348:
349: const char *first_quote=strchr(json_cstr,'"');
350: if(first_quote && first_quote>json_cstr && *(--first_quote) == '\\')
351: json_exception_with_source(r, "illegal quote escape, json may be tainted", json_cstr, first_quote-json_cstr);
352:
1.25 moko 353: uint32_t processed;
354: if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed))
1.35 moko 355: json_exception_with_source(r, json_error_message(result), json_cstr, processed);
1.3 moko 356:
1.25 moko 357: if (!json_parser_is_done(&parser))
1.35 moko 358: json_exception_with_source(r, "unexpected end of json data", json_cstr, processed);
359:
1.25 moko 360: json_parser_free(&parser);
1.1 misha 361:
1.48 moko 362: if (json.result) r.write(*json.result);
1.1 misha 363: }
364:
1.26 moko 365: const uint ANTI_ENDLESS_JSON_STRING_RECOURSION=128;
366:
1.8 moko 367: char *get_indent(uint level){
368: static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
369: if (!cache[level]){
1.56 moko 370: char *result = static_cast<char*>(pa_malloc_atomic(level+1));
1.8 moko 371: memset(result, '\t', level);
1.9 moko 372: result[level]='\0';
1.8 moko 373: return cache[level]=result;
374: }
375: return cache[level];
376: }
377:
1.56 moko 378: String *get_delim(uint level){
379: static String* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
380:
381: if (!cache[level]){
382: char *result = static_cast<char*>(pa_malloc_atomic(level+2+1+1));
383: result[0]=',';
384: result[1]='\n';
385: memset(result+2, '\t', level);
386: result[level+2]='"';
387: result[level+3]='\0';
388: return cache[level] = new String(result, String::L_AS_IS);
389: }
390: return cache[level];
391: }
392:
1.26 moko 393: class Json_string_recoursion {
394: Json_options& foptions;
395: public:
396: Json_string_recoursion(Json_options& aoptions) : foptions(aoptions) {
397: if(++foptions.json_string_recoursion==ANTI_ENDLESS_JSON_STRING_RECOURSION)
398: throw Exception(PARSER_RUNTIME, 0, "call canceled - endless json recursion detected");
399: }
400: ~Json_string_recoursion() {
401: if(foptions.json_string_recoursion)
402: foptions.json_string_recoursion--;
403: }
404: };
405:
1.21 moko 406: const String& value_json_string(String::Body key, Value& v, Json_options& options);
1.6 misha 407:
1.37 moko 408: const String* Json_options::hash_json_string(HashStringValue *hash) {
409: if(!hash || !hash->count())
1.21 moko 410: return new String("{}", String::L_AS_IS);
1.8 moko 411:
1.26 moko 412: Json_string_recoursion go_down(*this);
1.8 moko 413:
414: String& result = *new String("{\n", String::L_AS_IS);
415:
1.21 moko 416: if (indent){
1.8 moko 417:
418: String *delim=NULL;
1.26 moko 419: indent=get_indent(json_string_recoursion);
1.37 moko 420: for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8 moko 421: if (delim){
422: result << *delim;
423: } else {
1.21 moko 424: result << indent << "\"";
1.56 moko 425: delim = get_delim(json_string_recoursion);
1.8 moko 426: }
1.21 moko 427: result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8 moko 428: }
1.26 moko 429: result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "}";
1.6 misha 430:
1.8 moko 431: } else {
432:
433: bool need_delim=false;
1.37 moko 434: for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8 moko 435: result << (need_delim ? ",\n\"" : "\"");
1.21 moko 436: result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8 moko 437: need_delim=true;
438: }
439: result << "\n}";
1.6 misha 440:
441: }
442:
1.21 moko 443: return &result;
1.6 misha 444: }
445:
1.21 moko 446: static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) {
1.15 misha 447: return v->is(key.cstr());
448: }
1.26 moko 449:
1.21 moko 450: const String& value_json_string(String::Body key, Value& v, Json_options& options) {
451: if(options.methods) {
452: Value* method=options.methods->get(v.type());
453: if(!method){
454: method=options.methods->first_that<Value*>(based_on, &v);
1.31 misha 455: options.methods->put(v.type(), method ? method : VVoid::get());
1.21 moko 456: }
457: if(method && !method->is_void()) {
1.6 misha 458: Junction* junction=method->get_junction();
1.26 moko 459: HashStringValue* params_hash=options.params && options.indent ? options.params->get_hash() : NULL;
1.27 moko 460: Temp_hash_value<HashStringValue, Value*> indent(params_hash, "indent", new VString(*new String(options.indent, String::L_AS_IS)));
1.26 moko 461:
1.21 moko 462: Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()};
1.6 misha 463:
1.49 moko 464: METHOD_FRAME_ACTION(*junction->method, options.r->method_frame, junction->self, {
465: frame.store_params(params, 3);
466: options.r->call(frame);
467: return frame.result().as_string();
468: });
1.6 misha 469: }
1.15 misha 470: }
1.6 misha 471:
1.21 moko 472: options.key=key;
1.6 misha 473: return *v.get_json_string(options);
474: }
475:
476: static void _string(Request& r, MethodParams& params) {
477: Json_options json(&r);
478:
479: if(params.count() == 2)
480: if(HashStringValue* options=params.as_hash(1)) {
1.47 moko 481: json.params=¶ms[1];
1.6 misha 482: HashStringValue* methods=new HashStringValue();
483: int valid_options=0;
1.14 misha 484: HashStringValue* vvalue;
1.6 misha 485: for(HashStringValue::Iterator i(*options); i; i.next() ){
486: String::Body key=i.key();
487: Value* value=i.value();
488: if(key == "skip-unknown"){
1.46 moko 489: json.skip_unknown=r.process(*value).as_bool();
1.6 misha 490: valid_options++;
1.50 moko 491: } else if(key == "one-line"){
492: json.one_line=r.process(*value).as_bool();
493: valid_options++;
1.6 misha 494: } else if(key == "date" && value->is_string()){
495: const String& svalue=value->as_string();
496: if(!json.set_date_format(svalue))
1.38 moko 497: throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string', 'iso-string' or 'unix-timestamp'");
1.6 misha 498: valid_options++;
1.8 moko 499: } else if(key == "indent"){
1.26 moko 500: if(value->is_string()){
501: json.indent=value->as_string().cstr();
502: json.json_string_recoursion=strlen(json.indent);
1.46 moko 503: } else json.indent=r.process(*value).as_bool() ? "" : NULL;
1.8 moko 504: valid_options++;
1.6 misha 505: } else if(key == "table" && value->is_string()){
506: const String& svalue=value->as_string();
507: if(!json.set_table_format(svalue))
1.13 moko 508: throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.6 misha 509: valid_options++;
510: } else if(key == "file" && value->is_string()){
511: const String& svalue=value->as_string();
512: if(!json.set_file_format(svalue))
1.19 misha 513: throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'");
1.6 misha 514: valid_options++;
1.32 misha 515: } else if(key == "void" && value->is_string()){
516: const String& svalue=value->as_string();
517: if(!json.set_void_format(svalue))
518: throw Exception(PARSER_RUNTIME, &svalue, "must be 'string' or 'null'");
519: valid_options++;
1.14 misha 520: #ifdef XML
521: } else if(key == "xdoc" && (vvalue = value->get_hash())){
1.24 moko 522: json.xdoc_options=new XDocOutputOptions();
523: json.xdoc_options->append(r, vvalue);
1.14 misha 524: valid_options++;
525: #endif
1.6 misha 526: } else if(Junction* junction=value->get_junction()){
1.49 moko 527: if(!junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.13 moko 528: throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr());
1.6 misha 529: methods->put(key, value);
530: valid_options++;
531: }
532: }
1.22 moko 533:
1.6 misha 534: if(valid_options!=options->count())
535: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.22 moko 536:
537: // special handling for $._default
1.43 moko 538: if(VHashBase* vhash=static_cast<VHashBase*>(params[1].as(VHASH_TYPE)))
1.22 moko 539: if(Value* value=vhash->get_default()) {
1.34 misha 540: if(!value->is_string()){
1.43 moko 541: Junction* junction=value->get_junction();
1.49 moko 542: if(!junction || !junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.42 moko 543: throw Exception(PARSER_RUNTIME, 0, "$._default must be string or parser method with 3 parameters");
1.34 misha 544: }
1.22 moko 545: json.default_method=value;
546: }
547:
1.6 misha 548: if(methods->count())
549: json.methods=methods;
550: }
1.14 misha 551:
1.46 moko 552: const String& result_string=value_json_string(String::Body(), r.process(params[0]), json);
1.28 moko 553: String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.50 moko 554: if(json.one_line){
555: char *result=result_body.cstrm();
556: for(char *c=result;*c;c++)
557: if(*c=='\n')
558: *c=' ';
559: result_body=result;
560: }
1.48 moko 561: r.write(*new String(result_body, String::L_AS_IS));
1.50 moko 562: }
1.6 misha 563:
1.1 misha 564: // constructor
565:
566: MJson::MJson(): Methoded("json") {
567: add_native_method("parse", Method::CT_STATIC, _parse, 1, 2);
1.6 misha 568:
569: add_native_method("string", Method::CT_ANY, _string, 1, 2);
1.1 misha 570: }
E-mail: