Annotation of parser3/src/classes/json.C, revision 1.23

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"
                     15: #include "JSON_parser.h"
                     16: 
1.14      misha      17: #ifdef XML
                     18: #include "pa_vxdoc.h"
                     19: #endif
                     20: 
1.23    ! moko       21: volatile const char * IDENT_JSON_C="$Id: json.C,v 1.22 2012-05-29 21:57:01 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.3       moko       94: String* json_string(Json *json, const JSON_value* value){
                     95:        String::C result = json->charset !=NULL ? 
1.23    ! moko       96:                Charset::transcode(String::C(value->vu.str.value, value->vu.str.length), UTF8_charset, *json->charset) :
        !            97:                String::C(pa_strdup(value->vu.str.value, value->vu.str.length), value->vu.str.length);
        !            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: 
                    111: static int json_callback(Json *json, int type, const JSON_value* value)
                    112: {
                    113:        switch(type) {
                    114:                case JSON_T_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:                }
                    125:                case JSON_T_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:                }
                    139:                case JSON_T_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:                }
                    150:                case JSON_T_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;
                    164:                case JSON_T_KEY:
                    165:                        json->key = json_string(json, value);
1.16      misha     166:                        break;
1.1       misha     167:                case JSON_T_INTEGER:
1.20      moko      168:                        set_json_value(json, new VDouble((double)value->vu.integer_value));
1.1       misha     169:                        break;
                    170:                case JSON_T_FLOAT:
                    171:                        if (json->handle_double){
1.3       moko      172:                                set_json_value(json, new VDouble( json_string(json, value)->as_double() ));
1.1       misha     173:                                break;
                    174:                        } // else is JSON_T_STRING
                    175:                case JSON_T_STRING:
1.3       moko      176:                        set_json_value(json, new VString(*json_string(json, value)));
1.1       misha     177:                        break;
                    178:                case JSON_T_NULL:
1.18      moko      179:                        set_json_value(json, VVoid::get());
1.1       misha     180:                        break;
                    181:                case JSON_T_TRUE:
                    182:                        set_json_value(json, &VBool::get(true));
                    183:                        break;
                    184:                case JSON_T_FALSE:
                    185:                        set_json_value(json, &VBool::get(false));
                    186:                        break; 
                    187:        }
                    188:        return 1;
                    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,
                    194:                "invalid char",
                    195:                "invalid keyword",
                    196:                "invalid escape sequence",
                    197:                "invalid unicode sequence",
                    198:                "invalid number",
                    199:                "nesting depth reached",
                    200:                "unbalanced collection",
                    201:                "expected key",
                    202:                "expected colon",
                    203:                "out of memory"
                    204:        };
                    205:        return error_messages[error_code];
                    206: }
                    207: 
1.23    ! moko      208: extern String::Language get_untaint_lang(const String& lang_name);
        !           209: 
1.1       misha     210: static void _parse(Request& r, MethodParams& params) {
1.3       moko      211:        const String& json_string=params.as_string(0, "json must be string");
                    212: 
                    213:        Json json(r.charsets.source().isUTF8() ? NULL : &(r.charsets.source()));
1.1       misha     214: 
                    215:        JSON_config config;
                    216:        init_JSON_config(&config);
                    217: 
                    218:        config.depth                  = 19;
                    219:        config.callback               = (JSON_parser_callback)&json_callback;
                    220:        config.allow_comments         = 1;
                    221:        config.handle_floats_manually = 1;
                    222:        config.callback_ctx           = &json;
                    223: 
                    224:        if(params.count() == 2)
                    225:                if(HashStringValue* options=params.as_hash(1)) {
                    226:                        int valid_options=0;
                    227:                        if(Value* value=options->get("depth")) {
1.4       moko      228:                                config.depth=r.process_to_value(*value).as_int();
1.1       misha     229:                                valid_options++;
                    230:                        }
                    231:                        if(Value* value=options->get("double")) {
1.4       moko      232:                                json.handle_double=r.process_to_value(*value).as_bool();
                    233:                                valid_options++;
                    234:                        }
                    235:                        if(Value* value=options->get("distinct")) {
                    236:                                const String& sdistinct=value->as_string();
                    237:                                if (!json.set_distinct(sdistinct))
                    238:                                        throw Exception(PARSER_RUNTIME, &sdistinct, "must be 'first', 'last' or 'all'");
1.1       misha     239:                                valid_options++;
                    240:                        }
1.23    ! moko      241:                        if(Value* value=options->get("taint")) {
        !           242:                                json.taint=get_untaint_lang(value->as_string());
        !           243:                                valid_options++;
        !           244:                        }
1.1       misha     245:                        if(Value* value=options->get("object")) {
1.16      misha     246:                                json.hook_object=value->get_junction();
1.3       moko      247:                                json.request=&r;
1.16      misha     248:                                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     249:                                        throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters");
                    250:                                valid_options++;
                    251:                        }
1.16      misha     252:                        if(Value* value=options->get("array")) {
                    253:                                json.hook_array=value->get_junction();
                    254:                                json.request=&r;
                    255:                                if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_names->count() == 2))
                    256:                                        throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters");
                    257:                                valid_options++;
                    258:                        }
