|
|
| version 1.28, 2013/08/27 11:27:45 | version 1.41, 2016/03/31 21:46:20 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: @b json parser class. | Parser: @b json parser class. |
| Copyright (c) 2000-2012 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2000-2015 Art. Lebedev Studio (http://www.artlebedev.com) |
| */ | */ |
| #include "classes.h" | #include "classes.h" |
| Line 12 | Line 12 |
| #include "pa_charset.h" | #include "pa_charset.h" |
| #include "pa_charsets.h" | #include "pa_charsets.h" |
| #include "json.h" | #include "pa_json.h" |
| #ifdef XML | #ifdef XML |
| #include "pa_vxdoc.h" | #include "pa_vxdoc.h" |
| Line 29 public: | Line 29 public: |
| // global variable | // global variable |
| DECLARE_CLASS_VAR(json, new MJson, 0); | DECLARE_CLASS_VAR(json, new MJson); |
| // methods | // methods |
| struct Json { | struct Json { |
| Line 47 struct Json { | Line 47 struct Json { |
| String::Language taint; | String::Language taint; |
| bool handle_double; | bool handle_double; |
| bool handle_int; | |
| enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct; | enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct; |
| Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), | Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), |
| request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), distinct(D_EXCEPTION){} | request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), handle_int(true), |
| distinct(D_EXCEPTION){} | |
| bool set_distinct(const String &value){ | bool set_distinct(const String &value){ |
| if (value == "first") distinct = D_FIRST; | if (value == "first") distinct = D_FIRST; |
| Line 95 String* json_string(Json *json, const ch | Line 97 String* json_string(Json *json, const ch |
| String::C result = json->charset !=NULL ? | String::C result = json->charset !=NULL ? |
| Charset::transcode(String::C(value, length), UTF8_charset, *json->charset) : | Charset::transcode(String::C(value, length), UTF8_charset, *json->charset) : |
| String::C(pa_strdup(value, length), length); | String::C(pa_strdup(value, length), length); |
| return new String(result.str, json->taint, result.length); | return new String(result, json->taint); |
| } | } |
| static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){ | static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){ |
| Line 165 static int json_callback(Json *json, int | Line 167 static int json_callback(Json *json, int |
| json->key = json_string(json, value, length); | json->key = json_string(json, value, length); |
| break; | break; |
| case JSON_INT: | case JSON_INT: |
| set_json_value(json, new VDouble( json_string(json, value, length)->as_double() )); | if (json->handle_int){ |
| set_json_value(json, new VDouble( json_string(json, value, length)->as_double() )); | |
| } else { | |
| // JSON_STRING | |
| set_json_value(json, new VString(*json_string(json, value, length))); | |
| } | |
| break; | break; |
| case JSON_FLOAT: | case JSON_FLOAT: |
| if (json->handle_double){ | if (json->handle_double){ |
| Line 198 static const char* json_error_message(in | Line 205 static const char* json_error_message(in |
| "nesting limit", | "nesting limit", |
| "data limit", | "data limit", |
| "comment not allowed by config", | "comment not allowed by config", |
| "unexpected char", | "unexpected character", |
| "missing unicode low surrogate", | "missing unicode low surrogate", |
| "unexpected unicode low surrogate", | "unexpected unicode low surrogate", |
| "error comma out of structure", | "error comma out of structure", |
| Line 209 static const char* json_error_message(in | Line 216 static const char* json_error_message(in |
| extern String::Language get_untaint_lang(const String& lang_name); | extern String::Language get_untaint_lang(const String& lang_name); |
| #define SOURCE_MAX_LEN 60 | |
| void json_exception_with_source(Request& r, const char* msg, const char* json, int offset){ | |
| int i; | |
| int line=0; | |
| int start=0; | |
| int end=strlen(json); | |
| if(offset>end) | |
| offset=end; | |
| for(i = 0; i < offset; i++){ | |
| if(json[i]=='\n'){ | |
| line++; | |
| } | |
| } | |
| if(offset > SOURCE_MAX_LEN/2) | |
| start = offset - SOURCE_MAX_LEN/2; | |
| for(i = offset-1; i>=start; i--){ | |
| if(json[i]=='\n'){ | |
| start=i+1; | |
| break; | |
| } | |
| } | |
| if(start+SOURCE_MAX_LEN < end) | |
| end=start+SOURCE_MAX_LEN; | |
| for(i = offset+1; i<end; i++){ | |
| if(json[i]=='\n'){ | |
| end=i; | |
| break; | |
| } | |
| } | |
| char *source = pa_strdup(json+start, end-start); | |
| int source_offset = offset-start; | |
| if(source[source_offset]=='\n') | |
| source[source_offset]=' '; | |
| for(i = 0; i < source_offset; i++){ | |
| if(source[i]=='\t'){ | |
| source[i]=' '; | |
| } | |
| } | |
| if(r.charsets.source().isUTF8()){ | |
| source=(char *)fixUTF8(source); | |
| if(source_offset>0){ | |
| String s_source(pa_strdup(source,source_offset)); | |
| source_offset=s_source.length(r.charsets.source()); | |
| } | |
| } | |
| throw Exception("json.parse", 0, "%s at line %d\n%s\n%*s", msg, line+1, source, source_offset+1, "^"); | |
| } | |
| static void _parse(Request& r, MethodParams& params) { | static void _parse(Request& r, MethodParams& params) { |
| const String& json_string=params.as_string(0, "json must be string"); | const String& json_string=params.as_string(0, "json must be string"); |
| Line 236 static void _parse(Request& r, MethodPar | Line 304 static void _parse(Request& r, MethodPar |
| json.handle_double=r.process_to_value(*value).as_bool(); | json.handle_double=r.process_to_value(*value).as_bool(); |
| valid_options++; | valid_options++; |
| } | } |
| if(Value* value=options->get("int")) { | |
| json.handle_int=r.process_to_value(*value).as_bool(); | |
| valid_options++; | |
| } | |
| if(Value* value=options->get("distinct")) { | if(Value* value=options->get("distinct")) { |
| const String& sdistinct=value->as_string(); | const String& sdistinct=value->as_string(); |
| if (!json.set_distinct(sdistinct)) | if (!json.set_distinct(sdistinct)) |
| Line 271 static void _parse(Request& r, MethodPar | Line 343 static void _parse(Request& r, MethodPar |
| if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json)) | if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json)) |
| throw Exception("json.parse", 0, "%s", json_error_message(result)); | throw Exception("json.parse", 0, "%s", json_error_message(result)); |
| if(!*json_cstr) | |
| throw Exception("json.parse", 0, "empty string is not valid json"); | |
| const char *first_quote=strchr(json_cstr,'"'); | |
| if(first_quote && first_quote>json_cstr && *(--first_quote) == '\\') | |
| json_exception_with_source(r, "illegal quote escape, json may be tainted", json_cstr, first_quote-json_cstr); | |
| uint32_t processed; | uint32_t processed; |
| if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed)) | if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed)) |
| throw Exception("json.parse", 0, "%s at byte %d", json_error_message(result), processed); | json_exception_with_source(r, json_error_message(result), json_cstr, processed); |
| if (!json_parser_is_done(&parser)) | if (!json_parser_is_done(&parser)) |
| throw Exception("json.parse", 0, "unexpected end of json data"); | json_exception_with_source(r, "unexpected end of json data", json_cstr, processed); |
| json_parser_free(&parser); | json_parser_free(&parser); |
| if (json.result) r.write_no_lang(*json.result); | if (json.result) r.write_no_lang(*json.result); |
| Line 311 public: | Line 390 public: |
| const String& value_json_string(String::Body key, Value& v, Json_options& options); | const String& value_json_string(String::Body key, Value& v, Json_options& options); |
| const String* Json_options::hash_json_string(HashStringValue &hash) { | const String* Json_options::hash_json_string(HashStringValue *hash) { |
| if(!hash.count()) | if(!hash || !hash->count()) |
| return new String("{}", String::L_AS_IS); | return new String("{}", String::L_AS_IS); |
| Json_string_recoursion go_down(*this); | Json_string_recoursion go_down(*this); |
| Line 323 const String* Json_options::hash_json_st | Line 402 const String* Json_options::hash_json_st |
| String *delim=NULL; | String *delim=NULL; |
| indent=get_indent(json_string_recoursion); | indent=get_indent(json_string_recoursion); |
| for(HashStringValue::Iterator i(hash); i; i.next() ){ | for(HashStringValue::Iterator i(*hash); i; i.next() ){ |
| if (delim){ | if (delim){ |
| result << *delim; | result << *delim; |
| } else { | } else { |
| Line 337 const String* Json_options::hash_json_st | Line 416 const String* Json_options::hash_json_st |
| } else { | } else { |
| bool need_delim=false; | bool need_delim=false; |
| for(HashStringValue::Iterator i(hash); i; i.next() ){ | for(HashStringValue::Iterator i(*hash); i; i.next() ){ |
| result << (need_delim ? ",\n\"" : "\""); | result << (need_delim ? ",\n\"" : "\""); |
| result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this); | result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this); |
| need_delim=true; | need_delim=true; |
| Line 358 const String& value_json_string(String:: | Line 437 const String& value_json_string(String:: |
| Value* method=options.methods->get(v.type()); | Value* method=options.methods->get(v.type()); |
| if(!method){ | if(!method){ |
| method=options.methods->first_that<Value*>(based_on, &v); | method=options.methods->first_that<Value*>(based_on, &v); |
| options.methods->put(key, method ? method : VVoid::get()); | options.methods->put(v.type(), method ? method : VVoid::get()); |
| } | } |
| if(method && !method->is_void()) { | if(method && !method->is_void()) { |
| Junction* junction=method->get_junction(); | Junction* junction=method->get_junction(); |
| Line 398 static void _string(Request& r, MethodPa | Line 477 static void _string(Request& r, MethodPa |
| } else if(key == "date" && value->is_string()){ | } else if(key == "date" && value->is_string()){ |
| const String& svalue=value->as_string(); | const String& svalue=value->as_string(); |
| if(!json.set_date_format(svalue)) | if(!json.set_date_format(svalue)) |
| throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string' or 'unix-timestamp'"); | throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string', 'iso-string' or 'unix-timestamp'"); |
| valid_options++; | valid_options++; |
| } else if(key == "indent"){ | } else if(key == "indent"){ |
| if(value->is_string()){ | if(value->is_string()){ |
| Line 416 static void _string(Request& r, MethodPa | Line 495 static void _string(Request& r, MethodPa |
| if(!json.set_file_format(svalue)) | if(!json.set_file_format(svalue)) |
| throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'"); | throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'"); |
| valid_options++; | valid_options++; |
| } else if(key == "void" && value->is_string()){ | |
| const String& svalue=value->as_string(); | |
| if(!json.set_void_format(svalue)) | |
| throw Exception(PARSER_RUNTIME, &svalue, "must be 'string' or 'null'"); | |
| valid_options++; | |
| #ifdef XML | #ifdef XML |
| } else if(key == "xdoc" && (vvalue = value->get_hash())){ | } else if(key == "xdoc" && (vvalue = value->get_hash())){ |
| json.xdoc_options=new XDocOutputOptions(); | json.xdoc_options=new XDocOutputOptions(); |
| Line 436 static void _string(Request& r, MethodPa | Line 520 static void _string(Request& r, MethodPa |
| // special handling for $._default | // special handling for $._default |
| if(VHash* vhash=static_cast<VHash*>(params[1].as(VHASH_TYPE))) | if(VHash* vhash=static_cast<VHash*>(params[1].as(VHASH_TYPE))) |
| if(Value* value=vhash->get_default()) { | if(Value* value=vhash->get_default()) { |
| if(!value->is_string()){ | |
| Junction* junction=value->get_junction(); | Junction* junction=value->get_junction(); |
| if(!junction || !junction->method || !junction->method->params_names || junction->method->params_names->count() != 3) | if(!junction || !junction->method || !junction->method->params_names || junction->method->params_names->count() != 3) |
| throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", HASH_DEFAULT_ELEMENT_NAME); | throw Exception(PARSER_RUNTIME, 0, "$.%s must be string or parser method with 3 parameters", HASH_DEFAULT_ELEMENT_NAME); |
| } | |
| json.default_method=value; | json.default_method=value; |
| } | } |
| Line 446 static void _string(Request& r, MethodPa | Line 532 static void _string(Request& r, MethodPa |
| json.methods=methods; | json.methods=methods; |
| } | } |
| const String& result_string=value_json_string(String::Body(), params[0], json); | const String& result_string=value_json_string(String::Body(), r.process_to_value(params[0]), json); |
| String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets); | String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets); |
| r.write_pass_lang(*new String(result_body, String::L_AS_IS)); | r.write_pass_lang(*new String(result_body, String::L_AS_IS)); |
| } | } |