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