1.1       misha     259:                        if(valid_options!=options->count())
                    260:                                throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                    261:                }
                    262: 
1.2       misha     263:        const String::Body json_body = json_string.cstr_to_string_body_untaint(String::L_JSON, 0, &(r.charsets));
1.1       misha     264:        const char *json_cstr = json.charset != NULL ? Charset::transcode(json_body, *json.charset, UTF8_charset).cstr() : json_body.cstr();
                    265: 
1.3       moko      266:        struct JSON_parser_struct* jc = new_JSON_parser(&config);
                    267: 
1.1       misha     268:        for (const char *c=json_cstr; *c; c++){
                    269:                if (!JSON_parser_char(jc, *((const unsigned char *)c))) {
                    270:                        throw Exception("json.parse", 0, "%s at byte %d", json_error_message(JSON_parser_get_last_error(jc)), c-json_cstr);
                    271:                }
                    272:        }
                    273: 
                    274:        if (!JSON_parser_done(jc)) {
                    275:                throw Exception("json.parse", 0, "%s at the end", json_error_message(JSON_parser_get_last_error(jc)));
                    276:        }
                    277:        
                    278:        delete_JSON_parser(jc);
                    279: 
                    280:        if (json.result) r.write_no_lang(*json.result);
                    281: }
                    282: 
1.8       moko      283: char *get_indent(uint level){
                    284:        static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
                    285:        if (!cache[level]){
                    286:                char *result =  static_cast<char*>(pa_gc_malloc_atomic(level+1));
                    287:                memset(result, '\t', level);
1.9       moko      288:                result[level]='\0';
1.8       moko      289:                return cache[level]=result;
                    290:        }
                    291:        return cache[level];
                    292: }
                    293: 
1.21      moko      294: const String& value_json_string(String::Body key, Value& v, Json_options& options);
1.6       misha     295: 
1.21      moko      296: const String* Json_options::hash_json_string(HashStringValue &hash) {
1.6       misha     297:        if(!hash.count())
1.21      moko      298:                return new String("{}", String::L_AS_IS);
1.8       moko      299: 
1.21      moko      300:        uint level = r->json_string_recoursion_go_down();
1.8       moko      301: 
                    302:        String& result = *new String("{\n", String::L_AS_IS);
                    303: 
1.21      moko      304:        if (indent){
1.8       moko      305: 
                    306:                String *delim=NULL;
1.21      moko      307:                indent=get_indent(level);
1.8       moko      308:                for(HashStringValue::Iterator i(hash); i; i.next() ){
                    309:                        if (delim){
                    310:                                result << *delim;
                    311:                        } else {
1.21      moko      312:                                result << indent << "\"";
                    313:                                delim = new String(",\n", String::L_AS_IS); *delim << indent << "\"";
1.8       moko      314:                        }
1.21      moko      315:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      316:                }
1.21      moko      317:                result << "\n" << (indent=get_indent(level-1)) << "}";
1.6       misha     318: 
1.8       moko      319:        } else {
                    320: 
                    321:                bool need_delim=false;
                    322:                for(HashStringValue::Iterator i(hash); i; i.next() ){
                    323:                        result << (need_delim ? ",\n\"" : "\"");
1.21      moko      324:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      325:                        need_delim=true;
                    326:                }
                    327:                result << "\n}";
1.6       misha     328: 
                    329:        }
                    330: 
1.21      moko      331:        r->json_string_recoursion_go_up();
                    332:        return &result;
1.6       misha     333: }
                    334: 
1.21      moko      335: static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) {
1.15      misha     336:        return v->is(key.cstr());
                    337: }
                    338:                                        
1.21      moko      339: const String& value_json_string(String::Body key, Value& v, Json_options& options) {
                    340:        if(options.methods) {
                    341:                Value* method=options.methods->get(v.type());
                    342:                if(!method){
                    343:                        method=options.methods->first_that<Value*>(based_on, &v);
                    344:                        options.methods->put(key, method ? method : VVoid::get());
                    345:                }
                    346:                if(method && !method->is_void()) {
1.6       misha     347:                        Junction* junction=method->get_junction();
1.21      moko      348:                        VMethodFrame frame(*junction->method, options.r->method_frame, junction->self);
1.6       misha     349: 
1.21      moko      350:                        Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()};
1.13      moko      351:                        frame.store_params(params, 3);
1.6       misha     352: 
1.21      moko      353:                        options.r->execute_method(frame);
1.6       misha     354: 
                    355:                        return frame.result().as_string();
                    356:                }
1.15      misha     357:        }
1.6       misha     358: 
1.21      moko      359:        options.key=key;
1.6       misha     360:        return *v.get_json_string(options);
                    361: }
                    362: 
                    363: static void _string(Request& r, MethodParams& params) {
                    364:        Json_options json(&r);
                    365: 
                    366:        if(params.count() == 2)
                    367:                if(HashStringValue* options=params.as_hash(1)) {
                    368:                        json.params=params.get(1);
                    369:                        HashStringValue* methods=new HashStringValue();
                    370:                        int valid_options=0;
1.14      misha     371:                        HashStringValue* vvalue;
1.6       misha     372:                        for(HashStringValue::Iterator i(*options); i; i.next() ){
                    373:                                String::Body key=i.key();
                    374:                                Value* value=i.value();
                    375:                                if(key == "skip-unknown"){
                    376:                                        json.skip_unknown=r.process_to_value(*value).as_bool();
                    377:                                        valid_options++;
                    378:                                } else if(key == "date" && value->is_string()){
                    379:                                        const String& svalue=value->as_string();
                    380:                                        if(!json.set_date_format(svalue))
                    381:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string' or 'unix-timestamp'");
                    382:                                        valid_options++;
1.8       moko      383:                                } else if(key == "indent"){
                    384:                                        json.indent=r.process_to_value(*value).as_bool() ? "":NULL;
                    385:                                        valid_options++;
1.6       misha     386:                                } else if(key == "table" && value->is_string()){
                    387:                                        const String& svalue=value->as_string();
                    388:                                        if(!json.set_table_format(svalue))
1.13      moko      389:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.6       misha     390:                                        valid_options++;
                    391:                                } else if(key == "file" && value->is_string()){
                    392:                                        const String& svalue=value->as_string();
                    393:                                        if(!json.set_file_format(svalue))
1.19      misha     394:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'");
1.6       misha     395:                                        valid_options++;
1.14      misha     396: #ifdef XML
                    397:                                } else if(key == "xdoc" && (vvalue = value->get_hash())){
                    398:                                        json.xdoc_options=new XDocOutputOptions(r, vvalue);
                    399:                                        valid_options++;
                    400: #endif
1.6       misha     401:                                } else if(Junction* junction=value->get_junction()){
1.13      moko      402:                                        if(!junction->method || !junction->method->params_names || junction->method->params_names->count() != 3)
                    403:                                                throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr());
1.6       misha     404:                                        methods->put(key, value);
                    405:                                        valid_options++;
                    406:                                }
                    407:                        }
1.22      moko      408: 
1.6       misha     409:                        if(valid_options!=options->count())
                    410:                                throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.22      moko      411: 
                    412:                        // special handling for $._default 
                    413:                        if(VHash* vhash=static_cast<VHash*>(params[1].as(VHASH_TYPE)))
                    414:                                if(Value* value=vhash->get_default()) {
                    415:                                        Junction* junction=value->get_junction();
                    416:                                        if(!junction || !junction->method || !junction->method->params_names || junction->method->params_names->count() != 3)
                    417:                                                throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", HASH_DEFAULT_ELEMENT_NAME);
                    418:                                        json.default_method=value;
                    419:                                }
                    420: 
1.6       misha     421:                        if(methods->count())
                    422:                                json.methods=methods;
                    423:                }
1.14      misha     424: 
1.21      moko      425:        const String& result_string=value_json_string(String::Body(), params[0], json);
1.13      moko      426:        String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, 0, &r.charsets);
                    427:        r.write_pass_lang(*new String(result_body, String::L_AS_IS));
1.6       misha     428:  }
                    429: 
1.1       misha     430: // constructor
                    431: 
                    432: MJson::MJson(): Methoded("json") {
                    433:        add_native_method("parse", Method::CT_STATIC, _parse, 1, 2);
1.6       misha     434: 
                    435:        add_native_method("string", Method::CT_ANY, _string, 1, 2);
1.1       misha     436: }

E-mail: