--- parser3/src/main/pa_request.C 2021/01/02 23:01:11 1.415 +++ parser3/src/main/pa_request.C 2026/09/26 17:41:10 1.445 @@ -1,12 +1,13 @@ /** @file Parser: request class main part. @see compile.C and execute.C. - Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) - Author: Alexandr Petrosian (http://paf.design.ru) + Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) + Authors: Konstantin Morshnev , Alexandr Petrosian */ #include "pa_sapi.h" #include "pa_common.h" +#include "pa_dir.h" #include "pa_os.h" #include "pa_request.h" #include "pa_wwrapper.h" @@ -33,8 +34,9 @@ #include "pa_vmemory.h" #include "pa_vconsole.h" #include "pa_vdate.h" +#include "pa_varray.h" -volatile const char * IDENT_PA_REQUEST_C="$Id: pa_request.C,v 1.415 2021/01/02 23:01:11 moko Exp $" IDENT_PA_REQUEST_H IDENT_PA_REQUEST_CHARSETS_H IDENT_PA_REQUEST_INFO_H IDENT_PA_VCONSOLE_H; +volatile const char * IDENT_PA_REQUEST_C="$Id: pa_request.C,v 1.445 2026/09/26 17:41:10 moko Exp $" IDENT_PA_REQUEST_H IDENT_PA_REQUEST_CHARSETS_H IDENT_PA_REQUEST_INFO_H IDENT_PA_VCONSOLE_H; // consts @@ -43,8 +45,10 @@ volatile const char * IDENT_PA_REQUEST_C /// content type of response when no $MAIN:defaults.content-type defined const char* DEFAULT_CONTENT_TYPE="text/html"; -const uint LOOP_LIMIT=20000; -const uint EXECUTE_RECOURSION_LIMIT=1000; +const uint LOOP_LIMIT=100000; +const uint ARRAY_LIMIT=1000000; +const uint EXECUTE_RECURSION_LIMIT=1000; +const uint EXECUTE_RECURSION_RESERVE=100; const uint HTTPD_TIMEOUT=4; const size_t FILE_SIZE_LIMIT=512*1024*1024; @@ -55,10 +59,6 @@ const size_t FILE_SIZE_LIMIT=512*1024*10 #define USE_METHOD_NAME "use" #define AUTOUSE_METHOD_NAME "autouse" -#define EXCEPTION_TYPE_PART_NAME "type" -#define EXCEPTION_SOURCE_PART_NAME "source" -#define EXCEPTION_COMMENT_PART_NAME "comment" - #define ORIGIN_KEY "origin" // globals @@ -68,15 +68,11 @@ const String auto_method_name(AUTO_METHO static const String use_method_name(USE_METHOD_NAME); static const String autouse_method_name(AUTOUSE_METHOD_NAME); -const String exception_type_part_name(EXCEPTION_TYPE_PART_NAME); -const String exception_source_part_name(EXCEPTION_SOURCE_PART_NAME); -const String exception_comment_part_name(EXCEPTION_COMMENT_PART_NAME); -const String exception_handled_part_name(EXCEPTION_HANDLED_PART_NAME); - static const String origin_key(ORIGIN_KEY); int pa_loop_limit=LOOP_LIMIT; -int pa_execute_recoursion_limit=EXECUTE_RECOURSION_LIMIT; +int pa_array_limit=ARRAY_LIMIT; +int pa_execute_recursion_limit=EXECUTE_RECURSION_LIMIT; int pa_httpd_timeout=HTTPD_TIMEOUT; size_t pa_file_size_limit=FILE_SIZE_LIMIT; @@ -101,7 +97,8 @@ static const String getter_protected_nam static const String locals_name("LOCALS"); static const String limits_name("LIMITS"); static const String loop_limit_name("max_loop"); -static const String recoursion_limit_name("max_recoursion"); +static const String array_limit_name("max_array_size"); +static const String recursion_limit_name("max_recursion"); static const String file_size_limit_name("max_file_size"); static const String lock_wait_timeout_name("lock_wait_timeout"); static const String httpd_name("HTTPD"); @@ -122,6 +119,9 @@ static const String content_disposition_ static const String content_disposition_inline(CONTENT_DISPOSITION_INLINE); static const String content_disposition_attachment(CONTENT_DISPOSITION_ATTACHMENT); +const String content_disposition_filename_name(CONTENT_DISPOSITION_FILENAME_NAME); +const String content_disposition_filename_name_asterisk("filename*"); + // defines #define CHARSET_NAME_UPPER "CHARSET" @@ -131,9 +131,18 @@ static const String content_disposition_ VStateless_class& VClassMAIN_create(); // +void Request::recursion_limit_reached() { + if(execute_recursion_limit>pa_execute_recursion_limit) + throw Recursion_limit_exception(); + + execute_recursion_limit+=EXECUTE_RECURSION_RESERVE; + throw Exception(PARSER_RUNTIME, 0, "call canceled - endless recursion detected"); +} + Request::Request(SAPI_Info& asapi_info, Request_info& arequest_info, String::Language adefault_lang): // private - anti_endless_execute_recoursion(0), + anti_endless_execute_recursion(0), + execute_recursion_limit(pa_execute_recursion_limit), // public allow_class_replace(false), @@ -168,8 +177,8 @@ Request::Request(SAPI_Info& asapi_info, pa_register_thread_request(*this); // file_no=0 => unknown - file_list+="UNKNOWN"; - file_list+="-body of process-"; // pseudo_file_no__process + file_list+=String::Body("UNKNOWN"); + file_list+=String::Body("-body of process-"); // pseudo_file_no__process // maybe expire old caches cache_managers->maybe_expire(); @@ -242,6 +251,16 @@ VStateless_class& Request::get_class_ref return *result; } +bool Request::add_class(const char* atype, VStateless_class *aclass){ + if(!classes().put_dont_replace(String::Body(atype), aclass)) + return true; + if(!allow_class_replace && strcmp(atype, VARRAY_TYPE)) + return false; + classes().remove(String::Body(atype)); + classes().put(atype, aclass); + return true; +} + static void load_charset(HashStringValue::key_type akey, HashStringValue::value_type avalue, Request_charsets* charsets) { pa_charsets.load_charset(*charsets, akey, avalue->as_string()); } @@ -325,17 +344,25 @@ void Request::configure_admin(VStateless if(pa_loop_limit==0) pa_loop_limit=INT_MAX; }, "LIMITS.%s must be int"); - pa_execute_recoursion_limit=EXECUTE_RECOURSION_LIMIT; - CONF_OPTION(limits, recoursion_limit_name, { - pa_execute_recoursion_limit=option->as_int(); - if(pa_execute_recoursion_limit==0) pa_execute_recoursion_limit=INT_MAX; + pa_array_limit=ARRAY_LIMIT; + CONF_OPTION(limits, array_limit_name, { + pa_array_limit=option->as_int(); + if(pa_array_limit==0) pa_array_limit=INT_MAX; + }, "LIMITS.%s must be int"); + + pa_execute_recursion_limit=EXECUTE_RECURSION_LIMIT; + CONF_OPTION(limits, recursion_limit_name, { + pa_execute_recursion_limit=option->as_int(); + if(pa_execute_recursion_limit<=0) pa_execute_recursion_limit=INT_MAX; + pa_execute_recursion_limit=min(pa_execute_recursion_limit, INT_MAX-EXECUTE_RECURSION_RESERVE); }, "LIMITS.%s must be int"); + execute_recursion_limit=pa_execute_recursion_limit; pa_file_size_limit=FILE_SIZE_LIMIT; CONF_OPTION(limits, file_size_limit_name, { double limit=option->as_double(); if(limit >= (double)SSIZE_MAX) - throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less then %.15g", file_size_limit_name.cstr(), (double)SSIZE_MAX); + throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less than %.15g", file_size_limit_name.cstr(), (double)SSIZE_MAX); pa_file_size_limit=(size_t)limit; if(pa_file_size_limit==0) pa_file_size_limit=SSIZE_MAX; @@ -345,7 +372,7 @@ void Request::configure_admin(VStateless CONF_OPTION(limits, lock_wait_timeout_name, { double limit=option->as_double(); if(limit >= 3600*24) - throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less then %d", lock_wait_timeout_name.cstr(), 3600*24); + throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less than %d", lock_wait_timeout_name.cstr(), 3600*24); pa_lock_attempts=(unsigned int)(limit*2)+1; }, "LIMITS.%s must be number"); @@ -369,7 +396,7 @@ void Request::configure_admin(VStateless methoded_array().configure_admin(*this); } -const char* Request::get_exception_cstr(const Exception& e, Request::Exception_details& details) { +const char* Request::get_exception_cstr(const Exception& e, VException& details) { #define PA_URI_FORMAT "%s: " #define PA_COMMENT_TYPE_FORMAT "%s [%s]" @@ -407,7 +434,7 @@ const char* Request::get_exception_cstr( return result; } -Table &Request::Exception_trace::table(Request &r){ +Table &Request::Exception_trace::table(Request &r, const String* problem_source, size_t bottom, size_t top){ // $stack[^table::create{name file lineno colno}] Table::columns_type stack_trace_columns(new ArrayString); *stack_trace_columns+=new String("name"); @@ -416,8 +443,8 @@ Table &Request::Exception_trace::table(R *stack_trace_columns+=new String("colno"); Table& stack_trace=*new Table(stack_trace_columns); - if(!is_empty()/*signed!*/) - for(size_t i=bottom_index(); iget_table()) @@ -460,9 +484,6 @@ void Request::configure() { */ void Request::core(const char* config_filespec, bool header_only, const String &amain_method_name, const String* amain_class_name) { - VFile* body_file=NULL; - bool as_attachment=false; - try { // loading config if(config_filespec) @@ -489,7 +510,7 @@ void Request::core(const char* config_fi // extract response body Value* body_value=response.fields().get(download_name_upper); // $response:download? - as_attachment=body_value!=0; + bool as_attachment=body_value!=0; if(!body_value) body_value=response.fields().get(body_name_upper); // $response:body if(!body_value) @@ -504,31 +525,36 @@ void Request::core(const char* config_fi }); } - body_file=body_value->as_vfile(flang, &charsets); - + // can throw exceptions while handling $response:download[] + output_result(body_value->as_vfile(flang, &charsets), header_only, as_attachment); } catch(const Exception& e) { // request handling problem - try { - // we're returning not result, but error explanation - Request::Exception_details details=get_details(e); - const char* exception_cstr=get_exception_cstr(e, details); + // OK, as native execution stack has unwound + anti_endless_execute_recursion=0; - // reset language to default - flang=fdefault_lang; - // reset response - response.fields().clear(); + // we're returning not result, but error explanation + VException& details=*new VException(*this, e); + const char* exception_cstr=get_exception_cstr(e, details); + + // reset language to default + flang=fdefault_lang; + // reset response + response.fields().clear(); + SAPI::clear_headers(sapi_info); - // this is what we'd return in $response:body - const String* body_string=0; + // this is what we'd return in $response:body + const String* body_string=0; + try { // maybe we'd be lucky enough as to report an error in a gracefull way... if(const Method *method=main_class.get_method(*new String(UNHANDLED_EXCEPTION_METHOD_NAME))) { // preparing parameters to @unhandled_exception[exception;stack] - Table& stack_trace=exception_trace.table(*this); + Table& stack_trace=exception_trace.table(*this, details.problem_source); + details.expire_trace(); exception_trace.clear(); // forget all about previous life, in case there would be error inside of this method, error handled would not be mislead by old stack contents (see extract_origin) - Value *params[]={&details.vhash, new VTable(&stack_trace)}; + Value *params[]={&details, new VTable(&stack_trace)}; METHOD_FRAME_ACTION(*method, 0 /*no caller*/, main_class, { frame.store_params(params, 2); call(frame); @@ -537,31 +563,27 @@ void Request::core(const char* config_fi } // conditionally log it - Value* vhandled=details.vhash.hash().get(exception_handled_part_name); + Value* vhandled=details.handled(); if(!vhandled || !vhandled->as_bool()) { SAPI::log(sapi_info, "%s", exception_cstr); } + } catch(const Exception& e) { // exception in @unhandled_exception + VException& details=*new VException(*this, e); + // logging both initial and new exceptions + SAPI::log(sapi_info, "%s", exception_cstr); + SAPI::log(sapi_info, "Exception in @unhandled_exception at %s", get_exception_cstr(e, details)); + SAPI::send_error(sapi_info, "Exception in @unhandled_exception, details are available in Parser error log."); + return; + } - if(body_string) { // could report an error beautifully? - VString body_vstring(*body_string); - - body_file=body_vstring.as_vfile(flang, &charsets); - as_attachment=false; - } else { - // doing that ugly - SAPI::send_error(sapi_info, exception_cstr, !strcmp(e.type(), "file.missing") ? "404" : "500"); - return; - } - - } catch(const Exception& e) { // exception in unhandled exception - Request::Exception_details details=get_details(e); - // unconditionally log the beast in exception handler - throw Exception(0, 0, "Unhandled exception in %s", get_exception_cstr(e, details)); + if(body_string) { // could report an error beautifully? + VString body_vstring(*body_string); + output_result(body_vstring.as_vfile(flang, &charsets), header_only, false); + } else { + // doing that ugly + SAPI::send_error(sapi_info, exception_cstr, strcmp(e.type(), "file.missing") ? "500" : "404"); } } - - // write out the result outside of try as network exceptions should not be handled by parser code. - output_result(body_file, header_only, as_attachment); } uint Request::register_file(String::Body file_spec) { @@ -681,7 +703,7 @@ void Request::use_buf(VStateless_class& ArrayClass& cclasses=compile(&aclass, source, main_alias, file_no, line_no_offset); VString* vfilespec= - new VString(*new String(file_list[file_no], String::L_TAINTED)); + new VString(file_list[file_no]); for(size_t i=0; iname*/); // locate and execute possible @auto[] static - execute_method_if_exists(cclass, auto_method_name, vfilespec); + execute_auto_method_if_exists(cclass, auto_method_name, vfilespec); cclass.enable_default_setter(); } @@ -707,18 +729,14 @@ const String& Request::relative(const ch } const String& Request::full_disk_path(const String& relative_name) { + if(is_os_absolute_path(relative_name)) + return relative_name; if(relative_name.first_char()=='/') { String& result=*new String(pa_strdup(request_info.document_root)); result << relative_name; return result; } - if(relative_name.pos("://")!=STRING_NOT_FOUND // something like "http://xxx" -#ifdef WIN32 - || relative_name.pos(":")==1 // DRIVE: - || relative_name.starts_with("\\\\") // UNC1 - || relative_name.starts_with("//") // UNC2 -#endif - ) + if(relative_name.starts_with("http://") || relative_name.starts_with("parser://")) return relative_name; return relative(request_info.path_translated ? request_info.path_translated : request_info.document_root, relative_name); @@ -755,10 +773,10 @@ static void output_sole_piece(Request& r output=Charset::transcode(output, r.charsets.source(), r.charsets.client()); // prepare header: Content-Length - SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, format(output.length, "%u")); + SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, pa_uitoa(output.length)); // send header - SAPI::send_header(r.sapi_info); + SAPI::send_headers(r.sapi_info); // send body if(!header_only) @@ -787,9 +805,9 @@ static void parse_range(const String* s, while(*p==' ' || *p=='\t') p++; if(*p >= '0' && *p <= '9'){ - const char *s=p; + const char *n=p; while(*p>='0' && *p<='9') p++; - r.start = pa_atoul(pa_strdup(s,p-s)); + r.start = pa_atoul(pa_strdup(n, p-n)); // ((uint64_t)-1) is treated as UNSET } while(*p==' ' || *p=='\t') p++; @@ -799,9 +817,9 @@ static void parse_range(const String* s, while(*p==' ' || *p=='\t') p++; if(*p >= '0' && *p <= '9'){ - const char *s=p; + const char *n=p; while(*p>='0' && *p<='9') p++; - r.end = pa_atoul(pa_strdup(s,p-s)); + r.end = pa_atoul(pa_strdup(n, p-n)); // ((uint64_t)-1) is treated as UNSET } while(*p==' ' || *p=='\t') p++; @@ -822,13 +840,12 @@ struct Send_range_action_info { static void send_range(struct stat& /*finfo*/, int f, const String& /*file_spec*/, void *context){ Send_range_action_info &info = *(Send_range_action_info*)context; - SAPI::send_header(info.r->sapi_info); + SAPI::send_headers(info.r->sapi_info); pa_lseek(f, info.offset, SEEK_SET); - const size_t BUFSIZE = 128*0x400; - char buf[BUFSIZE]; + char buf[FILE_BUFFER_SIZE]; do{ - size_t to_read = info.part_length < BUFSIZE ? (size_t)info.part_length : BUFSIZE; + size_t to_read = info.part_length < FILE_BUFFER_SIZE ? (size_t)info.part_length : FILE_BUFFER_SIZE; size_t to_write = file_block_read(f, buf, to_read); if(to_write == 0) @@ -860,6 +877,8 @@ static void output_pieces(Request& r, bo return SAPI::send_error(r.sapi_info, "", "416"); if(rg.start == UNSET && rg.end != UNSET){ + if(rg.end == 0) + return SAPI::send_error(r.sapi_info, "", "416"); if(rg.end > content_length) rg.end = content_length; rg.start = content_length - rg.end; @@ -887,13 +906,13 @@ static void output_pieces(Request& r, bo } } - SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, format((double)part_length, "%.15g")); + SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, pa_uitoa(part_length)); if(add_last_modified) SAPI::add_header_attribute(r.sapi_info, "last-modified", attributed_meaning_to_string(date, String::L_AS_IS, true).cstr()); if(header_only){ - SAPI::send_header(r.sapi_info); + SAPI::send_headers(r.sapi_info); } else { Send_range_action_info info = { &r, offset, part_length}; file_read_action_under_lock(r.full_disk_path(filename), "send", send_range, &info); @@ -917,9 +936,13 @@ void Request::output_result(VFile* body_ // Content-Disposition, use $.name[] to avoid const String* disposition_name = sname ? sname->is_empty() ? NULL : sname : sfile; if(disposition_name) { + String& filename = *new String(pa_filename(disposition_name->cstr()), String::L_URI); + String& filename_asterisk = *new String(charsets.client().NAME(), String::L_CLEAN) << (*new String("''")) << filename; + VHash& hash=*new VHash(); hash.hash().put(value_name, new VString(as_attachment ? content_disposition_attachment : content_disposition_inline)); - hash.hash().put(content_disposition_filename_name, new VString(*new String(*disposition_name, String::L_HTTP_HEADER))); + hash.hash().put(content_disposition_filename_name, new VString(filename)); + hash.hash().put(content_disposition_filename_name_asterisk, new VString(filename_asterisk)); response.fields().put(content_disposition_name_upper, &hash); } @@ -952,9 +975,8 @@ void Request::output_result(VFile* body_ VDate* vdate=0; if(Value* v=body_file->fields().get("mdate")) { - if(Value* vdatep=v->as(VDATE_TYPE)) - vdate=static_cast(vdatep); - else + vdate=dynamic_cast(v); + if(!vdate) throw Exception(PARSER_RUNTIME, 0, "mdate must be a date"); } if(!vdate) @@ -1010,47 +1032,83 @@ const String& Request::transcode(const x } #endif -Request::Exception_details Request::get_details(const Exception& e) { - const String* problem_source=e.problem_source(); - VHash& vhash=*new VHash; HashStringValue& hash=vhash.hash(); - Operation::Origin origin={0, 0, 0}; +VException::VException(Request& r, const Exception& e): + fexception(e), fhandled(&VBool::get(false)), + ftrace_bottom(r.exception_trace.bottom_index()), ftrace_top(r.exception_trace.top_index()), + origin(Operation::Origin::create(0, 0, 0)), problem_source(e.problem_source()) { + Request::Exception_trace& exception_trace=r.exception_trace; if(!exception_trace.is_empty()) { - Trace bottom=exception_trace.bottom_value(); + Request::Trace bottom=exception_trace.bottom_value(); origin=bottom.origin(); - if(!problem_source) { // we don't know who trigged the bug + if(!problem_source) // we don't know who trigged the bug problem_source=bottom.name(); // we usually know source of next-from-throw-point exception did that - exception_trace.set_bottom_index(exception_trace.bottom_index()+1); - } else if (bottom.name()==problem_source) { // it is that same guy? - exception_trace.set_bottom_index(exception_trace.bottom_index()+1); // throw away that trace - } else { - // stack top contains not us, leaving intact to help ^throw - } } + if(origin.file_no) + ffile=r.file_list[origin.file_no]; +} - // $.type - if(const char* type=e.type(true)) - hash.put(exception_type_part_name, new VString(*new String(type))); - - // $.source - if(problem_source) - hash.put(exception_source_part_name, new VString(*new String(*problem_source, String::L_TAINTED))); - - // $.file $.lineno $.colno - if(origin.file_no){ - hash.put("file", new VString(*new String(file_list[origin.file_no], String::L_TAINTED))); - hash.put("lineno", new VInt(1+origin.line)); - hash.put("colno", new VInt(1+origin.col)); - } +Value* VException::handled() { + return fhash ? fhash->get(Symbols::HANDLED_SYMBOL) : fhandled; +} - // $.comment - if(const char* comment=e.comment(true)) - hash.put(exception_comment_part_name, new VString(*new String(comment, String::L_TAINTED))); +Table& VException::stack(Request& r) { + if(ftrace_top==size_t(-1)) + throw Exception(PARSER_RUNTIME, 0, "exception stack used outside of context"); + return r.exception_trace.table(r, problem_source, ftrace_bottom, ftrace_top); +} + +Value* VException::get_element(const String& name) { + if(!fhash) { + if(SYMBOLS_EQ(name,HANDLED_SYMBOL)) + return fhandled; + if(SYMBOLS_EQ(name,TYPE_SYMBOL)) + return fexception.type(true) ? new VString(*new String(fexception.type())) : 0; + if(SYMBOLS_EQ(name,SOURCE_SYMBOL)) + return problem_source ? new VString(*problem_source) : 0; + if(SYMBOLS_EQ(name,COMMENT_SYMBOL)) + return fexception.comment(true) ? new VString(fexception.comment()) : 0; + if(SYMBOLS_EQ(name,FILE_SYMBOL)) + return origin.file_no ? new VString(ffile) : 0; + if(SYMBOLS_EQ(name,LINENO_SYMBOL)) + return origin.file_no ? new VInt(1+origin.line) : 0; + if(SYMBOLS_EQ(name,COLNO_SYMBOL)) + return origin.file_no ? new VInt(1+origin.col) : 0; + if(SYMBOLS_EQ(name,FIELDS_SYMBOL)) + return this; + } + // Computed field names need not be interned: use ordinary hash lookup. + return VHashReference::get_element(name); +} + +const VJunction* VException::put_element(const String& name, Value* value) { + if(!fhash && SYMBOLS_EQ(name,HANDLED_SYMBOL)) { + fhandled=value; + return 0; + } + return VHashReference::put_element(name, value); +} + +HashStringValue& VException::hash() { + if(fhash) + return *fhash; + HashStringValue& result=*new HashStringValue; - // $.handled(0) - hash.put(exception_handled_part_name, &VBool::get(false)); + if(const char* type=fexception.type(true)) + result.put(Symbols::TYPE_SYMBOL, new VString(*new String(type))); + if(problem_source) + result.put(Symbols::SOURCE_SYMBOL, new VString(*problem_source)); + if(origin.file_no) { + result.put(Symbols::FILE_SYMBOL, new VString(ffile)); + result.put(Symbols::LINENO_SYMBOL, new VInt(1+origin.line)); + result.put(Symbols::COLNO_SYMBOL, new VInt(1+origin.col)); + } + if(const char* comment=fexception.comment(true)) + result.put(Symbols::COMMENT_SYMBOL, new VString(comment)); + result.put(Symbols::HANDLED_SYMBOL, fhandled); - return Request::Exception_details(origin, problem_source, vhash); + fhash=&result; + return result; } Temp_value_element::Temp_value_element(Request& arequest, Value& awhere, const String& aname, Value* awhat) :