Annotation of parser3/src/classes/json.C, revision 1.25
1.1 misha 1: /** @file
2: Parser: @b json parser class.
3:
1.17 moko 4: Copyright (c) 2000-2012 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.25 ! moko 15: #include "json.h"
1.1 misha 16:
1.14 misha 17: #ifdef XML
18: #include "pa_vxdoc.h"
19: #endif
20:
1.25 ! moko 21: volatile const char * IDENT_JSON_C="$Id: json.C,v 1.24 2012/06/21 14:22:09 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:
32: DECLARE_CLASS_VAR(json, new MJson, 0);
33:
34: // methods
35: struct Json {
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.4 moko 50: enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct;
1.3 moko 51:
1.23 moko 52: Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL),
53: request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), distinct(D_EXCEPTION){}
1.4 moko 54:
55: bool set_distinct(const String &value){
56: if (value == "first") distinct = D_FIRST;
57: else if (value == "last") distinct = D_LAST;
58: else if (value == "all") distinct = D_ALL;
59: else return false;
60: return true;
61: }
1.1 misha 62: };
63:
64: static void set_json_value(Json *json, Value *value){
1.4 moko 65: VHash *top = json->stack.top_value();
1.3 moko 66: if(json->key == NULL){
1.4 moko 67: top->hash().put(String(format(top->get_hash()->count(), 0)), value);
1.1 misha 68: } else {
1.4 moko 69: switch (json->distinct){
70: case Json::D_EXCEPTION:
71: if (top->hash().put_dont_replace(*json->key, value))
72: throw Exception(PARSER_RUNTIME, json->key, "duplicate key");
73: break;
74: case Json::D_FIRST:
75: top->hash().put_dont_replace(*json->key, value);
76: break;
77: case Json::D_LAST:
78: top->hash().put(*json->key, value);
79: break;
80: case Json::D_ALL:
81: if (top->hash().put_dont_replace(*json->key, value)){
82: for(int i=2;;i++){
83: String key;
84: key << *json->key << "_" << format(i, 0);
85: if (!top->hash().put_dont_replace(key, value)) break;
86: }
87: }
88: break;
89: }
1.3 moko 90: json->key=NULL;
1.1 misha 91: }
92: }
93:
1.25 ! moko 94: String* json_string(Json *json, const char *value, uint32_t length){
1.3 moko 95: String::C result = json->charset !=NULL ?
1.25 ! moko 96: Charset::transcode(String::C(value, length), UTF8_charset, *json->charset) :
! 97: String::C(pa_strdup(value, length), length);
1.23 moko 98: return new String(result.str, json->taint, result.length);
1.1 misha 99: }
100:
1.3 moko 101: static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){
102: VMethodFrame frame(*hook->method, r.method_frame, hook->self);
1.10 moko 103: Value *params[]={new VString(key ? *key : String::Empty), value};
1.3 moko 104:
105: frame.store_params(params, 2);
106: r.execute_method(frame);
107:
108: return &frame.result().as_value();
1.1 misha 109: }
110:
1.25 ! moko 111: static int json_callback(Json *json, int type, const char *value, uint32_t length)
1.1 misha 112: {
113: switch(type) {
1.25 ! moko 114: case JSON_OBJECT_BEGIN:{
1.4 moko 115: VHash *v = new VHash();
1.16 misha 116: if (json->hook_object){
1.1 misha 117: json->key_stack.push(json->key);
1.16 misha 118: json->key=NULL;
1.1 misha 119: } else {
120: if (json->stack.count()) set_json_value(json, v);
121: }
122: json->stack.push(v);
123: break;
124: }
1.25 ! moko 125: case JSON_OBJECT_END:{
1.16 misha 126: if (json->hook_object){
1.3 moko 127: String* key = json->key_stack.pop();
1.16 misha 128: json->result = json_hook(*json->request, json->hook_object, key, json->stack.pop());
1.1 misha 129:
130: if (json->stack.count()){
131: json->key = key;
132: set_json_value(json, json->result);
133: }
134: } else {
135: json->result = json->stack.pop();
136: }
137: break;
138: }
1.25 ! moko 139: case JSON_ARRAY_BEGIN:{
1.4 moko 140: VHash *v = new VHash();
1.16 misha 141: if (json->hook_array){
142: json->key_stack.push(json->key);
143: json->key=NULL;
144: } else {
145: if (json->stack.count()) set_json_value(json, v);
146: }
1.1 misha 147: json->stack.push(v);
148: break;
149: }
1.25 ! moko 150: case JSON_ARRAY_END:
1.12 moko 151: // libjson supports array at top level, we too
1.16 misha 152: if (json->hook_array){
153: String* key = json->key_stack.pop();
154: json->result = json_hook(*json->request, json->hook_array, key, json->stack.pop());
155:
156: if (json->stack.count()){
157: json->key = key;
158: set_json_value(json, json->result);
159: }
160: } else {
161: json->result = json->stack.pop();
162: }
1.1 misha 163: break;
1.25 ! moko 164: case JSON_KEY:
! 165: json->key = json_string(json, value, length);
1.16 misha 166: break;
1.25 ! moko 167: case JSON_INT:
! 168: set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
1.1 misha 169: break;
1.25 ! moko 170: case JSON_FLOAT:
1.1 misha 171: if (json->handle_double){
1.25 ! moko 172: set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
1.1 misha 173: break;
1.25 ! moko 174: } // else is JSON_STRING
! 175: case JSON_STRING:
! 176: set_json_value(json, new VString(*json_string(json, value, length)));
1.1 misha 177: break;
1.25 ! moko 178: case JSON_NULL:
1.18 moko 179: set_json_value(json, VVoid::get());
1.1 misha 180: break;
1.25 ! moko 181: case JSON_TRUE:
1.1 misha 182: set_json_value(json, &VBool::get(true));
183: break;
1.25 ! moko 184: case JSON_FALSE:
1.1 misha 185: set_json_value(json, &VBool::get(false));
1.25 ! moko 186: break;
1.1 misha 187: }
1.25 ! moko 188: return 0;
1.1 misha 189: }
190:
1.5 moko 191: static const char* json_error_message(int error_code){
192: static const char* error_messages[] = {
1.1 misha 193: NULL,
1.25 ! moko 194: "out of memory",
! 195: "bad character",
! 196: "stack empty",
! 197: "pop unexpected mode",
! 198: "nesting limit",
! 199: "data limit",
! 200: "comment not allowed by config",
! 201: "unexpected char",
! 202: "missing unicode low surrogate",
! 203: "unexpected unicode low surrogate",
! 204: "error comma out of structure",
! 205: "error in a callback"
1.1 misha 206: };
207: return error_messages[error_code];
208: }
209:
1.23 moko 210: extern String::Language get_untaint_lang(const String& lang_name);
211:
1.1 misha 212: static void _parse(Request& r, MethodParams& params) {
1.3 moko 213: const String& json_string=params.as_string(0, "json must be string");
214:
215: Json json(r.charsets.source().isUTF8() ? NULL : &(r.charsets.source()));
1.1 misha 216:
1.25 ! moko 217: json_config config = {
! 218: 0, // buffer_initial_size
! 219: 128, // max_nesting
! 220: 0, // max_data
! 221: 1, // allow_c_comments
! 222: 1, // allow_yaml_comments
! 223: pa_malloc,
! 224: pa_realloc,
! 225: pa_free
! 226: };
1.1 misha 227:
228: if(params.count() == 2)
229: if(HashStringValue* options=params.as_hash(1)) {
230: int valid_options=0;
231: if(Value* value=options->get("depth")) {
1.25 ! moko 232: config.max_nesting=r.process_to_value(*value).as_int();
1.1 misha 233: valid_options++;
234: }
235: if(Value* value=options->get("double")) {
1.4 moko 236: json.handle_double=r.process_to_value(*value).as_bool();
237: valid_options++;
238: }
239: if(Value* value=options->get("distinct")) {
240: const String& sdistinct=value->as_string();
241: if (!json.set_distinct(sdistinct))
242: throw Exception(PARSER_RUNTIME, &sdistinct, "must be 'first', 'last' or 'all'");
1.1 misha 243: valid_options++;
244: }
1.23 moko 245: if(Value* value=options->get("taint")) {
246: json.taint=get_untaint_lang(value->as_string());
247: valid_options++;
248: }
1.1 misha 249: if(Value* value=options->get("object")) {
1.16 misha 250: json.hook_object=value->get_junction();
1.3 moko 251: json.request=&r;
1.16 misha 252: if (!json.hook_object || !json.hook_object->method || !json.hook_object->method->params_names || !(json.hook_object->method->params_names->count() == 2))
1.1 misha 253: throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters");
254: valid_options++;
255: }
1.16 misha 256: if(Value* value=options->get("array")) {
257: json.hook_array=value->get_junction();
258: json.request=&r;
259: if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_names->count() == 2))
260: throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters");
261: valid_options++;
262: }
1.1 misha 263: if(valid_options!=options->count())
264: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
265: }
266:
1.2 misha 267: const String::Body json_body = json_string.cstr_to_string_body_untaint(String::L_JSON, 0, &(r.charsets));
1.1 misha 268: const char *json_cstr = json.charset != NULL ? Charset::transcode(json_body, *json.charset, UTF8_charset).cstr() : json_body.cstr();
269:
1.25 ! moko 270: json_parser parser;
! 271: if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json))
! 272: throw Exception("json.parse", 0, "%s", json_error_message(result));
! 273:
! 274: uint32_t processed;
! 275: if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed))
! 276: throw Exception("json.parse", 0, "%s at byte %d", json_error_message(result), processed);
1.3 moko 277:
1.25 ! moko 278: if (!json_parser_is_done(&parser))
! 279: throw Exception("json.parse", 0, "unexpected end of json data");
1.1 misha 280:
1.25 ! moko 281: json_parser_free(&parser);
1.1 misha 282:
283: if (json.result) r.write_no_lang(*json.result);
284: }
285:
1.8 moko 286: char *get_indent(uint level){
287: static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
288: if (!cache[level]){
289: char *result = static_cast<char*>(pa_gc_malloc_atomic(level+1));
290: memset(result, '\t', level);
1.9 moko 291: result[level]='\0';
1.8 moko 292: return cache[level]=result;
293: }
294: return cache[level];
295: }
296:
1.21 moko 297: const String& value_json_string(String::Body key, Value& v, Json_options& options);
1.6 misha 298:
1.21 moko 299: const String* Json_options::hash_json_string(HashStringValue &hash) {
1.6 misha 300: if(!hash.count())
1.21 moko 301: return new String("{}", String::L_AS_IS);
1.8 moko 302:
1.21 moko 303: uint level = r->json_string_recoursion_go_down();
1.8 moko 304:
305: String& result = *new String("{\n", String::L_AS_IS);
306:
1.21 moko 307: if (indent){
1.8 moko 308:
309: String *delim=NULL;
1.21 moko 310: indent=get_indent(level);
1.8 moko 311: for(HashStringValue::Iterator i(hash); i; i.next() ){
312: if (delim){
313: result << *delim;
314: } else {
1.21 moko 315: result << indent << "\"";
316: delim = new String(",\n", String::L_AS_IS); *delim << indent << "\"";
1.8 moko 317: }
1.21 moko 318: result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8 moko 319: }
1.21 moko 320: result << "\n" << (indent=get_indent(level-1)) << "}";
1.6 misha 321:
1.8 moko 322: } else {
323:
324: bool need_delim=false;
325: for(HashStringValue::Iterator i(hash); i; i.next() ){
326: result << (need_delim ? ",\n\"" : "\"");
1.21 moko 327: result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8 moko 328: need_delim=true;
329: }
330: result << "\n}";
1.6 misha 331:
332: }
333:
1.21 moko 334: r->json_string_recoursion_go_up();
335: return &result;
1.6 misha 336: }
337:
1.21 moko 338: static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) {
1.15 misha 339: return v->is(key.cstr());
340: }
341:
1.21 moko 342: const String& value_json_string(String::Body key, Value& v, Json_options& options) {
343: if(options.methods) {
344: Value* method=options.methods->get(v.type());
345: if(!method){
346: method=options.methods->first_that<Value*>(based_on, &v);
347: options.methods->put(key, method ? method : VVoid::get());
348: }
349: if(method && !method->is_void()) {
1.6 misha 350: Junction* junction=method->get_junction();
1.21 moko 351: VMethodFrame frame(*junction->method, options.r->method_frame, junction->self);
1.6 misha 352:
1.21 moko 353: Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()};
1.13 moko 354: frame.store_params(params, 3);
1.6 misha 355:
1.21 moko 356: options.r->execute_method(frame);
1.6 misha 357:
358: return frame.result().as_string();
359: }
1.15 misha 360: }
1.6 misha 361:
1.21 moko 362: options.key=key;
1.6 misha 363: return *v.get_json_string(options);
364: }
365:
366: static void _string(Request& r, MethodParams& params) {
367: Json_options json(&r);
368:
369: if(params.count() == 2)
370: if(HashStringValue* options=params.as_hash(1)) {
371: json.params=params.get(1);
372: HashStringValue* methods=new HashStringValue();
373: int valid_options=0;
1.14 misha 374: HashStringValue* vvalue;
1.6 misha 375: for(HashStringValue::Iterator i(*options); i; i.next() ){
376: String::Body key=i.key();
377: Value* value=i.value();
378: if(key == "skip-unknown"){
379: json.skip_unknown=r.process_to_value(*value).as_bool();
380: valid_options++;
381: } else if(key == "date" && value->is_string()){
382: const String& svalue=value->as_string();
383: if(!json.set_date_format(svalue))
384: throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string' or 'unix-timestamp'");
385: valid_options++;
1.8 moko 386: } else if(key == "indent"){
387: json.indent=r.process_to_value(*value).as_bool() ? "":NULL;
388: valid_options++;
1.6 misha 389: } else if(key == "table" && value->is_string()){
390: const String& svalue=value->as_string();
391: if(!json.set_table_format(svalue))
1.13 moko 392: throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.6 misha 393: valid_options++;
394: } else if(key == "file" && value->is_string()){
395: const String& svalue=value->as_string();
396: if(!json.set_file_format(svalue))
1.19 misha 397: throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'");
1.6 misha 398: valid_options++;
1.14 misha 399: #ifdef XML
400: } else if(key == "xdoc" && (vvalue = value->get_hash())){
1.24 moko 401: json.xdoc_options=new XDocOutputOptions();
402: json.xdoc_options->append(r, vvalue);
1.14 misha 403: valid_options++;
404: #endif
1.6 misha 405: } else if(Junction* junction=value->get_junction()){
1.13 moko 406: if(!junction->method || !junction->method->params_names || junction->method->params_names->count() != 3)
407: throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr());
1.6 misha 408: methods->put(key, value);
409: valid_options++;
410: }
411: }
1.22 moko 412:
1.6 misha 413: if(valid_options!=options->count())
414: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.22 moko 415:
416: // special handling for $._default
417: if(VHash* vhash=static_cast<VHash*>(params[1].as(VHASH_TYPE)))
418: if(Value* value=vhash->get_default()) {
419: Junction* junction=value->get_junction();
420: if(!junction || !junction->method || !junction->method->params_names || junction->method->params_names->count() != 3)
421: throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", HASH_DEFAULT_ELEMENT_NAME);
422: json.default_method=value;
423: }
424:
1.6 misha 425: if(methods->count())
426: json.methods=methods;
427: }
1.14 misha 428:
1.21 moko 429: const String& result_string=value_json_string(String::Body(), params[0], json);
1.13 moko 430: String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, 0, &r.charsets);
431: r.write_pass_lang(*new String(result_body, String::L_AS_IS));
1.6 misha 432: }
433:
1.1 misha 434: // constructor
435:
436: MJson::MJson(): Methoded("json") {
437: add_native_method("parse", Method::CT_STATIC, _parse, 1, 2);
1.6 misha 438:
439: add_native_method("string", Method::CT_ANY, _string, 1, 2);
1.1 misha 440: }
E-mail: